Listener in QEMU
和 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, §ion);
// 如果这个 mask 置上了,那么调用 local 的 log_start
if (fr->dirty_log_mask && listener->log_start)
listener->log_start(listener, §ion, 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, §ion, fr->dirty_log_mask, 0);
if (listener->region_del)
listener->region_del(listener, §ion);
}
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
KVMMemoryListener
是 MemoryListener
的一种。只不过每一个 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, §ion);
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_add
和 transaction_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);
//...
}