kvm迁移中脏页位图机制源码分析

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ajfgurjfmvvlsfkjglkh/article/details/54948532

        我们都知道KVM支持在线迁移,而其在线迁移是通过内存预拷贝(迭代拷贝)机制来实现的。预拷贝的一个思想就是不断记录脏页并每一轮迭代拷贝脏页面,直至达到一定的条件就退出迭代,进行最后的停机拷贝。关于预拷贝的原理我之前有博客进行了简单分析,在这里就不重复说明了。那这篇文章主要是就其中的脏页记录机制从源码层面进行分析。

        这里分析的代码版本是kvm3.10.1和qemu 1.5.3。我们直接进入到迁移(migration)的线程函数Migration.c--->migration_thread(),直接看代码:

static void *migration_thread(void *opaque)
{
    //.........//省略部分代码
    qemu_savevm_state_begin(s->file, &s->params);

    while (s->state == MIG_STATE_ACTIVE) {
        int64_t current_time;
        uint64_t pending_size;

        if (!qemu_file_rate_limit(s->file)) {
            DPRINTF("iterate\n");
            pending_size = qemu_savevm_state_pending(s->file, max_size);
            DPRINTF("pending size %lu max %lu\n", pending_size, max_size);
            if (pending_size && pending_size >= max_size) {
                qemu_savevm_state_iterate(s->file);
            } else {
                //..........//省略部分代码
                qemu_savevm_state_complete(s->file);
                //..........//省略部分代码
            }
        }

        //..........//省略部分代码
        
    }

    //..........//省略部分代码

    return NULL;
}

        这个函数主要是完成迁移的初始化以及内存拷贝过程,这里先看一下里面qemu_savevm_state_begin()这个函数,他里面调用了save_live_setup这个函数指针,这个函数指针在之前被注册,指向了ram_save_setup()这个函数,具体的注册过程这里不进行分析,根据代码追一下可以追到的。ram_save_setup()主要是进行一些初始化的工作,下面看ram_save_setup()这个函数:

static int ram_save_setup(QEMUFile *f, void *opaque)
{
    ........

    migration_bitmap = bitmap_new(ram_pages);
    bitmap_set(migration_bitmap, 0, ram_pages);
    migration_dirty_pages = ram_pages;

    ........

    memory_global_dirty_log_start();
    migration_bitmap_sync();
    ........
    return 0;
}

        我们看这个函数,里面有一个migration_bitmap,没错,这就是迁移过程中用到的脏页位图,看其定义可以发现这是一个unsigned long *类型的全局变量,这个函数代码中首先给位图分配了内存空间,然后初始化为全0。memory_global_dirty_log_start()这个函数是开启脏页记录,实际上就是对内存页面进行写保护,这里略过不谈。migration_bitmap_sync()函数是同步脏页位图。那为什么要同步脏页位图呢?到底有多少脏页位图?我们下面来思考一下。

        我们知道脏页就是修改了内存页面,kvm可以认为是qemu和kvm内核两个层次,I/O主要是在qemu层次进行的,那qemu层可能会有大量的内存的修改。那kvm内核有没有页面的修改呢?实际上,kvm内核也可能发生少量的内存的修改。这样,kvm就需要在两个层次都要维持各自的脏页位图,并且要将这两层的位图进行同步。通过分析代码,kvm两个层次我们可以认为维护了三套位图:一是kvm内核维护的脏页位图memslot->dirty_bitmap,这个位图记录kvm内核中的脏页,kvm内核中所有的对内存页面的修改都会对应着修改这个位图的位;二是qemu层次维护的一个脏页位图ram_list_phys_dirty[],qemu层次对内存页面的修改都会相应的修改这个位图的位,并且它还会通过系统调用获取内核中的位图,并进行同步修改;三是迁移时具体使用的位图migration_btimap,这也是在qemu层的,但是它是复制ram_list_phys_dirty[]的相应位。

        

        我们一项一项分析,先看看migration_bitmap是如何复制ram_list_phys_dirty[]的。这个复制的过程是在migration_bitmap_sync()函数实现的,先看代码:

static void migration_bitmap_sync(void)
{
    ........
    trace_migration_bitmap_sync_start();
    memory_global_sync_dirty_bitmap(get_system_memory());

    QTAILQ_FOREACH(block, &ram_list.blocks, next) {
        for (addr = 0; addr < block->length; addr += TARGET_PAGE_SIZE) {
            if (memory_region_test_and_clear_dirty(block->mr,
                                                   addr, TARGET_PAGE_SIZE,
                                                   DIRTY_MEMORY_MIGRATION)) {
                migration_bitmap_set_dirty(block->mr, addr);
            }
        }
    }
    trace_migration_bitmap_sync_end(migration_dirty_pages
                                    - num_dirty_pages_init);
    ........
}
        我们分析这个函数,里面memory_global_sync_dirty_bitmap()就是将kvm内核中的位图同步到ram_list_phys_dirty[]中,这样就保证了现在ram_list_phys_dirty[]是完整的位图。memory_region_test_and_clear_dirty()是判断ram_list_phys_dirty[]该位是否置位,如果置位则清位,migration_bitmap_set_dirty()这个函数则是将migration_bitmap置位,这两个函数结合,就将ram_list_phys_dirty[]中为1的位复制到了migration_bitmap中。这里引申一点做个说明,迁移时,如果migration_bitmap相应位为1,就将页面保存,并将migration_bitmap位图相应位清位(migration_bitmap_find_and_reset_dirty()函数来实现的),这样就能保证migration_bitmap的准确性和实时性。

        我们再分析一下前面提到的几个函数。memory_global_sync_dirty_bitmap()这里先不分析,后面在分析内核与qemu位图同步时候再分析。先看memory_region_test_and_clear_dirty()这个函数:

