virtio 学习随笔 —— 二、virtio与vhost
二、virtio与vhost
参考文档:
Introduction to virtio-networking and vhost-net
很多人在学习virtio初期总是搞混virtio与vhost的关系,本篇文档将以virtio-net为例,介绍virtio与vhost的协作关系。virtio是Guest中前端设备驱动与Hypervisor后端设备驱动的标准化接口规范,而vhost可以通过在Hypervisor中接管virtio的数据平面提升Guest与Hypervisor之间的I/O性能。
virtio的两个平面:数据平面与控制平面
virtio类似NVMe协议,采用双平面结构。控制平面负责Hypervisor与Guest之间进行协商交互,包括数据平面的的建立与关闭等操作;数据平面则专门处理实际的数据传输业务。virtio对于两个平面有不同的要求,数据平面追求更高速的数据传输,而控制平面则强调灵活性与可扩展性以支持更多的设备与厂商。
使用virtio的系统概览
通常一个虚拟化系统由三个模块构成:
- KVM(Kernel-based Virtual Machine):KVM将Linux Kernel转变为Hypervisor,使其能够运行若干互相隔离的虚拟机,这些虚拟机被称为Guest Machine,并作为Linux的进程统一由Linux Kernel进行调度管理
- QEMU:通过硬件模拟为Guest提供多样的设备模型。当与KVM协同工作时,QEMU能利用硬件扩展特性使虚拟机达到接近原生的运行速度
- Libvirt:可以将XML格式的配置文件转换成QEMU命令行调用,同时通过管理守护进程配置子进程(如QEMU),从而避免QEMU直接需要root权限
它们之间的关系见下图:
vhost协议
以下流程图展示了在上图中的系统架构中virtio-net设备的配置过程,以及通过PCI总线与virtio-net设备通信的virtio-net驱动程序发送数据包的流程。
在虚拟机准备好数据之后, virtio-net驱动会发出“available buffer”可用的信号,并且将控制权交给QEMU,将数据通过TAP设备发出。在QEMU完成数据发送/接收之后,将会通知虚拟机操作已完成。其实现方式是将数据放入virtqueue,并发送一个已使用通知事件,触发虚拟机的vCPU中断。接收数据的流程和发送数据的流程类似,唯一不同的是接收数据所使用的缓冲区需要事前分配,这样在数据来临时设备可以直接将数据写入缓冲区。
不过,这种 依赖 QEMU 进行数据面转发 的方式存在明显性能瓶颈:
- 在virtio驱动程序发送可用缓冲区通知后,vCPU会停止运行并将控制权交还给虚拟机监控程序,从而引发高昂的上下文切换开销。
- QEMU额外的任务/线程同步机制。每个数据包需要通过系统调用和数据拷贝经由tap接口实际发送或接收(无法批量处理)。
- 通过ioctl发送可用缓冲区通知(触发vCPU中断)。
- 还需要额外发起系统调用来恢复vCPU执行,伴随所有相关的内存映射切换等操作。
为了解决上述缺陷,vhost 协议应运而生。通过引入 vhost,将 virtio 的数据面处理从 QEMU 中剥离出来,由 Hypervisor 线程直接执行,从而有效减少上下文切换与数据拷贝,显著提升转发性能。
在 virtio 架构中,控制平面 由 QEMU 根据 virtio 规范实现,而 数据平面 则通常被卸载到 vhost。为什么要这样设计呢?如果数据平面也完全由 QEMU 实现,那么数据在传输过程中需要频繁地在用户态(QEMU 进程)与内核态之间切换,带来高昂的上下文切换开销,进而增加整个数据通路的延迟。
因此,引入 vhost 的核心目的就是实现 Guest 与 Hypervisor 内核之间绕过 QEMU 的直接数据交换。根据实现方式的不同,vhost 协议可以运行在内核态(如 vhost-net),也可以运行在用户态(如 vhost-user)。
在采用 vhost 的 virtio 虚拟化系统中,整体架构可以分为两个部分:
- virtio 规范:规定了 Guest 与 Hypervisor 之间如何建立控制平面(Control Plane)和数据平面(Data Plane);
- vhost 协议:为提升性能,virtio 允许将数据平面卸载到 QEMU 之外的组件,例如 Hypervisor 的用户进程或内核,从而避免频繁的用户态与内核态切换。
在实际的虚拟化架构中,virtio 与 vhost 通常协同工作以获得最佳性能:virtio 的控制平面严格遵循规范,由 QEMU 实现设备管理与配置功能;而数据平面则通过 vhost 协议在 Guest 与 Hypervisor 内核之间建立直接通道,从而绕过 QEMU 的软件模拟层。这种 控制面与数据面分离 的设计,不仅保证了 virtio 的标准兼容性,还能通过内核态(vhost-net)或用户态(vhost-user)的数据路径,实现接近物理设备的 I/O 性能。
从实现角度来看,virtio 虚拟化框架可以抽象为 前端(Frontend) 与 后端(Backend):前端运行在虚拟机内部,后端运行在宿主机上。例如,在 vhost-net/virtio-net 架构中,前端是 virtio-net 驱动,后端则由 vhost-net 模块提供支持。