3. VirtIO 概述

VirtIO 是 Linux 与 KVM/QEMU 生态中最主流的半虚拟化 I/O 框架。它并不规定某一种具体硬件,而是定义了一套虚拟设备与 Guest 驱动之间的标准接口:Guest 侧安装的是"看起来像真实网卡"的 virtio-net 驱动,Host 侧则由 QEMU 或 vhost 后端实现设备语义。二者通过共享内存中的 virtqueue 交换描述符,从而避免全虚拟化设备频繁陷入模拟的开销。理解 VirtIO,是理解云主机网络性能、vhost-net、vhost-user、DPDK 加速等一系列技术的起点。

一、规范演进:从 0.9 到 1.x 的兼容性分水岭

VirtIO 规范最初由 Rusty Russell 提出,早期以"0.9 版"实现随 Linux 内核与 QEMU 一起演进,长期没有正式标准文本。直到 OASIS 接手后,才形成 VirtIO 1.0(2016 年)及后续 1.1、1.2 等正式规范。这一演进带来两个关键变化:一是设备发现与配置方式标准化,从早期的 PCI 专有寄存器布局,扩展为 PCI、MMIO、Channel I/O 等多种传输层;二是特性位(feature bits)协商机制规范化,Guest 与 Host 必须就可用特性达成一致后才能初始化队列。

工程中最常见的兼容性问题是"legacy 与 modern 混用"。例如老镜像默认走 legacy virtio-pci,而新内核可能优先尝试 modern 设备,若后端未正确暴露特性位,就会出现网卡识别但无法收发报文的现象。排查时应先确认设备类型与协商结果:

# 查看 Guest 内 virtio 网卡与驱动
lspci -nn | grep -i virtio
ethtool -i ens3

# 查看协商到的特性位(Host 侧 QEMU 监控台)
(qemu) info qtree
(qemu) info pci

二、设备与驱动模型:前端、后端与传输层

VirtIO 采用典型的前后端分离模型。Guest 内的前端驱动(virtio-net、virtio-blk 等)负责把上层协议栈的请求封装成描述符;Host 内的后端设备负责消费描述符并完成真实 I/O。二者之间由传输层(virtio-pci、virtio-mmio)提供寄存器与中断通道。三者关系可以用下面的结构图表示。

Guest 虚拟机(前端) 应用 / 协议栈 TCP/IP、socket、netfilter virtio-net 前端驱动 描述符封装 / 中断处理 / 特性协商 virtqueue(共享内存环) Descriptor Table Available Ring(Guest→Host) Used Ring(Host→Guest) 传输层 virtio-pci virtio-mmio 寄存器 / 中断 特性位协商 Host(后端) QEMU virtio-net 设备 或 vhost-net / vhost-user TAP / 物理网卡 内核协议栈 / DPDK 转发 后端消费 virtqueue 读取 Available Ring 写回 Used Ring 触发中断 / 事件通知 描述符 通知

图中可以看到,Guest 与 Host 并不直接传递数据包,而是通过 virtqueue 中的描述符间接引用 Guest 内存页。这种"零拷贝"设计是 VirtIO 高性能的根本原因,但也意味着内存布局、队列深度、通知抑制等参数会直接影响吞吐与延迟。

三、前后端协作机制:一次收发的完整路径

以 virtio-net 收包为例,一次完整协作大致分为以下步骤:

  1. Guest 驱动预先在接收队列中填充空缓冲区描述符,写入 Available Ring,并通知 Host。
  2. Host 后端收到通知后,从 TAP 或物理网卡读取报文,填入对应描述符指向的 Guest 内存。
  3. Host 将已使用的描述符索引写入 Used Ring,并按需触发中断或事件通知。
  4. Guest 驱动在中断或轮询中读取 Used Ring,把报文交给协议栈,随后重新填充缓冲区,形成闭环。

发包路径方向相反,但机制一致。关键参数包括队列数量(多队列)、队列深度、中断合并与通知抑制。生产环境中常通过多队列把不同 CPU 的流量分散到不同 virtqueue,减少锁竞争:

# 查看 virtio-net 队列与中断分布
ethtool -l ens3
ethtool -S ens3 | grep -i queue
cat /proc/interrupts | grep virtio

# 调整队列数(需网卡与后端支持)
ethtool -L ens3 combined 4
当 Guest 内出现"网卡 up 但 ping 不通"时,优先检查 virtqueue 是否正常协商、后端是否收到通知,而不是直接怀疑路由或防火墙。

四、典型应用场景与选型对照

VirtIO 并不只用于网卡。它在云平台中承担了绝大多数半虚拟化 I/O 角色,常见场景包括:

  • virtio-net:云主机主网卡,配合 vhost-net 或 vhost-user 提升转发性能。
  • virtio-blk / virtio-scsi:云盘与本地盘,virtio-scsi 支持更多队列与热插拔。
  • virtio-vsock:Host 与 Guest 之间的高效 socket 通道,常用于监控与日志采集。
  • virtio-fs:共享文件系统,替代 9p 以降低元数据开销。
  • vDPA / vhost-user:把 VirtIO 数据面卸载到硬件或用户态,兼顾兼容性与性能。
后端形态 数据面位置 典型性能 适用场景
QEMU 用户态 virtio-net QEMU 进程 较低 开发调试、低负载
vhost-net Host 内核 中高 通用云主机
vhost-user + DPDK 用户态轮询 高 NFV、高吞吐网关
vDPA 硬件 智能网卡 很高 裸金属云、SR-IOV 替代

五、常见误区与复盘要点

误区一:认为 VirtIO 就是"虚拟网卡驱动"。实际上它是一套通用 I/O 框架,网卡只是其中一种设备类型。误区二:只关注 Guest 侧参数,忽略 Host 后端形态。同一块 virtio-net,在 QEMU 用户态与 vhost-net 下的性能可能相差数倍。误区三:盲目开启多队列却不绑定中断亲和性,导致队列之间互相抢占 CPU。

复盘时建议按"协商—队列—后端—中断"四层排查:先确认特性位与设备类型是否匹配,再检查 virtqueue 数量与深度,然后确认后端形态与 CPU 绑定,最后观察中断分布与丢包计数。只有把前后端作为一个整体来看,VirtIO 的性能与稳定性问题才能真正定位。