2018-03-01

热迁移的用法

虚拟化环境中热迁移的好处是很明显的,所以QEMU/KVM在很早就支持了热迁移。 首先我们来看一下热迁移是怎么用的。按照官网指示,一般来说需要迁移的src和dst同时访问虚拟机镜像,这里为了简单起见,我们只是在两台host使用同一个虚拟机镜像。

在src启动一个虚拟机vm1:

qemu-system-x86_64  -m 2048   -hda centos.img  -vnc :0 --enable-kvm

在dst启动另一个虚拟机vm2:

qemu-system-x86_64  -m 2048   -hda centos.img  -vnc :0 --enable-kvm -incoming tcp:0:6666

在vm1的的monitor里面输入:

migrate tcp:$ip:6666

隔了十几秒可以看到vm2已经成为了vm1的样子,vm1处于stop状态。

热迁移的基本原理

首先看看热迁移过程中qemu的哪些部分会包含进来。上图中间的灰色部分是虚拟机的内存,它对于qemu来说是完全的黑盒,qemu不会做任何假设,而只是一股脑儿的发送到dst。左边的区域是表示的设备状态,这部分是虚拟机可见的,qemu使用自己的协议来发送这部分。右边的是不会迁移的部分,但是还是将dst和src保持一致,所以一般来说,src和dst的虚拟机使用相同的qemu command line能够保证这部分一致。

需要满足很多条件才能进行热迁:

1. 使用共享存储,如NFS
2. host的时间要一致
3. 网络配置要一致,不能说src能访问某个网络,dst不能
4. host CPU类型要一致,毕竟host导出指令集给guest
5. 虚拟机的机器类型,QEMU版本,rom版本等

热迁移主要包括三个步骤:

1. 将虚拟机所有RAM设置成dirty,主要函数:ram_save_setup
2. 持续迭代将虚拟机的dirty RAM page发送到dst,直到达到一定条件,不如dirty page数量比较少, 主要函数:ram_save_iterate
3. 停止src上面的guest,把剩下的dirty RAM发送到dst,之后发送设备状态,主要函数: qemu_savevm_state_complete_precopy

其中步骤1和步骤2是上图中的灰色区域,步骤3是灰色和左边的区域。

之后就可以在dst上面继续运行qemu程序了。

发送端源码分析

在qemu的monitor输入migrate命令后,经过的一些函数:

hmp_migrate
  ->qmp_migrate
      ->tcp_start_outgoing_migration
          ->socket_start_outgoing_migration
              ->socket_outgoing_migration
                  ->migration_channel_connect
                      ->qemu_fopen_channel_output
                      ->migrate_fd_connect

最后这个函数就重要了,创建了一个迁移线程,线程函数为migration_thread

void migrate_fd_connect(MigrationState *s)
{
    xxx 

    qemu_thread_create(&s->thread, "migration", migration_thread, s,
                    QEMU_THREAD_JOINABLE);
    s->migration_thread_running = true;
}


