virt-report虚拟化社区动态

设备与 I/O 虚拟化:从网卡模拟到共享队列与卸载

本文由 OpenAI Codex (GPT-6-Astra) 辅助编写。

一、设备接口与 I/O 处理路径

设备虚拟化向客户机提供磁盘、网卡等设备接口,并将客户机请求连接到宿主资源。以网络为例,一台物理服务器可能运行几十台虚拟机,却只有几块网卡;每台客户机仍需独立初始化设备、提交报文并接收完成通知。虚拟化层在连接真实网络的同时,需要隔离不同客户机的内存和请求。

这些名称涉及三个层次:e1000 与 virtio 规定客户机使用的设备接口;QEMU、vhost-net、vhost-user 安排请求的处理位置;VFIO 与 IOMMU 管理设备分配及内存访问隔离。例如客户机使用 virtio-net 驱动时,宿主可以选择 QEMU 或 vhost 后端,驱动提交请求的接口保持不变。

源码版本:QEMU 10.2.0、Linux 6.18。

二、一个报文的四步交接

设客户机要发送一个以太网帧 P1。各种后端都要完成以下交接,区别在于接口、执行者和每次交接的成本:

  1. 准备。驱动把 P1 放入内存,记录地址和长度。在请求完成前,这块内存不能被随意改写或释放。
  2. 提交。驱动发布请求,并按需要通知设备。请求描述处理什么,通知提示有工作可以处理。
  3. 处理。设备模型、宿主内核、外部进程或硬件读取 P1,交给网络出口。
  4. 归还。处理方确认不再引用原缓冲区后发布完成,驱动据此回收内存。远端是否收到报文,由网络协议另行确认。

例如先复制 P1 到宿主缓冲区,可以较早归还客户机内存;直接使用原缓冲区,则必须让它存活到最后一次访问结束。后面的方案比较都围绕这条路径展开。

三、模拟已有网卡,先解决驱动兼容

以 e1000 为例,客户机驱动配置网卡寄存器,准备硬件规定格式的发送描述符,再更新尾指针。QEMU 实现这些寄存器及状态变化,从客户机内存读取描述符和报文,然后交给 TAP 等网络后端。客户机因而可以使用已有驱动,无需知道网卡由软件实现。

代价来自兼容整套硬件协议:寄存器访问可能触发退出,描述符解析、状态更新和中断行为都需要实现。批处理能让多个报文共享一次通知,软件仍需维护硬件接口的语义。允许客户机使用 virtio 驱动后,双方可以采用面向虚拟化的通用交接格式。

四、virtio 用共享队列表达请求和完成

virtio 规定设备与驱动怎样协商能力、建立队列和归还缓冲区。客户机驱动是请求提交者,QEMU 或其他后端扮演设备。双方共享客户机内存中的结构,通过队列批量交接缓冲区,再用独立通知提示进度。传统 e1000 同样使用描述符和设备直接访问内存的 DMA 接口;virtio 的区别在于针对虚拟化统一这些约定,减少兼容具体硬件行为的负担。

Split virtqueue 的组成在 P1 中的作用更新者
描述符表 desc记录网络头与 P1 的地址、长度和访问方向驱动准备,设备读取
可用环 avail发布已经准备好的描述符链头驱动发布,设备消费
已用环 used记录已经可以归还的链设备发布,驱动回收

两个环循环使用数组槽位,报文本身保存在描述符指向的缓冲区。写好内容后才能发布进度,看到完成后才能复用缓冲区,这两条规则决定了队列的正确性。发送链让设备读取数据;接收链则预先提供可写空间。kick 与中断用于提示另一方检查队列,多项请求可以共享一次通知。

双方必须先确认共同支持的特性和队列格式,再配置共享区域并开始处理。字段、索引回绕与链 0 → 2 的变化见发送队列交互图,协商和启用顺序见virtio 设备初始化。客户机接口确定后,可以单独改变处理 P1 的后端。

五、保留 virtio 接口,改变队列处理位置

后端P1 的主要处理路径配置责任
QEMU 用户态设备模型客户机队列 → QEMU 进程 → TAPQEMU 配置并消费队列
宿主内核 vhost-net客户机队列 → 内核 vhost → TAP 对应网络路径QEMU 配置,内核处理报文
独立进程中的 vhost-user 后端客户机队列 → 后端进程 → 后端选择的出口QEMU 与后端进程协商资源

