和 Dirty bitmap 有一定的关系。

Currently, there are 3 listener types:

  • struct MemoryListener
  • struct DeviceListener
  • struct DisplayChangeListener

struct MemoryListener / memory_listeners QEMU

这是一个全局的链表,包含了所有的 listener,尽管每一个 listener listen 的 AddressSpace 可能不一样。

static QTAILQ_HEAD(, MemoryListener) memory_listeners = QTAILQ_HEAD_INITIALIZER(memory_listeners);
/**
 * struct MemoryListener: callbacks structure for updates to the physical memory map
 *
 * Allows a component to adjust to changes in the guest-visible memory map.
 * Use with memory_listener_register() and memory_listener_unregister().
 */
struct MemoryListener {
    // The address space this listener is listening...
    AddressSpace *address_space;
    // Called at the beginning of an address space update transaction.
    // begin 和 commit 之间只有五个其他函数:
    //  - .region_add()
    //  - .region_del()
    //  - .region_nop()
    //  - .log_start()
    //  - .log_stop()
    // 其他的函数,比如 log_sync_global 不需要这样
    void (*begin)(MemoryListener *listener);

    // Called at the end of an address space update transaction,
    // after the last call to
    //  - #MemoryListener.region_add(),
    //  - #MemoryListener.region_del(),
    //  - #MemoryListener.region_nop(),
    //  - #MemoryListener.log_start(),
    //  - #MemoryListener.log_stop().
    // 很容易理解,毕竟就是为了 commit 
    void (*commit)(MemoryListener *listener);

    // Called during an **address space** update transaction,
    // for a section of the address space that is new in this address space
    // since the last transaction.
    // 注意,添加的是 MemoryRegionSection 而不是 MemoryRegion
    // address space 相关的事务中,如果有一个新的 section,那么会调用这个函数
    void (*region_add)(MemoryListener *listener, MemoryRegionSection *section);

    // Called during an address space update transaction,
    // for a section of the address space that has disappeared in the address
    // space since the last transaction.
    // 注意,删除的是 MemoryRegionSection 而不是 MemoryRegion
    // address space 相关的事务中,如果要删除一个旧的 section,那么会调用这个函数
    void (*region_del)(MemoryListener *listener, MemoryRegionSection *section);

    // Called during an address space update transaction,
    // for a section of the address space that is in the same place in the address
    // space as in the last transaction.
    // address space 相关的事务中,如果一个 section 没有变,那么会调用这个函数
    void (*region_nop)(MemoryListener *listener, MemoryRegionSection *section);

    // Called during an address space update transaction, after one of
    //  - #MemoryListener.region_add(),
    //  - #MemoryListener.region_del(),
    //  - #MemoryListener.region_nop(),
    // if dirty memory logging clients have become active since the last transaction.
    // address space 相关的事务中,如果 dirty memory logging clients 被激活了,主要是为了记录 dirty memory
    void (*log_start)(MemoryListener *listener, MemoryRegionSection *section, int old, int new);

    /**
     * Called during an address space update transaction, after
     * one of #MemoryListener.region_add(), #MemoryListener.region_del() or
     * #MemoryListener.region_nop() and possibly after
     * #MemoryListener.log_start(), if dirty memory logging clients have
     * become inactive since the last transaction.
     */
    void (*log_stop)(MemoryListener *listener, MemoryRegionSection *section, int old, int new);

    // log_sync_global 和 log_sync 只能定义一个
    /**
     * This is the global version of @log_sync when the listener does
     * not have a way to synchronize the log with finer granularity.
     * When the listener registers with @log_sync_global defined, then
     * its @log_sync must be NULL.  Vice versa.
     */
    void (*log_sync_global)(MemoryListener *listener, bool last_stage);

    /**
     * Called by memory_region_snapshot_and_clear_dirty() and
     * memory_global_dirty_log_sync(), before accessing QEMU's "official"
     * copy of the dirty memory bitmap for a #MemoryRegionSection.
     */
    void (*log_sync)(MemoryListener *listener, MemoryRegionSection *section);

memory_listener_register()

listener 挂到:

