Chinaunix首页 | 论坛 | 博客
  • 博客访问: 122258
  • 博文数量: 25
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 251
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-29 14:18
个人简介

不以物喜,勿以己悲;乐观向上,持之以恒。

文章分类

全部博文(25)

文章存档

2015年(25)

我的朋友

分类: 云计算

2015-08-25 16:45:44


qmp_drive_mirror命令实现流程

迁移发起端:
入口函数qmp_drive_mirror(blockdev.c)
> qmp_drive_mirror(blockdev.c)
     |--> bdrv_open:打开target指定的设备
     |--> mirror_start:创建mirror
               |--> mirror_start_job:此函数会传入一个回调函数block_job_cb用于mirror_job完成后处理等操作
                         |--> block_job_create: 申请BlockJob结构job,初始化后返回
                         |--> qemu_coroutine_create(mirror_run),交给协程处理mirror操作,执行函数注册为mirror_run
                         |--> qemu_coroutine_enter:执行函数完成后的处理,一般可以在这里执行回调函数
函数mirror_run(block/mirror.c)工作流程如下:
1. bdrv_is_allocated_above --> bdrv_set_dirty,获取设备要迁移的数据,如果要迁移,就设置bitmap中该block的位为dirty
2. 循环开始迁移bitmap中为drity的数据,直到虚拟机满足切换条件为止(should_complete被赋值或者block_job_is_cancelled)
     2.1 bdrv_get_dirty_count,统计需要迁移的数据块个数
     2.2 mirror_iteration,开始迁移数据
     mirror_complete时会将should_complete赋值为true,此函数由libvirt调用命令qmp_block_job_complete触发
           block_job_is_cancelled是由libvirt调用命令qmp_block_job_cancel触发
3. 迁移完成后,调用函数bdrv_drain_all下刷所有数据
4. 调用函数block_job_completed,释放申请的block job资源

从上述流程中可以看到,qemu层自己维护了一个bitmap,根据bitmap中标记的dirty_page做为迁移依据,即:
1. 全量数据盘迁移bitmap中所有位置为1;
2. 增量数据盘迁移根据bitmap记录的哪些位为dirty决定,因为虚拟机IO请求都要走qeme层,所以可以做到记录每个数据块的写操作;
3. 迁移过程中的增量数据,依然可以继续找到bitmap中其对应的位,然后置为1。

因此,在增量数据迁移时,整个过程最重要两个步骤是:
1. 初始化bitmap位图:根据每个driver自己注册的bdrv_co_get_block_status接口获取,此接口由函数bdrv_is_allocated_above调用
2. 根据bitmap位图迁移数据:调用函数mirror_iteration完成

第一步:位图设置
> bdrv_is_allocated_above(block.c)
     |--> bdrv_is_allocated
               |--> bdrv_get_block_status
                         |--> bdrv_get_block_status_co_entry
                                   |--> bdrv_co_get_block_status
                                             |--> bs->drv->bdrv_co_get_block_status
假设磁盘文件是file/qcow2,则bs->drv->bdrv_co_get_block_status被注册为函数qcow2_co_get_block_status(block/qcow2.c)
> qcow2_co_get_block_status
     |--> qcow2_get_cluster_offset

 函数qcow2_get_cluster_offset根据传入offset从qcow2的l1_table和l2_table中查找是否有对应的值,最终返回下面几个状态:
 1. COMPRESSED:压缩过的数据
 2. ZERO:写过,但写的是0
 3. UNALLOCATED:未写,或者数据在base上
 4. NORMAL:正常的数据
 经过处理,上述1、2、4的数据为需要迁移的数据;3的数据需要额外处理,后续还会提及到。

假设磁盘文件是block/raw,则bs->drv->bdrv_co_get_block_status被注册为函数raw_co_get_block_status(block/raw.c),
此函数直接返回>0的值,经过处理,全部为需要迁移的数据,即raw格式的磁盘默认全部迁移。

第二步:按位迁移
> mirror_iteration(block/mirror.c)
     |--> bdrv_aio_readv:异步从源端磁盘中读数据
               |--> bdrv_co_aio_rw_vector:启动一个协程,执行函数为bdrv_co_do_rw
                         ?--> bdrv_co_do_rw:此处处理读
                                   |--> bdrv_co_do_readv:传入的第一个参数为bs,下面的drv=bs->drv,注定了最后的读函数进入第一层设备类型
                                             |--> bdrv_co_do_preadv:其中io_limits_enabled决定是否限速
                                                       |--> bdrv_aligned_preadv
                                                                 |--> drv->bdrv_co_readv,此函数由第一层设备类型决定

假设磁盘文件是file/qcow2,则注册为qcow2_co_readv函数
> qcow2_co_readv(block/qcow2.c)
     |--> 如果要读的数据为UNALLOCATED,则在没有base image时,直接返回0,有base image时,从base中读
     |--> 如果要读的数据为ZERO,则直接返回0
     |--> 如果要读的数据为COMPRESSED,则先解压数据到buffer中,然后直接返回buffer中的数据(解压时读数据调用bdrv_read函数,有兴趣的可以跟一下)
     |--> 如果要读的数据为NORMAL,则需要进一步调用函数bdrv_co_readv(block.c)
               |--> bdrv_co_do_readv:传入的第一个参数为bs->file,下面的drv=bs->file->drv,注册了最后的读函数进入第二层设备类型
                         |--> bdrv_co_do_preadv:其中io_limits_enabled决定是否限速
                                   |--> bdrv_aligned_preadv
                                             |--> drv->bdrv_co_readv,因为第一层设备类型为file/qcow2,决定了第二层类型为file,其注册函数为raw_aio_readv函数
                                                       |--> raw_aio_readv(block/raw-posix.c)
                                                                 |--> raw_aio_submit
                                                                           |--> paio_submit,后续流程就为qemu实现的设备的读写

假设磁盘文件是block/raw,则注册为raw_co_readv函数
> raw_co_readv(block/raw.c) 
     |--> bdrv_co_readv(block.c)
               |--> bdrv_co_do_readv:传入的第一个参数为bs->file,下面的drv=bs->file->drv,注册了最后的读函数进入第二层设备类型
                         |--> bdrv_co_do_preadv:其中io_limits_enabled决定是否限速
                                   |--> bdrv_aligned_preadv
                                             |--> drv->bdrv_co_readv,因为第一层设备类型为block/raw,决定了第二层类型为host_device,其注册函数为raw_aio_readv函数
                                                       |--> raw_aio_readv(block/raw-posix.c)
                                                                 |--> raw_aio_submit
                                                                           |--> paio_submit,后续流程就为qemu实现的设备的读写

当数据读完成后,会执行定义好的回调函数mirror_read_complete
> mirror_read_complete(block/mirror.c)
     |--> bdrv_aio_writev:异步从源端磁盘中读数据
               |--> bdrv_aio_writev
                         |--> bdrv_co_aio_rw_vector:启动一个协程,执行函数为bdrv_co_do_rw
                                   ?--> bdrv_co_do_rw:此处处理写
                                             |--> bdrv_co_do_writev
                                                       |--> ... ... 后续流程大体同读,不再赘述
 
至此,drive-mirror源端的流程就分析到这里,还有些细节上的东西并没有提及,有兴趣的可以自己看看。

迁移目的端:
启动nbd server,然后将需要创建driver-mirror的disk导出(虚拟机有几块disk,就需要导出几次)。


未完 -- 待续。
阅读(4866) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~