目录
文章目录
- 目录
- VirtIO
- VirtIO 虚拟设备接口标准
- VirtIO 的前后端分层架构标准
- VirtIO 的数控路径分离架构标准
- VirtIO 的传输层标准
- VirtIO 标准在 Linux 中的实现
VirtIO
VirtIO 由 Rusty Russell 开发,最初是为了支持自己开发的 lguest Hypervisor,其设计目标是在虚拟化环境下提供与物理设备相近的 I/O 功能和性能,并且避免在虚拟机中安装额外的驱动程序。基于这一目标,后来通过开源的方式将 VirtIO 延伸为一种虚拟化设备接口标准,并广泛的支持 KVM、QEMU、Xen 和 VMware 等虚拟化解决方案。
为了追求更高的性能和更低的开销表现,VirtIO 采用了 Para-virtualizaiton(半虚拟化/准虚拟化)技术路线,本质区别于性能低下的 Full-virtualizaiton(e.g. QEMU I/O Emulation)。这也意味着使用 VirtIO 的 GuestOS 需要经过一定的代码修改(安装非原生的 Device Driver),这也是为什么需要将 VirtIO 定位于 “虚拟化设备接口标准“ 的原因,其整体的集成架构需要由 Front-end(GuestOS Kernel Device Driver)和 Back-end(VMM Provider)共同遵守统一的传输协议。
在应用 VirtIO 的场景中,除了 GuestOS 需要安装额外的 VirtIO Front-end Device Driver 这一点不足之外(通常在制作 QCOW2 镜像时会安装好),相对的带来了下列诸多好处:
- 高性能:VirtIO 省去了 Full-virtualizaiton 模式下的 Traps(操作捕获)环节,GuestOS 通过 VirtIO Interfaces 可以直接与 Hypervisor 中的 Device Emulation 进行交互。
- 低开销:VirtIO 优化了 CPU 在内核态和用户态之间频繁切换,以及 CPU 在 VM Exit 和 VM Entry 之间频繁陷入陷出所带来的性能开销。
- 标准化:VirtIO 实现了统一的虚拟设备接口标准,可以应用在多种虚拟化解决方案中。
VirtIO 虚拟设备接口标准
为了抑制 Para-virtualizaiton 所具有的跨平台兼容性缺点,VirtIO 明智的走上了开源之路,通过构建标准而统一的生态来争取最小化的兼容性排斥问题,反而让其逆转成为了一种优势。
在 VirtIO 提出的虚拟化设备接口标准中,包括多个子系统,每个子系统都定义了一组虚拟设备类型和协议。例如:
- VirtIO-Block(块设备):提供虚拟磁盘设备的接口。
- VirtIO-Net(网络设备):提供虚拟网络设备的接口。
- VirtIO-Serial(串口设备):提供虚拟串口设备的接口。
- VirtIO-Memory(内存设备):提供虚拟内存设备的接口。
- VirtIO-Input(输入设备):提供虚拟输入设备的接口。
此外还有 VirtIO-SCSI、VirtIO-NVMe、VirtIO-GPU、VirtIO-FS、VirtIO-VSock 等等虚拟设备类型和协议。
VirtIO 的前后端分层架构标准
VirtIO 虚拟设备接口标准的分层软件架构如下图所示:
- Front-end:是一组通用的,安装在 GuestOS Kernel 中的 VirtIO Device Driver,通过 Hyper-call 的方式对 Back-end 进行调用。
- Back-end:是一组 Hypervisor 专用的,运行在 VMM 中的设备模拟程序,提供了 Hyper-call 调用接口。
- Transport:是一组标准的传输层接口,基于环形队列的方式来批量处理 I/O 请求。
VirtIO 的数控路径分离架构标准
VirtIO 还定义了 “控制路径” 和 “数据路径” 的分离架构,两者的侧重各有不同,控制平面追求尽可能的灵活以兼容不用的设备和厂商,而数据平面则追求由更高的转发效率以快速的交换数据包。
- 控制路径:控制建立或删除 Front-end 和 Back-end 之间的数据路径,提供可管理的灵活性。
- 数据路径:在 Front-end 和 Back-end 之间进行数据传输,追求性能。
对应的,VirtIO 标准也可以分为两个部分:
- VirtIO Spec:定义了如何在 Front-end 和 Back-end 之间构建控制路径和数据路径的标准,例如:数据路径规定采用环形队列缓冲区布局。
- Vhost Protocol:定义了如何降数据路径的高性能实现标准,例如:基于 Kernel、DPDK、SmartNIC Offloading 的实现方式。
以 QEMU 为例,其根据 VirtIO Spec 实现了控制路径,而数据路径则可以 Bypass QEMU,使用 vhost-net、vhost-user 等实现。
VirtIO 的传输层标准
VirtIO 的传输层标准称之为 Virtqueues。以 QEMU VirtIO-Net 为例,对于一个虚拟网卡设备而言,采用了非常类似于物理网卡设备(NIC Rx/Tx Queues 和 Kernel Rx/Tx Rings)的设计思路。
Front-end 和 Back-end 之间存在 2 个 Virtqueues,对应到 NIC 的 Rx/Tx Queues。Virtqueues 的底层利用了 IPC 共享内存技术,在 HostOS 和 GuestOS 之间建立直接的传输通道,绕开了 VMM 的低效处理。
- Receive Queue:是一块 Buffer 内存空间,存放具体的由 Back-end 发送到 Front-end 数据。
- Send Queue:是一块 Buffer 内存空间,存放具体的由 Front-end 发送到 Back-end 数据。
同时,每个 Virtqueue 都具有 2 个 Vrings,对应到 Kernel 的 Rx/Tx Rings:
- Available Ring:存放 Front-end 向 Back-end 发送的 Queue 中可用的 Buffer 内存地址。
- Used Ring:存放 Back-end 向 Front-end 发送的 Queue 中已使用的 Buffer 内存地址。
当然,Front-end 和 Back-end 之间还需要一种双向通知机制,对应到 NIC 和 CPU 之间的硬中断信号:
- Available Buffer Notification:Front-end 使用此信号通知 Back-end 存在待处理的缓冲区。
- Used Buffer Notification:Back-end 使用此信号标志已完成处理某些缓冲区。
具体的,当 GuestOS 发送数据时,Front-end 将数据填充到 Send Queue 内存空间,然后将地址记录到 Available Ring,并在适当的时候向 Back-end 发送 Available Buffer Notification;QEMU 接收到通知后,从 Available Ring 中获取到数据的数据,完成数据接收,然后记录 New Buffer Head Index 到 Used Ring,并在适当的时候向 Front-end 发送一个 Used Buffer Notification。Front-end 接收到通知后释放对应的 Send Queue 内存空间,Available Ring 指针指向下一位。
当 GuestOS 接收数据时,Front-end 首先分配好 NULL 的 Receive Queue 内存空间,然后将地址记录到 Available Ring,并在适当的时候向 Back-end 发送 Available Buffer Notification;QEMU 接收到通知后,从 Available Ring 中获取到数据填充入口地址,完成数据填充,然后记录 New Buffer Head Index 到 Used Ring,并在适当的时候向 Front-end 发送一个 Used Buffer Notification。Front-end 接收到通知后从 Receive Queue 中读取数据,Available Ring 指针指向下一位。
值得注意的是,在 PCI Passthrough 场景中,可以使用 virtio-pci(更多的是使用 VFIO)。虽然此时依旧会沿用了 Available/Used buffer notification 这一控制面的双向通知机制,但实际的数据面,则是由 DMA 和专用队列来完成的,具有更高的性能。这体现了数控分离带来的好处。
VirtIO 标准在 Linux 中的实现
在连接了 VirtIO 虚拟设备接口标准中,前后端分层(横向)、数控路径分离(纵向)架构之后,再回头看 VirtIO 在 Linux 中的具体实现方式。如下图所示:
-
驱动层(Front-end):安装到 GuestOS Kernel 中的各种 I/O 设备的驱动程序。例如:virtio-net、virtio-blk。
-
传输层:定义了控制面和数据面的传输标准,例如:vhost-net(Kernel)、vhost-user(DPDK)、virtio-pci(PCI、PCIe)。
- 控制平面通信层(virtio):用于 Front-end 与 Back-end 之间进行控制信令的交换和协商(数据结构、Notify 等通信机制),如上述 Vings,控制数据平面的建立/关闭。
- 数据平面通信层(Transport):用于 Front-end 与 Back-end 之间进行数据交换,如上述 Virtqueues。
-
设备层(Back-end):运行在后端 Hypervisor 上的设备模拟程序,或是一个真实的硬件设备。