本篇文章基于qemu2.6.0,Linux3.10.0内核,x86体系结构。Qemu与KVM同步脏页位图主要分为Qemu与KVM两部分,我们先来看看Qemu部分。
Qemu部分
主要内容:Qemu将KVM返回的位图设置到自己的ram_list->dirty_memory->blocks中的map位图
address_space_sync_dirty_bitmap
主要调用了kvm_log_sync将kvm内核中的位图(kvm_memory_slot.dirty_bitmap
)同步到qemu。本函数在热迁移过程中被migration_bitmap_sync
函数调用。
void address_space_sync_dirty_bitmap(AddressSpace *as)
{
FlatView *view;
FlatRange *fr;
view = address_space_get_flatview(as);
/* 对每一个flat range调用memory_listeners里的所有log_sync。由于只有kvm_memory_listener中
* log_sync是初始化了的,所以相当于对每一个flat range调用kvm_memory_listener中log_sync
*/
FOR_EACH_FLAT_RANGE(fr, view) {
//这里调用kvm_log_sync
MEMORY_LISTENER_UPDATE_REGION(fr, as, Forward, log_sync);
}
flatview_unref(view);
}
kvm_log_sync
//这是下面kvm_log_sync函数的第二个参数section,一个FlatRange转换成一个MemoryRegionSection
(&(MemoryRegionSection) {
\
.mr = (fr)->mr, \
.address_space = (as), \
.offset_within_region = (fr)->offset_in_region, \
.size = (fr)->addr.size, \
.offset_within_address_space = int128_get64((fr)->addr.start), \//起始物理地址
.readonly = (fr)->readonly, \
})
static void kvm_log_sync(MemoryListener *listener,
MemoryRegionSection *section)
{
//这里返回listener所属的KVMMemoryListener。每个MemoryListener都包含在一个KVMMemoryListener
//大家或许有疑问:这里返回KVMMemoryListener干嘛?KVMMemoryListener串起了整个VM物理地址空间对应的
//KVMSlot。所以下面kvm_physical_sync_dirty_bitmap的section就可以通过kml找到对应的KVMSlot
KVMMemoryListener *kml = container_of(listener, KVMMemoryListener, listener);
int r;
r = kvm_physical_sync_dirty_bitmap(kml, section);
if (r < 0) {
abort();
}
}
static int kvm_physical_sync_dirty_bitmap(KVMMemoryListener *kml,
MemoryRegionSection *section)
{
KVMState *s = kvm_state;
unsigned long size, allocated_size = 0;
struct kvm_dirty_log d = {
};
KVMSlot *mem;
int ret = 0;
//这里计算出section对应的物理地址区间
hwaddr start_addr = section->offset_within_address_space;
hwaddr end_addr = start_addr + int128_get64(section->size);
d.dirty_bitmap = NULL;
while (start_addr < end_addr) {
//根据物理地址区间找到对应的KVMSlot
mem = kvm_lookup_overlapping_slot(kml, start_addr, end_addr);
......
//把物理地址区间大小转换成 页框数对应位图的字节数。一个页框对应位图的一个位
//所以这里除8就是要分配位图的字节数
size = ALIGN(((mem->memory_size) >> TARGET_PAGE_BITS),
/*HOST_LONG_BITS*/ 64) / 8;
if (!d.dirty_bitmap) {
d.dirty_bitmap = g_malloc(size);//先给位图分配空间
} else if (size > allocated_size) {
d.dirty_bitmap = g_realloc(d.dirty_bitmap, size);
}
allocated_size = size;
//设置为全0,d.dirty_bitmap就是等下要传递给内核的参数,让内核将其脏页位图同步到d.dirty_bitmap
memset(d.dirty_bitmap, 0, allocated_size);
//把地址空间id 和 KVMSlot编号传递给内核,以便其查找到对应的 kvm_memory_slot
d.slot = mem->slot | (kml->as_id << 16);
//主要完成将内核slot的脏页位图设置到d.dirty_bitmap。且重置内核slot的脏页位图
//和清除EPT页表项的脏标志。KVM使用 kvm_vm_ioctl_get_dirty_log 完成以上操作
//具体过程请看 内核部分
if (kvm_vm_ioctl(s, KVM_GET_DIRTY_LOG, &d) == -1) {
DPRINTF("ioctl failed %d\n", errno);
ret =