951 字
5 分钟
ram_load_precopy处理 RAM 数据接收的核心函数
这段代码是 QEMU 热迁移中目标端处理 RAM 数据接收的核心函数。让我为您详细解释:
🎯 函数概述
ram_load_precopy()
- 在预拷贝模式下加载内存页数据
作用: 目标端从迁移流中读取并恢复源端发送的内存页数据
🔧 初始化和配置检查
MigrationIncomingState *mis = migration_incoming_get_current();
int flags = 0, ret = 0, invalid_flags = 0, len = 0, i = 0;
// 根据配置设置无效标志
if (!migrate_compress()) {
invalid_flags |= RAM_SAVE_FLAG_COMPRESS_PAGE; // 不支持压缩页
}
if (migrate_mapped_ram()) {
// mapped-ram模式下,某些标志无效
invalid_flags |= (RAM_SAVE_FLAG_HOOK | RAM_SAVE_FLAG_MULTIFD_FLUSH |
RAM_SAVE_FLAG_PAGE | RAM_SAVE_FLAG_XBZRLE |
RAM_SAVE_FLAG_ZERO);
}
目的: 根据迁移配置确定哪些数据格式是不被支持的
🔄 主处理循环
while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) { // 直到遇到结束标志
ram_addr_t addr;
void *host = NULL, *host_bak = NULL;
uint8_t ch;
// 🎯 关键优化:周期性让出控制权
if ((i & 32767) == 0 && qemu_in_coroutine()) {
aio_co_schedule(qemu_get_current_aio_context(),
qemu_coroutine_self());
qemu_coroutine_yield(); // 让出CPU给其他任务
}
i++;
重要优化: 每处理 32768 个页面后让出控制权,避免长时间占用 CPU 影响 Guest OS 运行
📦 数据解析和验证
addr = qemu_get_be64(f); // 读取64位地址+标志
flags = addr & ~TARGET_PAGE_MASK; // 提取标志位
addr &= TARGET_PAGE_MASK; // 提取实际地址
if (flags & invalid_flags) {
error_report("Unexpected RAM flags: %d", flags & invalid_flags);
ret = -EINVAL;
break;
}
数据格式: 地址和标志打包在一个 64 位值中,高位是标志,低位是页面地址
🏠 内存块定位和 COLO 优化
if (flags & (RAM_SAVE_FLAG_ZERO | RAM_SAVE_FLAG_PAGE | ...)) {
RAMBlock *block = ram_block_from_stream(mis, f, flags, RAM_CHANNEL_PRECOPY);
host = host_from_ram_block_offset(block, addr); // 获取目标内存地址
// 🎯 COLO优化:容灾备份优化
if (migration_incoming_colo_enabled()) {
if (migration_incoming_in_colo_state()) {
// COLO阶段:所有页面暂存到缓存
host = colo_cache_from_block_offset(block, addr, true);
} else {
// 迁移阶段:同时写入缓存和内存
host_bak = colo_cache_from_block_offset(block, addr, false);
}
}
}
COLO 优化:
- 迁移阶段: 页面同时写入内存和 COLO 缓存,减少后续备份时间
- COLO 阶段: 页面只写入缓存,不直接修改 SVM 内存
🔀 不同数据类型处理
switch (flags & ~RAM_SAVE_FLAG_CONTINUE) {
case RAM_SAVE_FLAG_MEM_SIZE:
ret = parse_ramblocks(f, addr); // 解析内存块信息
if (migrate_mapped_ram()) {
multifd_recv_sync_main(); // 多线程同步
}
break;
case RAM_SAVE_FLAG_ZERO:
ch = qemu_get_byte(f);
ram_handle_zero(host, TARGET_PAGE_SIZE); // 零页处理
break;
case RAM_SAVE_FLAG_PAGE:
qemu_get_buffer(f, host, TARGET_PAGE_SIZE); // 普通页面
break;
case RAM_SAVE_FLAG_COMPRESS_PAGE:
len = qemu_get_be32(f);
decompress_data_with_multi_threads(f, host, len); // 压缩页面
break;
case RAM_SAVE_FLAG_XBZRLE:
if (load_xbzrle(f, addr, host) < 0) { // 增量压缩
error_report("Failed to decompress XBZRLE page");
ret = -EINVAL;
}
break;
case RAM_SAVE_FLAG_MULTIFD_FLUSH:
multifd_recv_sync_main(); // 多线程刷新同步
break;
case RAM_SAVE_FLAG_EOS:
// 正常结束,可能需要多线程同步
if (migrate_multifd() && !migrate_mapped_ram()) {
multifd_recv_sync_main();
}
break;
}
各种页面类型:
- MEM_SIZE: 内存布局信息
- ZERO: 全零页面 (优化传输)
- PAGE: 普通页面数据
- COMPRESS_PAGE: 压缩页面
- XBZRLE: 增量压缩页面
- MULTIFD_FLUSH: 多线程同步点
- EOS: 数据流结束
🎯 关键设计特点
1️⃣ 性能优化
// 周期性让出控制权,避免影响Guest OS
if ((i & 32767) == 0 && qemu_in_coroutine()) {
qemu_coroutine_yield();
}
2️⃣ 多种数据格式支持
- 支持压缩、增量压缩、零页优化等多种传输优化
3️⃣ COLO 容灾优化
// 在迁移过程中同时备份,减少后续COLO准备时间
if (migration_incoming_colo_enabled()) {
host_bak = colo_cache_from_block_offset(block, addr, false);
}
4️⃣ 多线程协调
- 支持多线程接收,在关键点进行同步
💡 总结
这个函数是 QEMU 热迁移目标端的核心,负责:
- 🔄 持续接收源端发送的内存页数据
- 🎯 智能处理不同类型的页面 (零页、压缩页、增量页等)
- ⚡ 性能优化周期性让出 CPU,不影响系统响应
- 🛡️ 容灾支持 COLO 模式下的优化备份策略
- 🔗 多线程协调支持并行传输和同步
这就是为什么热迁移能够高效传输大量内存数据的关键实现!
ram_load_precopy处理 RAM 数据接收的核心函数
https://scudays.github.io/posts/icsas/ram迁移/ram_load_precopy处理-ram-数据接收的核心函数/