QEMU中,支持模拟硬件控制器,比如IDE/SATA/SCSI。但真实硬盘读写一次数据,要设置多个寄存器,导致触发多次 VM Exit,影响读写速率。
QEMU 的磁盘 I/O 虚拟化,从 IDE 到 virtio 再到 vhost,本质上是一条不断减少 VM Exit 开销和 QEMU 用户态参与程度的演进路径。
🐢 IDE:兼容性优先的"全模拟"方案
IDE(以及后来的 SATA)是 QEMU 对真实硬件控制器的完全模拟。客户机看到的是一个标准 IDE 控制器,用原生驱动就能识别,不需要任何额外软件。
这种兼容性是以性能为代价的。它的核心问题是队列深度只有 1 ,同一时间只能处理一个未完成的请求。从架构上看,客户机每完成一次 I/O,需要多次读写 IDE 的寄存器(命令、LBA、扇区计数、状态),每一次寄存器访问都可能触发一次 VM Exit,由 QEMU 逐条模拟。一次 I/O 累积几十次退出是常事。
⚡ 纯 virtio:减少退出次数,引入队列
virtio 是半虚拟化 (paravirtualization)方案。客户机"知道"自己在虚拟机里,通过共享内存中的 virtqueue 与 QEMU 通信。
纯 virtio(无 vhost)的 I/O 流程:
- 客户机驱动把请求打包放进 virtqueue(共享内存,不触发退出)。
- 驱动写 Queue Notify 寄存器 ,触发一次 VM Exit,陷入 QEMU。
- QEMU 从 virtqueue 取出请求,交给块后端。
- 完成后,QEMU 把结果写回 virtqueue,注入中断。
它比 IDE 快在哪:
- 退出次数大幅减少 :IDE 一次 I/O 可能几十次寄存器读写退出,virtio 压缩成一次 Queue Notify 退出。
- 数据搬运更直接:virtqueue 使用描述符(descriptor)指向客户机内存,QEMU 可以直接按 GPA 访问,不需要像 PIO 那样逐段模拟。
- 队列深度和并发 :virtio 支持多队列和深队列,客户机可以一次性塞多个请求,QEMU 批量处理。Ceph 的记录显示 virtio 单卷吞吐可以 超过 1.1 GB/s 。现代 virtio-blk 配合多队列优化,在快速存储(如 NVMe)上还能再提升约 20% 的 IOPS。
🚀 vhost:把 virtqueue 处理移出 QEMU
纯 virtio 虽然减少了退出次数,但每次 Queue Notify 退出后,处理 virtqueue 的仍然是 QEMU 用户态主循环。QEMU 要解析描述符、调用块层、处理完成,这个用户态路径本身有开销,在主循环繁忙时还可能成为瓶颈。
vhost 的思路是:QEMU 只负责初始化,把 virtqueue 的处理权交给另一个实体。
- vhost-blk / vhost-scsi(内核态) :QEMU 通过 eventfd 把 virtqueue 通知转发给宿主机内核模块 ,由内核直接处理 I/O。例如,一个 vhost-blk 的早期原型显示,相比用户态 virtio-blk,顺序读吞吐提升约 16% ,延迟下降约 14%。
- vhost-user(用户态) :把 virtqueue 处理交给一个独立的用户态进程(如 DPDK、SPDK)。QEMU 只建立共享内存映射和 eventfd/irqfd 通道,之后客户机的通知直接唤醒后端进程。这适合需要轮询、零拷贝的高性能场景。
vhost 的代价是功能受限。例如 vhost-blk 早期不支持非 raw 格式、块设备格式等,如果设置不满足加速条件,会自动回退到用户态 virtio。
📊 对比一览
| 方案 | 核心机制 | 队列深度 | QEMU 参与数据路径 | 典型性能(参考) |
|---|---|---|---|---|
| IDE | 全模拟真实控制器 | 1 | 每次 I/O 都参与 | ~70 MB/s, ~380 IOPS |
| 纯 virtio | 半虚拟化,virtqueue | 多队列/深 | 每次 I/O 处理 virtqueue | >1.1 GB/s |
| vhost | 内核/用户态后端处理 virtqueue | 多队列/深 | 仅初始化 | 比纯 virtio 再提升 ~16% |
所以,如果你追求兼容性 (老系统、无 virtio 驱动),IDE 是保底选择。如果客户机支持 virtio,纯 virtio 是性能的起点 ,而 vhost 是在此基础上进一步榨取性能的手段,代价是配置复杂度和功能限制。
现代场景下的真实差距
在现代 QEMU 中,virtio 和传统模拟控制器之间的差距,远不止 20%。原因在于架构:
| 维度 | IDE/SATA/SCSI(全模拟) | virtio(半虚拟化) |
|---|---|---|
| 每次 I/O 的 VM Exit | 多次寄存器访问,多次退出 | 一次 Queue Notify |
| 数据搬运 | PIO/MMIO 逐段模拟 | virtqueue 描述符 + DMA |
| 队列深度 | IDE 为 1;SATA/SCSI 有改善但有限 | 多队列、深队列 |
| 中断注入 | 完整设备中断模拟 | irqfd + posted interrupt |
| 后端处理 | QEMU 主循环 | QEMU 主循环(无 vhost 时) |
在 IOPS 密集、并发、小块随机读写 等真实负载下,这些架构差异会放大成数倍甚至十倍以上的差距,而不是 20%。
更准确的表述
-
IDE、SATA、SCSI 之间的差异:在同一代 QEMU、同一测试条件下,SATA/SCSI 因为队列深度和协议效率优于 IDE,确实会快一些。这个差异是真实的,但幅度取决于负载。
-
它们与 virtio 之间的差异 :这是架构层面的代差。在简单大块顺序读场景下,差距可能看起来不大;但在真实虚拟化负载下,virtio 的优势是压倒性的。
把"处理 virtqueue"这一步从 QEMU 用户态搬到内核来提高效率能理解,为什么搬到独立用户态进程也能提高效率呢,到底是内核态,还是独立用户态效率更高
你的疑问很关键。答案并不是简单的"内核态比用户态更高效",而是这两种架构提升效率的"侧重点"完全不同。vhost-net(内核态)追求的是减少切换的"数量",而 vhost-user(用户态)追求的是消灭切换的"代价"。
vhost-net:为什么"搬到内核"能提速?
它的核心价值是消除 QEMU 用户态与内核态之间的上下文切换。
在纯 virtio 架构中,数据路径的瓶颈在于 QEMU 作为一个用户态进程,每次处理 I/O 都需要在用户态和内核态之间来回穿梭(比如调用 read()/write() 系统调用)。vhost-net 把 virtqueue 的处理逻辑直接放进了宿主机内核,让数据处理在同一个内核上下文中完成,省去了进出用户态的切换开销。
你可以把它理解为:把处理 I/O 的"办公桌"从公司外面(用户态)搬进了公司大厅(内核态),沟通成本自然降低了。但它依然要遵循内核的规则,比如数据包还是要经过内核网络协议栈。
vhost-user:为什么"搬到独立用户态进程"也能提速,甚至更快?
vhost-user 的提速逻辑完全不同。它不再纠结于"用户态 vs 内核态"的切换,而是选择彻底绕开内核网络/存储协议栈,以此实现极致的性能。
它的秘密在于 DPDK/SPDK 的轮询模式(Polling):
- 消灭中断开销 :传统模式依赖中断来通知 I/O 完成,每次中断都是一次昂贵的上下文切换。vhost-user 的后端进程(如 DPDK 应用)会持续轮询 virtqueue,一旦有数据就立刻处理,完全不产生中断。
- 零拷贝与用户态直达:在 DPDK 的配合下,数据从虚拟机内存可以直接传输到物理网卡(或从物理存储读到虚拟机内存),全程在用户态完成,不经过内核协议栈,也不需要内核态的拷贝。
所以,vhost-user 的"高效"是牺牲了 CPU 核心换来的(轮询会持续占用 CPU),它把数据处理的整个流程都放在了用户态,从而获得了接近物理硬件的性能。
内核态 vs 独立用户态:到底谁更高?
这没有绝对答案,取决于你的性能目标和场景。
- vhost-net(内核态) :胜在通用和均衡。它大幅降低了传统 virtio 的开销,性能足以应对绝大多数通用场景(如 10G/25G 网络),同时保留了内核的调度、公平性和标准网络栈功能,配置和管理也更简单。
- vhost-user(用户态) :胜在极致性能 。它适合对吞吐和延迟有极致要求的场景(如 NFV、100G 网络、高性能存储 SPDK)。但代价是CPU 资源消耗大(因为轮询),且在 I/O 负载不高时会造成 CPU 空转浪费;在多个虚拟机高密度部署时,还会因为抢占 CPU 核心而导致性能骤降和延迟抖动。
总结
这不是"谁比谁绝对更快"的问题。vhost-net 是在传统架构内做减法 (减去 QEMU 切换),而 vhost-user 是跳出传统架构做乘法(乘以 DPDK 的零拷贝和轮询)。对于通用虚拟化,内核态 vhost 是性价比最高的选择;对于追求物理极限性能的专用场景,独立用户态的 vhost-user 才是那把更快的钥匙。