bool memory_region_test_and_clear_dirty(MemoryRegion *mr, hwaddr addr,
                                        hwaddr size, unsigned client)
{
    bool ret;
    assert(mr->terminates);
    ret = cpu_physical_memory_get_dirty(mr->ram_addr + addr, size,
                                        1 << client);
    if (ret) {
        cpu_physical_memory_reset_dirty(mr->ram_addr + addr,
                                        mr->ram_addr + addr + size,
                                        1 << client);
    }
    return ret;
}
        cpu_physical_memory_get_dirty()  --->  cpu_physical_memory_get_dirty_flag()  -->  return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS]  这就获得了ram_list.phys_dirty[]相应位的数值,然后判断如果是1,执行cpu_physical_memory_reset_dirty()  -->  cpu_physical_memory_mask_dirty_range()  -->  cpu_physical_memory_clear_dirty_flags()  -->  ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] &= mask  ,这就清了相应位。

       再看 migration_bitmap_set_dirty()这个函数:

static inline bool migration_bitmap_set_dirty(MemoryRegion *mr,
                                              ram_addr_t offset)
{
    bool ret;
    int nr = (mr->ram_addr + offset) >> TARGET_PAGE_BITS;

    ret = test_and_set_bit(nr, migration_bitmap);

    if (!ret) {
        migration_dirty_pages++;
    }
    return ret;
}
        里面test_and_set_bit(nr, migration_bitmap),这个函数将migration_bitmap置位。


        分析完migration_bitmap是如何复制ram_list_phys_dirty[]的,我们再分析ram_list_phys_dirty[]是如何维护的。跟内核的同步这一部分前面也提到过,具体的后面讲解。这里就分析qemu中如何随时修改的这个位图。实际上修改这个位图的函数是cpu_physical_memory_set_dirty_flags(),qemu中大概有几十处直接或间接调用这个函数,在这里就不再一一讲解。


        最后我们分析内核与qemu位图的同步以及内核中位图的维护。内核中的位图是memslot->dirty_bitmap,它的修改是通过mark_page_dirty_in_slot()这个函数,而mark_page_dirty_in_slot()这个函数又被kvm_write_guest_cached()和mark_page_dirty()这两个函数调用,kvm中大概有十几处调用了这两个函数,这里也不一一讲解了。

        关于内核与qemu位图的同步我们前面提到过memory_global_sync_dirty_bitmap()这个函数,这个过程实际上是qemu主动发起的系统调用,具体分析其代码:
memory_global_sync_dirty_bitmap()  -->  log_sync  -->  kvm_log_sync() -->  kvm_physical_sync_dirty_bitmap(),分析kvm_physical_sync_dirty_bitmap()这个函数,里面有几行代码如下:

        memset(d.dirty_bitmap, 0, allocated_size);

        d.slot = mem->slot;

        if (kvm_vm_ioctl(s, KVM_GET_DIRTY_LOG, &d) == -1) {
            DPRINTF("ioctl failed %d\n", errno);
            ret = -1;
            break;
        }

        kvm_get_dirty_pages_log_range(section, d.dirty_bitmap);
        我们可以看到,这就是ioctl系统调用,命令参数是KVM_GET_DIRTY_LOG,将内核中的位图复制到了d.dirty_bitmap中,后面又利用d来修改了ram_list_phys_dirty[]。既然是系统调用,那我们追踪到kvm内核中来看,找到kvm_vm_ioctl()函数,其中一段代码:

	case KVM_GET_DIRTY_LOG: {
		struct kvm_dirty_log log;

		r = -EFAULT;
		if (copy_from_user(&log, argp, sizeof log))
			goto out;
		r = kvm_vm_ioctl_get_dirty_log(kvm, &log);
		break;
	}
        这一段代码里看一下kvm_vm_ioctl_get_dirty_log()函数:

int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
{
	......

	dirty_bitmap = memslot->dirty_bitmap;
	r = -ENOENT;
	if (!dirty_bitmap)
		goto out;

	n = kvm_dirty_bitmap_bytes(memslot);

	dirty_bitmap_buffer = dirty_bitmap + n / sizeof(long);
	memset(dirty_bitmap_buffer, 0, n);

	spin_lock(&kvm->mmu_lock);

	for (i = 0; i < n / sizeof(long); i++) {
		unsigned long mask;
		gfn_t offset;

		if (!dirty_bitmap[i])
			continue;

		is_dirty = true;

		mask = xchg(&dirty_bitmap[i], 0);
		dirty_bitmap_buffer[i] = mask;

		offset = i * BITS_PER_LONG;
		kvm_mmu_write_protect_pt_masked(kvm, memslot, offset, mask);
	}
	if (is_dirty)
		kvm_flush_remote_tlbs(kvm);

	spin_unlock(&kvm->mmu_lock);

	r = -EFAULT;
	if (copy_to_user(log->dirty_bitmap, dirty_bitmap_buffer, n))
		goto out;

	r = 0;
out:
	mutex_unlock(&kvm->slots_lock);
	return r;
}
        这个函数实际上是将memslot->dirty_bitmap最终通过copy_to_user()函数复制到了qemu层。这就实现了两个层次的位图同步。














展开阅读全文

没有更多推荐了,返回首页