static void *migration_thread(void *opaque)
{

    xxx
    qemu_savevm_state_begin(s->to_dst_file, &s->params);

    xxx
    while (s->state == MIGRATION_STATUS_ACTIVE ||
        s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
        xxx
        if (!qemu_file_rate_limit(s->to_dst_file)) {
            uint64_t pend_post, pend_nonpost;

            qemu_savevm_state_pending(s->to_dst_file, max_size, &pend_nonpost,
                                    &pend_post);
            xxx
            if (pending_size && pending_size >= max_size) {
                xxx
                /* Just another iteration step */
                qemu_savevm_state_iterate(s->to_dst_file, entered_postcopy);
            } else {
                migration_completion(s, current_active_state,
                                    &old_vm_running, &start_time);
                break;
            }
        }

        xxx
}

migration_thread主要就是用来完成之前提到的热迁移三个步骤。 首先来看第一个步骤,qemu_savevm_state_begin标记所有RAM为dirty:

qemu_savevm_state_begin
-->ram_save_setup
    -->ram_save_init_globals
            -->bitmap_new
            -->bitmap_set

接着看第二个步骤,由migration_thread中的while循环中的两个函数完成: qemu_savevm_state_pending和qemu_savevm_state_iterate。

第一个函数通过调用回调函数ram_save_pending确定还要传输的字节数,比较简单。 第二个函数通过调用回调函数ram_save_iterate用来把dirty传到dst上面。

ram_save_iterate –>ram_find_and_save_block –>find_dirty_block –>ram_save_host_page –>ram_save_target_page –>migration_bitmap_clear_dirty –>ram_save_page –>qemu_put_buffer_async –>…->qemu_fflush –>…->send

在while循环中反复调用ram_save_pending和ram_save_iterate不停向dst发送虚拟机脏页,直到达到一定的条件,然后进入第三个步骤。

第三个步骤就是在migration_thread中调用migration_completion,在这一步中会停止src虚拟机,然后把最后剩的一点脏页拷贝到dst去。

migration_completion
-->vm_stop_force_state
-->bdrv_inactivate_all
-->qemu_savevm_state_complete_precopy
    -->ram_save_complete
            -->ram_find_and_save_block

可以看到最后一个函数跟第二个阶段传输脏页一样了。

接收端源码分析

接收端的qemu运行参数跟发送端的一样,但是多了一个参数-incoming tcp:0:6666, qemu在解析到-incoming后,就会等待src迁移过来,我们来看看这个流程。

main –>qemu_start_incoming_migration –>tcp_start_incoming_migration –>socket_start_incoming_migration –>socket_accept_incoming_migration –>migration_channel_process_incoming –>qemu_fopen_channel_input –>migration_fd_process_incoming –>process_incoming_migration_co –>qemu_loadvm_state ..->bdrv_invalidate_cache_all

process_incoming_migration_co函数用来完成接收数据,恢复虚拟机的运行。最重要的是qemu_loadvm_state,用于接收数据,在dst重构虚拟机。

int qemu_loadvm_state(QEMUFile *f)
{
    xxx  检查版本

    ret = qemu_loadvm_state_main(f, mis);
    
    xxx
    cpu_synchronize_all_post_init();

    return ret;
}

显然,qemu_loadvm_state_main是构建虚拟机的主要函数。

static int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis)
{
    uint8_t section_type;
    int ret = 0;

    while ((section_type = qemu_get_byte(f)) != QEMU_VM_EOF) {
        ret = 0;
        trace_qemu_loadvm_state_section(section_type);
        switch (section_type) {
        case QEMU_VM_SECTION_START:
        case QEMU_VM_SECTION_FULL:
            ret = qemu_loadvm_section_start_full(f, mis);
            if (ret < 0) {
                goto out;
            }
            break;
        case QEMU_VM_SECTION_PART:
        case QEMU_VM_SECTION_END:
            ret = qemu_loadvm_section_part_end(f, mis);
            if (ret < 0) {
                goto out;
            }
            break;
        case QEMU_VM_COMMAND:
            ret = loadvm_process_command(f);
            trace_qemu_loadvm_state_section_command(ret);
            if ((ret < 0) || (ret & LOADVM_QUIT)) {
                goto out;
            }
            break;
        default:
            error_report("Unknown savevm section type %d", section_type);
            ret = -EINVAL;
            goto out;
        }
    }

out:
    if (ret < 0) {
        qemu_file_set_error(f, ret);
    }
    return ret;
}

qemu_loadvm_state_main在一个循环里面处理各个section, src会把QEMU_VM_SECTION_START等标志放到流中。

qemu_loadvm_section_start_full
  -->find_se
  -->vmstate_load
       -->ram_load
            -->qemu_get_buffer

最后一个函数负责把接收到的数据拷贝到dst这端虚拟机内存上。 本文就是对热迁移的简单分析,后面会对一些具体的问题进行分析。

参考

Amit Shah: Live Migrating QEMU-KVM Virtual Machines



blog comments powered by Disqus