QEMU 可以自己消费队列,但高吞吐网络还可能受到用户态与内核交接、线程调度和缓存移动的影响。vhost-net 把 virtio-net 队列处理移入宿主内核,连接内核网络路径;QEMU 仍负责设备配置与控制,客户机无需更换 virtio 驱动。

vhost-user 让独立用户态进程消费队列。QEMU 通过 UNIX socket 协商特性、共享内存和通知文件描述符;报文通常从共享内存读取,不逐包走这条控制 socket。DPDK 后端可以接用户态交换或网卡驱动,SPDK 后端可以接存储栈。收益来自专门的数据处理实现,代价是跨进程内存、重连、故障和升级的协调。vhost-user 控制协议

设备可以通过中断通知完成,驱动或后端也可以轮询。轮询减少短请求的唤醒等待,却在空闲时继续消耗 CPU;批处理摊薄每次交接成本,等待凑批又可能增加低负载延迟。相同后端在高吞吐和低负载下,会有不同的 CPU 与延迟表现。

六、硬件分配、数据路径卸载与队列演进

如果希望减少软件处理,可以经 VFIO 把一个可隔离的设备功能交给客户机。客户机使用相应硬件驱动,设备通过 DMA 访问获准映射的内存;IOMMU 约束其访问范围。CPU 的 EPT/NPT 不能自动替代 DMA 隔离,设备重置和中断也仍需宿主协调。

SR-IOV 允许物理功能(PF)提供多个虚拟功能(VF),解决硬件资源如何细分;VFIO 可以负责把 VF 分配给客户机,两者经常组合使用。mdev 则由设备相关驱动提供中介设备实例,其共享与隔离能力取决于实现。vDPA 保留 virtio 兼容的数据接口,让适配后的硬件或软件处理队列,仍需控制侧配置。

接口选择与处理位置的分支
传统设备模拟、virtio 和分配的硬件功能可以长期并存。SR-IOV 主要划分设备功能,VFIO 管理访问与隔离,vDPA 连接 virtio 数据接口与后端;它们各自解决不同问题。
传统设备模拟、virtio 和分配的硬件功能可以长期并存。SR-IOV 主要划分设备功能,VFIO 管理访问与隔离,vDPA 连接 virtio 数据接口与后端;它们各自解决不同问题。 打开原图
Mermaid 源码
flowchart TB
    accTitle: 设备接口与数据后端的选择
    accDescr: 客户机设备可以使用模拟硬件接口、virtio 共享队列或分配的设备功能。virtio 队列有 QEMU、内核 vhost、用户态 vhost-user 和 vDPA 等不同后端,VFIO 直通则使用所分配设备的接口。
    G["客户机驱动"] --> E["模拟 e1000 接口"]
    G --> V["virtio 接口<br/>virtqueue"]
    G --> P["分配的 PCI 功能<br/>PF / VF 等"]
    E --> Q["QEMU 设备模型<br/>TAP 等后端"]
    V --> Q
    V --> K["vhost-net<br/>宿主内核"]
    V --> U["vhost-user<br/>DPDK / SPDK 等独立进程"]
    V --> D["vDPA 后端<br/>virtio 兼容数据面"]
    P --> H["设备 DMA<br/>VFIO / IOMMU 管理隔离"]

virtio 自身也在演进。Split 队列用三组结构分开描述请求与完成;packed 队列把可用、已用状态放入同一描述符环,并结合回绕标记识别所有权,以调整缓存访问成本。两种格式都必须遵守发布顺序和缓冲区生命周期,也都可连接不同后端。

七、按需求比较,沿数据路径定位成本

优先解决的问题常见选择还需确认的代价
沿用客户机已有驱动传统设备模拟兼容语义与设备模拟开销
通用队列接口与灵活管理virtio+QEMU 或 vhost队列交接、通知与后端 CPU 消耗
独立网络或存储数据处理vhost-user进程间共享、断连与恢复
减少软件数据处理VFIO 分配或 vDPA 卸载硬件能力、隔离粒度与状态迁移支持

先固定报文大小、队列数和 CPU 预算,再比较吞吐、尾延迟、丢包与 CPU 占用。随后核对启动、热插拔、迁移和故障恢复:硬件能发送报文,不代表它能导出完整设备状态。把请求、缓冲区和状态所有者逐项画出来,才容易发现省下了哪次交接,又新增了哪项协调。

八、沿队列与后端继续阅读

九、参考资料