  • 传进来的 as->listeners 里面;
  • 全局的 memory_listeners 里面。
void memory_listener_register(MemoryListener *listener, AddressSpace *as)
{
    MemoryListener *other = NULL;

    //...
    listener->address_space = as;
    // 虽然看起来复杂,但是很简单,memory_listeners 里面排列是根据 priority 从小到大的。
    // 我们在插入时也要遵守这一规则
    if (QTAILQ_EMPTY(&memory_listeners) || listener->priority >= QTAILQ_LAST(&memory_listeners)->priority) {
        QTAILQ_INSERT_TAIL(&memory_listeners, listener, link);
    } else {
        QTAILQ_FOREACH(other, &memory_listeners, link) {
            if (listener->priority < other->priority) {
                break;
            }
        }
        QTAILQ_INSERT_BEFORE(other, listener, link);
    }

    // 同上,只不过插入的对象变成了 as->listeners 而不是 memory_listeners
    if (QTAILQ_EMPTY(&as->listeners) || listener->priority >= QTAILQ_LAST(&as->listeners)->priority) {
        QTAILQ_INSERT_TAIL(&as->listeners, listener, link_as);
    } else {
        QTAILQ_FOREACH(other, &as->listeners, link_as) {
            if (listener->priority < other->priority) {
                break;
            }
        }
        QTAILQ_INSERT_BEFORE(other, listener, link_as);
    }

    // 调用 listener 里相关的 callback
    listener_add_address_space(listener, as);
    //...
}

.log_.*() And .log_global.*() QEMU

相比于 .log_global_.*().log_.*() 是更加被青睐的,因为其有更细的粒度。

.log_start() / .log_global_start() QEMU

Global 表示的是 global_dirty_tracking;普通的表示的是 fr->dirty_log_mask

都是在 listener_add_address_space()listener_del_address_space() 的时候会被调用。只不

  • global 的只调用一次,
  • log_start() 会对于加入的 as 的所有 MR section 都会调用一次。

.log_sync() / .log_sync_global() QEMU

  • 如果使用了 dirty ring,那么使用 .log_sync_global()
  • 如果使用了 dirty bitmap,那么使用 .log_sync()

在调用的时候,会先 check 有没有 .log_sync(),没有才会使用 .log_sync_global()

Trigger the memory listener callbacks / QEMU

