qmp_migrate命令实现流程
迁移发起端:
入口函数qmp_migrate(migration.c)
> qmp_migrate
|--> migrate_init
|--> ? tcp_start_outgoing_migration
|--> ? rdma_start_outgoing_migration
|--> ? exec_start_outgoing_migration
|--> ? exec_start_outgoing_migration
|--> ? unix_start_outgoing_migration
|--> ? fd_start_outgoing_migration
函数说明工作如下:
1. 检查是否支持迁移
2. 函数migrate_init,初始化描述迁移的结构MigrationState,此时迁移状态为MIG_STATE_SETUP
3. 假设走tcp方式迁移,那么迁移的执行函数为tcp_start_outgoing_migration
假设走fd方式迁移,那么迁移的执行函数为fd_start_outgoing_migration,等等。
如果是fd迁移,调用函数路径如下(根据前面的参数说明,会主要讲解这部分流程):
> fd_start_outgoing_migration(migration-fd.c)
|--> monitor_get_fd:由uri传入迁移的fdname,得到fd
|--> qemu_fdopen(savevm.c),根据fd申请QEMUFileSocket结构s和QEMUFile结构f,其中s->fd=fd, s->file=f,f->opaque=s,f->ops=&unix_write_ops(只写fd)
|--> migrate_fd_connect
如果是tcp迁移,调用函数路径如下:
> tcp_start_outgoing_migration(migration-tcp.c)
|--> inet_nonblocking_connect(qemu-sockets.c)
|--> inet_connect_opts(qemu-sockets.c) : 建立socket连接,监听目的端虚拟机
|--> inet_connect_addr
|--> qemu_set_fd_handler2
?-> wait_for_connect:注册的fd_write函数
|-> tcp_wait_for_connect(callback函数)
|--> qemu_fopen_socket(savevm.c)
|--> migrate_fd_connect
inet_connect_opts函数
主要工作如下:
1. 建立qemu socket,调用函数qemu_socket
2. 连接socket
3. 注册IO事件,这里主要注册fd_write函数为wait_for_connect,此函数参数参数为connect_state指针,注册的回调函数为tcp_wait_for_connect
接下来,当tcp连接建立完成,会调用回调函数tcp_wait_for_connect
主要工作如下:
1. 连接成功后,打开qemu socket,获得socket fd file,调用函数qemu_fopen_socket
此函数根据fd申请QEMUFileSocket结构s和QEMUFile结构f,其中s->fd=fd, s->file=f,f->opaque=s,f->ops=&socket_write_ops(只写fd)
2. 准备开始迁移虚拟机,调用函数migrate_fd_connect,此时迁移状态为MIG_STATE_SETUP.
函数migrate_fd_connect
主要工作如下:
1. 设置迁移状态
2. 设置迁移速度等
3. 创建并启动迁移线程,其中线程执行函数为migration_thread(migration.c)
函数migration_thread
主要工作如下:
1. 函数qemu_savevm_state_begin
调用se->ops->save_live_setup,内存部分被注册为函数ram_save_setup,完成内容如下:
1.1 申请并初始化一个内存bitmap,用于记录内存的数据变更
1.2 调用函数migration_bitmap_sync根据内存中的脏数据来更新bitmap信息
2. 设置迁移状态为MIG_STATE_ACTIVE
3. while循环条件为判断状态是否为MIG_STATE_ACTIVE,如果是,就一直迭代执行内存数据拷贝操作
3.1 迁移失败,状态为MIG_STATE_ERROR
3.2 迁移成功,状态为MIG_STATE_COMPLETED
拷贝内存数据时,经历了以下2个阶段:
1. 迭代拷贝阶段:
1.1 qemu_savevm_state_pending --> ram_save_pending,调用migration_bitmap_sync,重新获取bitmap中的脏数据
1.2 qemu_savevm_state_iterate --> ram_save_iterate,调用函数ram_save_block,将内存中的数据写入buffer中并等待socket传输到目的端,
这里需要注意的一点是,每次处理数据时长默认小于50ms,因为内存数据拷贝过程中需要加锁,加锁阶段业务的请求不可访问内存!!
2. 拷贝结束阶段:
进入此阶段的条件是:[真实剩余内存数据大小] <= [数据传输带宽 * 最大中断业务时间(默认30ms)]
2.1 qemu_system_wakeup_request:唤醒虚拟机,恢复虚拟机状态
2.2 vm_stop_force_state:中断源端虚拟机运行,传入参数RUN_STATE_FINISH_MIGRATE
2.3 qemu_savevm_state_complete --> ram_save_complete:停机期间,调用函数ram_save_block,将最后的所有内存数据写入buffer中并等待socket传输到目的端,
然后调用函数migration_end,完成迁移过程中申请内存的释放
2.4 调用回调函数migrate_fd_cleanup:
2.4.1 关闭迁移线程
2.4.2 关闭QEMUFile
2.4.3 如果最终状态不是MIG_STATE_COMPLETED,则调用函数qemu_savevm_state_cancel --> ram_migration_cancel --> migration_end 释放为迁移申请的内存空间等,
然后把状态改成MIG_STATE_CANCELLED
迁移目的端:
入口函数main(vl.c)
工作内容如下:
1. 解析--incoming参数,将目的端的状态设置为RUN_STATE_INMIGRATE
2. 调用函数qemu_start_incoming_migration接收迁移过来的数据
> qemu_start_incoming_migration(migration.c)
|-- ? tcp_start_incoming_migration
|-- ? rdma_start_incoming_migration
|-- ? exec_start_incoming_migration
|-- ? unix_start_incoming_migration
|-- ? fd_start_incoming_migration
上述共有5种方式接收数据,以目的端是通过tcp方式接收数据为例说明。
> tcp_start_incoming_migration(migration-tcp.c)
|--> inet_listen:监听指定的端口
|--> qemu_set_fd_handler2:注册IO处理事件,并加入到io_handlers链表,此事件执行函数tcp_accept_incoming_migration,此为注册的read函数
> tcp_accept_incoming_migration(migration-tcp.c)
|--> qemu_accept:接收链接
|--> qemu_set_fd_handler2:注册IO处理事件,因为这里传入的读写执行函数均为空,表明是要删除上次注册的IO处理事件
|--> qemu_fopen_socket:此函数根据fd申请QEMUFileSocket结构s和QEMUFile结构f,其中s->fd=fd, s->file=f,f->opaque=s,f->ops=&socket_read_ops(只读fd)
|--> process_incoming_migration:开启协程,执行函数process_incoming_migration_co,从buffer中读取数据
|--> closesocket: 迁移结束,关闭套接字
> process_incoming_migration_co(migration-tcp.c)
|--> qemu_loadvm_state:从buffer中读取数据
|--> qemu_fclose:关闭打开的socket,并释放QEMUFile结构
|--> bdrv_invalidate_cache_all:将元数据下刷到磁盘中,qcow2文件格式注册有此函数,实现方式是先close file,然后再open file ^_^|||
|--> runstate_set(RUN_STATE_PAUSED):将虚拟机状态设置为paused,下一步libvirt会发一个run命令给虚拟机
函数qemu_loadvm_state(savevm.c)
工作内容如下:
1. 初始化loadvm_handlers链表
2. 判断是否支持在线迁移
3. 循环读取当前buffer对应位置的sizeof(uint8_t)个字符,这个是记录的section_type,表示数据接收开始,完成等信息,
如果section_type!=0,循环就一直进行
4. 循环结束,所有数据接收完毕,调用cpu_synchronize_all_post_init函数同步并初始化所有cpu
第3步的循环内主要调用函数vmstate_load完成数据的读取。
> vmstate_load(vmstate_load)
|--> vmstate_load_state
|--> field->info->get,其中get根据field->name被注册成各个函数,最终调用函数qemu_get_byte完成数据获取
|--> vmstate_subsection_load
> qemu_peek_byte
|--> qemu_fill_buffer
|--> f->ops->get_buffer,如果是通过socket接收数据,则被注册为socket_get_buffer函数,最终调用qemu_recv函数获取数据
以上,qemu部分的在线迁移主流程就介绍完毕,还有诸多细节,有用到的地方再细看。