Skip to the content.

virtio 有特殊的封装

static const VMStateDescription vmstate_virtio_blk = {
    .name = "virtio-blk",
    .minimum_version_id = 2,
    .version_id = 2,
    .fields = (const VMStateField[]) {
        VMSTATE_VIRTIO_DEVICE,
        VMSTATE_END_OF_LIST()
    },
};

关于 virtio

struct VirtioDeviceClass {
    // ....
     /* Saving and loading of a device; trying to deprecate save/load
     * use vmsd for new devices.
     */
    void (*save)(VirtIODevice *vdev, QEMUFile *f);
    int (*load)(VirtIODevice *vdev, QEMUFile *f, int version_id);
    /* Post load hook in vmsd is called early while device is processed, and
     * when VirtIODevice isn't fully initialized.  Devices should use this instead,
     * unless they specifically want to verify the migration stream as it's
     * processed, e.g. for bounds checking.
     */
    int (*post_load)(VirtIODevice *vdev);

不过,为什么 VirtioDeviceClass::save 和 virtio_net_class_init::load 只有 virtio-blk 注册

virtio-blk , virtio-scsi 和 virtio-net 全部都不一样的注册方法。

这个文档表达了什么: codex

这份文档讲的是:QEMU 如何保存和恢复 virtio 设备的迁移状态,以及其中容易踩坑的加载顺序与兼容性问题。

它并不是在描述整个 live migration 算法,而是聚焦 virtio 设备模型的 VMState 序列化。

核心问题:一个 virtio 设备的状态分散在三层

文档把状态分成三部分:

层次 典型状态 负责者 ━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ virtio core status、协商后的 features、队列数量、vring 地址和 index VirtIODevice ───────────── ───────────────────────────────────────────────────────── ───────────────────────────────────── transport PCI 配置、MSI-X、queue vector;或 MMIO/CCW 特有状态 virtio-pci、virtio-mmio、virtio-ccw ───────────── ───────────────────────────────────────────────────────── ───────────────────────────────────── 具体设备 MAC、网卡 offload、未完成的块请求等 virtio-net、virtio-blk 等

这正是文档所说的 “twisty maze”:docs/devel/migration/virtio.rst:10。

因此,不能简单地把一个 VirtIODevice 结构体复制过去。

保存过程

当前代码的大体顺序是:

具体设备的 VMState wrapper | v virtio_save() | +– transport.save_config() | PCI config、MSI-X、config vector… | +– 保存 virtio core | status、ISR、features、config | +– 对每条 virtqueue | size、地址、last_avail_idx | transport.save_queue() | +– device.save()/device.vmsd | net、blk 等设备特有状态 | +– virtio subsections 高位 features、modern/packed queue 状态、 endianness、额外 transport 状态…

入口可以直接看 hw/virtio/virtio.c:3056。

以 virtio-pci 为例,transport 层会保存 PCI 和 MSI-X 状态:

恢复过程及最重要的坑

加载顺序基本是保存顺序的镜像:

transport 状态 ↓ virtio core 基础状态 ↓ virtqueue 基础状态 ↓ 设备特有状态 ↓ virtio subsections ↓ 完整 feature 校验 ↓ 重建 ring cache、校验 index ↓ 设备的最终 post_load

对应代码是 hw/virtio/virtio.c:3497。

最关键的问题是:

设备特有状态加载时,virtio subsections 可能还没有加载。

例如旧迁移格式最初只有低 32 位 feature;后来新增的高位 feature 被放进 subsection。加载设备状态时,完整的 feature bitmap 还未必可用。

因此,依赖完整 feature 的初始化不能过早进行:

device VMState load/post_load 这时完整 subsection 可能尚未加载 ↓ load virtio subsections ↓ 恢复和验证完整 features ↓ VirtioDeviceClass.post_load 这里才适合做 feature-dependent setup

当前代码在加载 subsection 后才调用 virtio_set_features_nocheck…(),并最终执行 vdc->post_load():hw/virtio/virtio.c:3606、hw/virtio/virtio.c:3694。

这就是文档最后强调的主要 caveat:docs/devel/migration/virtio.rst:104。

为什么 live migration 中 virtio 特别需要考虑

1. Virtqueue 一半在 guest RAM,一半在设备内部

desc/avail/used ring 本身位于 guest RAM,因此 RAM migration 会搬走它们;但是 QEMU 或 vhost 还保存着 host-side 状态,例如:

例如:

guest 把请求 42 放进 avail ring ↓ QEMU 已经取走请求,last_avail_idx 前进 ↓ 后端 I/O 尚未完成,因此 used ring 还没有记录请求 42 ↓ 此时开始迁移

如果只迁移 guest RAM:

所以必须让 ring 内存、host index 和设备的 in-flight 状态对应同一个逻辑时间点。当前代码还会重建 ring cache,并检查 guest/host index 是否一致:hw/virtio/ virtio.c:3635。

2. Feature 决定状态应该怎样解释

同一组字段会因为协商 feature 不同而有不同含义。例如:

代码明确区分:

见 hw/virtio/virtio.c:3640。

目的端还必须支持源端已经与 guest 协商成功的 features,否则不能安全恢复;当前代码会直接拒绝迁移状态:hw/virtio/virtio.c:3617。

3. Transport 状态不能从设备状态推导出来

virtio-net 并不知道:

但 guest 恢复后会继续按照迁移前的 transport 状态访问设备。因此这些状态必须由 transport 层单独迁移。

4. 后端可能不在 QEMU userspace 中

virtio datapath 可能由以下组件处理:

此时状态不只分散在 QEMU 对象中,还可能存在于内核、外部进程或硬件里。迁移切换点必须确保后端停止取新请求,并妥善处理 in-flight 请求,否则仅暂停 vCPU 不够。

这超出了该文档的主要范围,但在实际 live migration 中是非常重要的一层。

5. 迁移格式必须长期向后兼容

早期 virtio 状态大量使用手工的顺序 stream:

field A → field B → field C

不能随意在中间插入字段,否则旧版本目的端会把后续字节全部错位。因此后来新增状态普遍通过可选 subsection 表达。

当前 core 已经有很多 subsection:

见 hw/virtio/virtio.c:3034。

一句话总结

virtio 迁移的本质是:

在源端构造一个一致的快照,将 guest RAM 中的共享 virtqueue、QEMU 中的 virtio core 状态、transport 状态、具体设备状态以及可能位于外部 backend 的 in-flight 状态 一起转移;然后在完整 feature 已知后,按正确顺序重建目的端。

严格来说,所有有状态设备迁移都要考虑一致性;virtio 格外复杂,是因为它同时具有:

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