深入 QEMU 热迁移

发布时间:2026/5/25 7:55:22

深入 QEMU 热迁移 深入 QEMU 热迁移从状态机到数据平面的全链路剖析“把一台正在运行的虚拟机从一台主机搬到另一台还让里面的操作系统浑然不觉——这听起来像魔法实则是精密的工程。”引言实时迁移是 QEMU 最核心的子系统之一。它允许将一个正在运行的虚拟机透明地从源主机迁移到目的主机停机时间通常控制在几百毫秒以内。整个迁移框架在客户机继续执行的同时传输完整的 VM 状态——RAM、设备寄存器、CPU 状态等——宛如在飞驰的列车上更换引擎。本文基于 QEMU 源码中 migration/ 目录的约 30 个源文件从架构、状态机、数据平面、压缩与加速四个维度对热迁移系统进行全链路剖析。一、架构概览源端与目的端的职责分离QEMU 的迁移子系统围绕源端出站迁移和目的端入站迁移之间清晰的职责分离来构建两者通过一种与传输方式无关的字节流进行连接。┌─────────────────── Source Host ───────────────────┐ ┌─────────────────── Destination Host ──────────────┐ │ │ │ │ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │ │ MigrationState │ ┌─────────┐ QEMUFile │ │ QEMUFile ┌─────────┐ │ MigrationIncoming│ │ │ │ (migration.c/h) │──│ QIOCh. │════════════════│─────│════════════════│ QIOCh. │──│ State │ │ │ │ │ └─────────┘ │ │ └─────────┘ │ (migration.c/h) │ │ │ │ - to_dst_file │ │ │ │ - from_src_file │ │ │ │ - rp_state │ ←── Return Path ──────────│─────│────────── Return Path ────→ │ - to_src_file │ │ │ └──────────────────┘ │ │ │ - fault_thread │ │ │ │ │ └──────────────────┘ │ └────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────┘关键数据结构MigrationState源端核心状态机持有to_dst_file到目的端的文件流、返回路径线程状态、迁移参数、能力位图、COLO 状态等。MigrationIncomingState目的端核心状态持有from_src_file、userfault fd、page request 树、后拷贝信号量等。两者之间的通信通过QEMUFile抽象进行底层由QIOChannel提供传输无关的字节流支持 TCP socket、UNIX socket、exec、RDMA 等多种后端。二、迁移状态机精密编排的状态流转每个迁移实例都经历一系列定义明确的状态推进通过 QMP 暴露的MigrationStatus枚举进行跟踪。理解这个状态机是理解整个迁移流程的钥匙。┌──────────┐ │ none │ └────┬─────┘ │ migrate QMP command ┌────▼─────┐ │ setup │ ←── 初始化通道、multifd 线程 └────┬─────┘ │ ┌────▼─────┐ ┌─────│ active │──────┐ │ └────┬─────┘ │ │ │ │ converging postcopy pause-before │ trigger switchover │ │ │ ┌─────▼──┐ ┌────▼──────┐ ┌───▼───────────┐ │wait- │ │postcopy- │ │pause-before- │ │device │ │active │ │switchover │ └───┬────┘ └────┬──────┘ └───┬───────────┘ │ │ │ ▼ ▼ ▼ ┌───────────────────────────────┐ │ completed │ └───────────────────────────────┘ 任何状态 ──→ failing ──→ failed postcopy-active ──→ postcopy-pause ──→ postcopy-recover状态转换的核心逻辑在 migration.c 中定义migrate_set_state()负责原子地更新状态并触发 QMP 事件通知。三、预拷贝迁移生命周期默认的迁移模式是预拷贝precopy即在客户机继续运行的同时传输大部分内存。3.1 主循环迭代传输脏页源端迁移线程的入口是 migration_thread()其核心循环如下// 简化后的主循环逻辑while(migration_is_active()){if(urgent||!migration_rate_exceeded(s-to_dst_file)){MigIterateState iter_statemigration_iteration_run(s);// 迭代传输脏页...}thr_errormigration_detect_error(s);// 检测网络故障等urgentmigration_rate_limit();// 速率限制与紧迫页处理}每一轮迭代扫描脏位图找出自上次传输以来被修改的内存页传输脏页到目的端检查收敛条件剩余脏页是否低于threshold_size由期望停机时间和实测带宽计算得出若收敛进入 switchover 阶段否则继续迭代3.2 脏页追踪脏页追踪是预拷贝迁移的基石。QEMU 通过DirtyMemoryBlocksRCU 方案实现高效的脏位图扫描迁移线程持有 RCU 读锁而非更重的 ramlist 互斥锁扫描位图RAM 热插拔操作可以并发扩展位图每个 RAMBlock 嵌入了bmap、receivedmap、clear_bmap、file_bmap等迁移专用位图3.3 CPU 节流自动收敛当脏页产生速度超过传输速度时QEMU 会启动自动收敛机制——通过 cpu-throttle.c 降低 vCPU 执行频率迫使客户机产生脏页的速度下降直至迁移收敛。最大节流比例由max-cpu-throttle参数控制默认 99%。四、后拷贝迁移用缺页中断换停机时间后拷贝是一种激进的迁移策略先快速传输 CPU 状态和非 RAM 设备状态让 VM 在目的端尽早启动然后按需拉取内存页。4.1 核心机制userfaultfd后拷贝的核心依赖是 Linux 的userfaultfd系统调用。目的端 QEMU 在迁移开始前为所有 RAMBlock 注册 userfaultfd 区域启动 fault_thread 监听缺页事件当 VM 在目的端访问尚未传输的页面时内核暂停该 vCPU 并通知 fault_threadfault_thread 通过返回路径向源端发送MIG_RP_MSG_REQ_PAGES请求源端的 source_return_path_thread() 收到请求后优先传输被请求的页面// 目的端请求页面的核心逻辑简化intmigrate_send_rp_req_pages(MigrationIncomingState*mis,RAMBlock*rb,ram_addr_tstart,...){WITH_QEMU_LOCK_GUARD(mis-page_request_mutex){if(!ramblock_recv_bitmap_test_byte_offset(rb,start)){g_tree_insert(mis-page_requested,aligned,(gpointer)1);qatomic_inc(mis-page_requested_count);}}// 向源端发送 REQ_PAGES 消息...}4.2 后拷贝抢占后拷贝面临一个经典问题当源端正在传输后台批量页面时目的端突然发生紧急缺页请求的页面可能被排在长队列后面。解决方案是后拷贝抢占——建立一条独立的紧急通道postcopy_qemufile_dst由专门的 postcopy_prio_thread 处理。紧急页面通过这条通道优先传输避免被批量数据阻塞。4.3 页面请求的空间局部性优化QEMU 还利用了空间局部性原理通过 PageLocationHint 结构在后拷贝抢占模式下源端在发送紧急请求页之后会顺便发送相邻的页面减少未来的缺页中断次数。五、Multifd并行传输引擎单通道传输在高带宽网络上是瓶颈。Multifd 通过创建 N 条并行通道来充分利用带宽。5.1 架构定义在 multifd.hmultifd 在源端创建 N 个发送线程mig/src/send_%d在目的端创建 N 个接收线程mig/dst/recv_%d通道数由x-multifd-channels参数控制默认 2。Source Destination ┌──────────────┐ ┌──────────────┐ │ Main Thread │═══════════════════│ Main Thread │ ├──────────────┤ ├──────────────┤ │ send_0 │═══════════════════│ recv_0 │ │ send_1 │═══════════════════│ recv_1 │ │ ... │ │ ... │ │ send_N-1 │═══════════════════│ recv_N-1 │ └──────────────┘ └──────────────┘每个 multifd 包的结构定义在 MultiFDPacket_ttypedefstruct{MultiFDPacketHdr_t hdr;// magic version flagsuint32_tpages_alloc;// 最大分配页数uint32_tnormal_pages;// 非零页数uint32_tnext_packet_size;// 数据负载大小uint64_tpacket_num;// 包序号uint32_tzero_pages;// 零页数charramblock[256];// 所属 RAMBlock 名称uint64_toffset[];// 每页的偏移量}MultiFDPacket_t;5.2 可插拔压缩后端Multifd 的压缩后端是完全可插拔的每个后端实现为独立文件后端文件说明无压缩multifd-nocomp.c基线直接传输原始页zlibmultifd-zlib.c通用压缩zstdmultifd-zstd.c高压缩比Intel QPLmultifd-qpl.cIntel 硬件加速Huawei UADKmultifd-uadk.c华为硬件加速Intel QATzipmultifd-qatzip.cIntel QAT 压缩卡压缩标志通过MULTIFD_FLAG_COMPRESSION_MASK位域标识接收端根据 flags 分派到对应的解压函数。六、返回路径双向通信的生命线迁移并非单向数据流。目的端需要向源端发送控制消息例如请求特定页面、确认切换、报告接收位图等。这就是返回路径Return Path。源端的 source_return_path_thread() 专门处理来自目的端的消息消息类型定义在 mig_rp_message_type消息类型方向用途MIG_RP_MSG_SHUTDST→SRC目的端关闭不再发送 RP 消息MIG_RP_MSG_PONGDST→SRC响应 PING确认通道存活MIG_RP_MSG_REQ_PAGESDST→SRC后拷贝期间请求特定页面MIG_RP_MSG_REQ_PAGES_IDDST→SRC带 RAMBlock ID 的页面请求MIG_RP_MSG_RECV_BITMAPDST→SRC回传接收位图MIG_RP_MSG_RESUME_ACKDST→SRC确认已准备好恢复MIG_RP_MSG_SWITCHOVER_ACKDST→SRC确认可以执行切换返回路径的建立通过 open_return_path_on_source() 完成它从to_dst_file的底层 QIOChannel 获取反向文件句柄并启动专门的线程。七、设备状态序列化VMState 框架迁移不仅是搬运内存页——设备的内部状态同样需要完整传输。QEMU 通过VMState框架实现设备状态的声明式序列化。每个可迁移设备通过SaveVMHandlers注册一组回调回调用途save_setup/load_setup初始化迁移传输save_live_iterate预拷贝迭代中传输可迭代状态save_live_complete_precopy预拷贝最终阶段传输剩余数据has_postcopy指示是否支持后拷贝模式save_postcopy_prepare为向后拷贝转换做准备load_state接收并应用状态数据save_cleanup/load_cleanup迁移完成后释放资源VirtIO 设备的迁移尤为复杂通过多子节 VMState 描述处理virtio.c 附近的vmstate_virtio仅当设备状态与默认值不同时才序列化减少传输量。八、专用传输与高级特性8.1 RDMA 迁移rdma.c 实现了基于 RDMA 的迁移通道利用远程直接内存访问绕过内核协议栈在高带宽低延迟网络如 InfiniBand上大幅提升传输性能。源端通过 RDMA WRITE 操作直接将页面写入目的端内存无需目的端 CPU 参与。8.2 COLO连续带外活体复制colo.c 实现了 COLOCOntinuous LOck-step模式——主备 VM 同步运行通过比较输出实现近乎零停机的容错。当检测到输出不一致时回滚到上一检查点适合对可用性要求极高的场景。8.3 CPR进程检查点与重启cpr.c 和 cpr-exec.c 实现了 CPRCheckPoint and Restart迁移模式允许将 VM 状态保存后在同一主机的新 QEMU 进程中恢复用于 QEMU 升级等场景。8.4 XBZRLE增量编码缓存xbzrle.c 实现了基于 XOR 的增量编码。源端维护一个页面缓存每次传输时只发送当前页与缓存版本的差异对于频繁修改相同页面的工作负载如数据库效果显著。九、关键配置参数速查参数默认值说明downtime-limit300 ms最大允许停机时间max-bandwidth1 GiB/s迁移最大带宽x-multifd-channels2并行 multifd 通道数x-multifd-page-count16每个 multifd 包的页数x-xbzrle-cache-size64 MiBXBZRLE 增量缓存大小max-cpu-throttle99%最大 CPU 节流比例十、源码导航地图migration/ ├── Core State Machine │ ├── migration.c/h — 状态机、主循环、QMP 命令实现 │ ├── options.c/h — 迁移参数解析与校验 │ └── global_state.c — 全局 VM 状态序列化 ├── Transport Layer │ ├── channel.c/h — QIOChannel 迁移通道抽象 │ ├── socket.c/h — TCP/UNIX socket 传输 │ ├── fd.c/h — 文件描述符传输 │ ├── file.c/h — 文件传输 │ ├── exec.c/h — exec 传输 │ ├── rdma.c/h — RDMA 传输 │ └── tls.c/h — TLS 加密传输 ├── RAM Migration │ ├── ram.c/h — RAM 脏页追踪、迭代传输 │ ├── xbzrle.c/h — XBZRLE 增量编码 │ ├── page_cache.c/h — 页面缓存XBZRLE 用 │ └── dirtyrate.c/h — 脏页速率统计 ├── Multifd Parallel Engine │ ├── multifd.c/h — 多通道并行框架 │ ├── multifd-nocomp.c — 无压缩基线 │ ├── multifd-zlib.c — zlib 压缩 │ ├── multifd-zstd.c — zstd 压缩 │ ├── multifd-qpl.c — Intel QPL 硬件加速 │ ├── multifd-uadk.c — 华为 UADK 加速 │ ├── multifd-qatzip.c — Intel QAT 压缩卡 │ └── multifd-zero-page.c — 零页优化 ├── Postcopy │ ├── postcopy-ram.c/h — 后拷贝 RAM 处理、userfaultfd ├── COLO │ ├── colo.c — COLO 主逻辑 │ ├── colo-failover.c — COLO 故障切换 │ └── multifd-colo.c/h — COLO multifd 集成 ├── CPR │ ├── cpr.c — CPR 公共逻辑 │ ├── cpr-exec.c — CPR exec 模式 │ └── cpr-transfer.c — CPR transfer 模式 ├── Device State │ ├── savevm.c/h — VMState 保存/恢复入口 │ ├── vmstate.c — VMState 描述符处理 │ ├── vmstate-types.c — 常用类型序列化 │ ├── block-active.c — 块设备活跃状态 │ ├── block-dirty-bitmap.c — 块设备脏位图 │ └── vfio.c / vfio-stub.c — VFIO 设备状态迁移 └── Infrastructure ├── qemu-file.c/h — 迁移字节流抽象 ├── migration-stats.c/h — 迁移统计信息 ├── cpu-throttle.c — CPU 节流 └── yank_functions.c — 网络中断安全清理结语QEMU 的热迁移系统是一个精巧的分布式状态机——它要在源端和目的端之间协调数十 GB 的内存传输、数千个设备的状态序列化、亚秒级的停机时间控制还要优雅地处理网络中断和硬件故障。理解它的关键在于状态机是骨架——所有行为都受MigrationStatus枚举驱动脏页追踪是心脏——决定了什么数据需要传输Multifd 是肌肉——提供并行传输的吞吐量后拷贝是利刃——用缺页中断换取极致的停机时间VMState 是灵魂——确保设备状态完整一致

相关新闻