  • listener_add_address_space()
  • listener_del_address_space()
  • memory_region_sync_dirty_bitmap()

listener_add_address_space() QEMU

当要把一个 listener 加入到 as->listeners 的时候,会调用。

static void listener_add_address_space(MemoryListener *listener, AddressSpace *as)
{
    FlatView *view;
    FlatRange *fr;

    // KVMMemoryListener 并没有设置这个
    // vHost Memory Listener 设置了
    // 暂时先不看
    if (listener->begin)
        listener->begin(listener);

    // 我们正处在 global log start 和 log end 之中
    // 因为在 start 的时候,当时调用了所有 listener 的 log_global_start()
    // 所以在我们注册这个新的 listener 的时候,也要调一下不能漏掉。
    if (global_dirty_tracking)
        if (listener->log_global_start)
            listener->log_global_start(listener);

    view = address_space_get_flatview(as);
    FOR_EACH_FLAT_RANGE(fr, view) {
        MemoryRegionSection section = section_from_flat_range(fr, view);

        // 这个 as 的 listeners 新 add 了 listener
        // 对这个 as 的所有 mr section,触发了 add 事件,调用 add
        if (listener->region_add)
            listener->region_add(listener, &section);
        // 如果这个 mask 置上了,那么调用 local 的 log_start
        if (fr->dirty_log_mask && listener->log_start)
            listener->log_start(listener, &section, 0, fr->dirty_log_mask);
    }
    // commit 并不是针对 section,而是整个 as
    if (listener->commit)
        listener->commit(listener);
    flatview_unref(view);
}

listener_del_address_space() QEMU

代码结构和 listener_add_address_space() 应该说完全一样,不过注意顺序:

listener_add_address_space
    region_add
        log_start
        log_stop
    region_del
listener_del_address_space
static void listener_del_address_space(MemoryListener *listener,
                                       AddressSpace *as)
{
    FlatView *view;
    FlatRange *fr;

    if (listener->begin) {
        listener->begin(listener);
    }
    view = address_space_get_flatview(as);
    FOR_EACH_FLAT_RANGE(fr, view) {
        MemoryRegionSection section = section_from_flat_range(fr, view);

        if (fr->dirty_log_mask && listener->log_stop)
            listener->log_stop(listener, &section, fr->dirty_log_mask, 0);
        if (listener->region_del)
            listener->region_del(listener, &section);
    }
    if (listener->commit)
        listener->commit(listener);
    flatview_unref(view);
}

memory_region_sync_dirty_bitmap() QEMU

static void memory_region_sync_dirty_bitmap(MemoryRegion *mr, bool last_stage)
{
    MemoryListener *listener;
    AddressSpace *as;
    FlatView *view;
    FlatRange *fr;

    /* If the same address space has multiple log_sync listeners, we
     * visit that address space's FlatView multiple times.  But because
     * log_sync listeners are rare, it's still cheaper than walking each
     * address space once.
     */
    QTAILQ_FOREACH(listener, &memory_listeners, link) {
        if (listener->log_sync) {
            as = listener->address_space;
            view = address_space_get_flatview(as);
            FOR_EACH_FLAT_RANGE(fr, view) {
                if (fr->dirty_log_mask && (!mr || fr->mr == mr)) {
                    MemoryRegionSection mrs = section_from_flat_range(fr, view);
                    // 对于每一个 memory section 调用每一个 listener 的 log_sync
                    // 因为大多数的 listener 的 log_sync 函数都是空的,所以 cost 是可以接受的。
                    listener->log_sync(listener, &mrs);
                }
            }
            flatview_unref(view);
            trace_memory_region_sync_dirty(mr ? mr->name : "(all)", listener->name, 0);
        } else if (listener->log_sync_global) {
            // 没法更细粒度的 sync,所以就 global sync 了。
            listener->log_sync_global(listener, last_stage);
            trace_memory_region_sync_dirty(mr ? mr->name : "(all)", listener->name, 1);
        }
    }
}

struct KVMMemoryListener QEMU

KVMMemoryListenerMemoryListener 的一种。只不过每一个 callback 都用了自己定义的函数。

KVMMemoryListener 只有一个全局的 object(KVMState->memory_listener)。

typedef struct KVMMemoryListener {
    MemoryListener listener;
    KVMSlot *slots;
    int as_id;
    // 表示所有的
    QSIMPLEQ_HEAD(, KVMMemoryUpdate) transaction_add;
    QSIMPLEQ_HEAD(, KVMMemoryUpdate) transaction_del;
} KVMMemoryListener;
// 以 kvm-memory 为例
kvm_init
    kvm_memory_listener_register(s, &s->memory_listener, &address_space_memory, 0, "kvm-memory");
        memory_listener_register
            listener_add_address_space
                listener->begin(listener);
                listener->region_add(listener, &section);
                listener->commit(listener);

kvm_memory_listener_register() QEMU

void kvm_memory_listener_register(KVMState *s, KVMMemoryListener *kml,
                                  AddressSpace *as, int as_id, const char *name)
{
    int i;

    // kml 里的数量和 KMVState->nr_slots 一致
    kml->slots = g_new0(KVMSlot, s->nr_slots);
    kml->as_id = as_id;

    // 这样能让每一个 slot 直到自己在 kml->slots 里的位置
    for (i = 0; i < s->nr_slots; i++)
        kml->slots[i].slot = i;

    QSIMPLEQ_INIT(&kml->transaction_add);
    QSIMPLEQ_INIT(&kml->transaction_del);

    // kml 是 MemoryListener 的子类。
    kml->listener.region_add = kvm_region_add;
    kml->listener.region_del = kvm_region_del;
    kml->listener.commit = kvm_region_commit;
    kml->listener.log_start = kvm_log_start;
    kml->listener.log_stop = kvm_log_stop;
    kml->listener.priority = MEMORY_LISTENER_PRIORITY_ACCEL;
    kml->listener.name = name;

    // log_sync 和 log_sync_global 只能赋值一个
    // 当使用 dirty ring 而不是 dirty bitmap 的时候,使用 global 的
    if (s->kvm_dirty_ring_size) {
        kml->listener.log_sync_global = kvm_log_sync_global;
    } else {
        kml->listener.log_sync = kvm_log_sync;
        kml->listener.log_clear = kvm_log_clear;
    }

    memory_listener_register(&kml->listener, as);

    // 找到第一个没有被分配的地方,分配 as 和 kml
    for (i = 0; i < s->nr_as; ++i) {
        if (!s->as[i].as) {
            s->as[i].as = as;
            s->as[i].ml = kml;
            break;
        }
    }
}

kvm_region_add() QEMU

唯一的作用就是把传进来的 section insert 到 kml->transaction_add 这个 list 后面。表示现在这个 section 正在 transaction 里。

static void kvm_region_add(MemoryListener *listener, MemoryRegionSection *section)
{
    KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
    // typedef struct KVMMemoryUpdate {
    //     QSIMPLEQ_ENTRY(KVMMemoryUpdate) next;
    //     MemoryRegionSection section;
    // } KVMMemoryUpdate;
    KVMMemoryUpdate *update;

    update = g_new0(KVMMemoryUpdate, 1);
    update->section = *section;

    QSIMPLEQ_INSERT_TAIL(&kml->transaction_add, update, next);
}

kvm_region_del() QEMU

唯一的作用就是把传进来的 section insert 到 kml->transaction_del 这个 list 后面。

static void kvm_region_del(MemoryListener *listener,
                           MemoryRegionSection *section)
{
    KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
    KVMMemoryUpdate *update;

    update = g_new0(KVMMemoryUpdate, 1);
    update->section = *section;

    QSIMPLEQ_INSERT_TAIL(&kml->transaction_del, update, next);
}

kvm_region_commit() QEMU

这个函数会对 transaction_addtransaction_del 这两个 list 进行一些处理。核心思想是:

  • transaction_del 里的所有 section,调用 KVM_SET_USER_MEMORY_REGION remove;
  • transaction_add 里的所有 section,调用 KVM_SET_USER_MEMORY_REGION add。
static void kvm_region_commit(MemoryListener *listener)
{
    KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
    KVMMemoryUpdate *u1, *u2;
    bool need_inhibit = false;

    // 如果两个 list 都是空的
    if (QSIMPLEQ_EMPTY(&kml->transaction_add) && QSIMPLEQ_EMPTY(&kml->transaction_del))
        return;

    // 虽然下面这一大段代码非常长,但是核心思想就是找到需不需要 inhibit
    //   - 如果有 overlap 的 section,那么需要
    //   - 如果没有,那么不需要
    // The lists are order by addresses, so it's easy to find overlaps.
    // 找到两个 overlap 的 Range
    u1 = QSIMPLEQ_FIRST(&kml->transaction_del);
    u2 = QSIMPLEQ_FIRST(&kml->transaction_add);
    while (u1 && u2) {
        Range r1, r2;

        // 把 u1, u2 的 (start, size) 更新到 r1, r2 中去
        range_init_nofail(&r1, u1->section.offset_within_address_space, int128_get64(u1->section.size));
        range_init_nofail(&r2, u2->section.offset_within_address_space, int128_get64(u2->section.size));

        // 如果有交叉,说明找到了,break 出去
        if (range_overlaps_range(&r1, &r2))
            break;

        if (range_lob(&r1) < range_lob(&r2))
            u1 = QSIMPLEQ_NEXT(u1, next);
        else
            u2 = QSIMPLEQ_NEXT(u2, next);
    }

    // 根据上面的判断结果来判断要不要 block ioctl
    if (need_inhibit) {
        accel_ioctl_inhibit_begin();
    }

    // Remove all memslots before adding the new ones.
    while (!QSIMPLEQ_EMPTY(&kml->transaction_del)) {
        // 取走一个并移除
        u1 = QSIMPLEQ_FIRST(&kml->transaction_del);
        QSIMPLEQ_REMOVE_HEAD(&kml->transaction_del, next);

        // 我们是对 transaction_del 里的所有 section 对应的 memory slot
        // 进行更改或删除,而不是添加。
        // 这么做的目的是 Remove all memslots before adding the new ones.
        kvm_set_phys_mem(kml, &u1->section, false);
        // unref and freeing...
    }
    while (!QSIMPLEQ_EMPTY(&kml->transaction_add)) {
        // 取走一个并移除
        u1 = QSIMPLEQ_FIRST(&kml->transaction_add);
        QSIMPLEQ_REMOVE_HEAD(&kml->transaction_add, next);

        // 我们是添加一个新的 slot。
        kvm_set_phys_mem(kml, &u1->section, true);
        // unref and freeing...
    }
    // unlocking...
}

kvm_log_start() QEMU

只有在 MR 的 flags 改了才会调用到 kvm_set_user_memory_region

这里并不会对 global 与否进行区分,也就是还没有 dirty ring 和 dirty logging 的分野。

// old 必然是 0
static void kvm_log_start(MemoryListener *listener, MemoryRegionSection *section, int old, int new)
{
    KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
    // 只有在 MR 的 flags 改了才会调用到 kvm_set_user_memory_region。
    kvm_section_update_flags(kml, section);
    //...
}

kvm_log_stop() QEMU

这里并不会对 global 与否进行区分,也就是还没有 dirty ring 和 dirty logging 的分野。

// new 必然是 0
static void kvm_log_stop(MemoryListener *listener, MemoryRegionSection *section, int old, int new)
{
    KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
    // 只有在 MR 的 flags 改了才会调用到 kvm_set_user_memory_region。
    kvm_section_update_flags(kml, section);
    //...
}