Skip to the content.

io 后端

具体代码分布

他们是拥有完全相同的结构的:

大端小端问题

migration/qemu-file.c 里字节序只在一种情况下真正需要考虑:往迁移流里写多字节 整数的元数据/控制字段时。

具体说:

一句话总结:写迁移流的结构化字段时必须用 qemu_put_be/qemu_get_be 保持大端格式;传原始字节块时无需也无法做字节序处理。

caller of migration_channel_connect

kimi 梳理

QEMUFile 不是”文件”

struct QEMUFile(migration/qemu-file.c:45)的全部内容:

即 QEMUFile 是一个带缓冲的字节流抽象(decorator),历史遗留名字(2003 年 savevm 写磁盘文件的抽象,QIOChannel 约 2015 年才出现)。

io/ 模块

qio_task:把异步操作(socket connect、TLS 握手)封装成”干活 + 主循环回 调”。qio_task_run_in_thread 把可能阻塞的活(TLS 握手要读证书、做密码学运 算)扔到 thread pool,完成后回主循环调 callback。

为什么需要 QEMUFile

  1. 历史(早于 QIOChannel)。
  2. 实际价值:缓冲减少 syscall;字节序/类型化 put/get helper(vmstate 序列化 全靠它);sticky 错误(qemu_file_shutdown 的注释:必须先置错误再 shutdown,否则收端可能装全零页导致 guest crash);fd 传递;zero-copy iov;file: 迁移需要的 seek/offset(qemu_put_buffer_at)。

save_zero_page 即典型用法:ram.c 只管往 QEMUFile* 写协议字段,不关心底 下是 tcp/unix/tls/fd。

migration channel 的 transport

入口:migration/channel.c 的 migration_connect_outgoing/incomingmigrate_uri_parse。URI scheme 决定 transport:tcp: unix: vsock: fd: (都走 socket 类)、rdma:exec:file:

收端 migration_channel_identify(channel.c:142)用 QIO_CHANNEL_FEATURE_READ_MSG_PEEK 偷看前 4 字节 magic 区分主通道 (QEMU_VM_FILE_MAGIC)和 multifd 通道(MULTIFD_MAGIC),因为多通道可能 乱序到达;TLS 握手时已确定身份所以不需要 peek。

qemu-file.c : kimi 的再次分析

qemu-file.c 本身只是迁移流的缓冲/发送层,内存页的数据是在 ram.c 里组织好后交给它的。发送内存的整体链路大致是:

上层:ram.c 组织要发的页

ram_save_iterate() → ram_find_and_save_block() → ram_save_page(),对每一页决定怎么发:

multifd 场景每个页被分发到某个 multifd 通道的 pss_channel,逻辑相同,只是 QEMUFile 不同。

qemu-file.c:QEMUFile 的发送机制

struct QEMUFile(qemu-file.c:45)核心是两级缓冲:

写入路径分两类:

qemu_put_buffer()(qemu-file.c:511)——有拷贝: 数据 memcpy 进 buf,buf 写满后 add_buf_to_iovec() 把这块 buf 挂进 iovec 数组。

qemu_put_buffer_async()(qemu-file.c:501)——零拷贝: 不拷贝,直接 add_to_iovec() 把 guest 物理内存的 host 虚拟地址挂进 iovec。add_to_iovec()(qemu-file.c:461)会把地址相邻 的项合并成一个大 iov,减少 writev 段数。如果调用时 may_free=true(postcopy 且开了 release-ram),对应位会记进 may_free 位图。

qemu_fflush()(qemu-file.c:281)——真正的发送点:

触发 flush 的时机:iovec 攒满 64 项自动 flush(add_to_iovec 里),或上层显式调用(比如一轮迭代结束、发 EOS 快照前)。

错误处理是惰性的:任何一次 IO 失败后错误记在 f->last_error,后续所有 put 操作直接 no-op,由上层在合适时机用 qemu_file_get_error() 检查。

两条旁路

简单画一下主线:

  ram_save_page()
    ├─ save_page_header()  → qemu_put_buffer()   (页头, memcpy 进 buf)
    └─ save_normal_page()
         └─ qemu_put_buffer_async()              (4K 页, 零拷贝挂 iovec)
              └─ add_to_iovec()  (相邻合并; 满 64 项 → qemu_fflush)
                   └─ qemu_fflush()
                        ├─ qio_channel_writev_all()   → socket
                        └─ madvise(DONTNEED)          (may_free 的页)

所以 qemu-file.c 的角色可以概括为:把上层零散的”写页头、写页数据”聚合成尽量少的 writev 系统调用, 并在发送后处理源端内存释放。

例如,我们可以观察到 qemu_put_buffer 的调用位置如此:

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。