参考:系《深入浅出dpdk》学习笔记以及redhat的官方博客
vhost属于virtio-net网络设备的后端驱动,经历了从virtio-net后端,到内核态vhost-net,到vhost-user的演进过程。先过一下背景知识,
背景知识
QEMU
QEMU 是一个托管虚拟机模拟器,它为客户机提供了一组不同的硬件和设备模型。对于主机,qemu 表现为由标准 Linux 调度程序调度的常规进程,具有自己的进程内存。在此过程中,QEMU为客户机分配内存(被客户机视为物理内存),并执行虚拟机的CPU指令。
要在存储或网络等裸机硬件上执行 I/O,CPU 必须与物理设备交互,执行特殊指令并访问特定内存区域(例如设备映射到的内存区域)。
当客户机(虚拟机)访问这些内存区域时,控制权将返回给 QEMU,QEMU 以对客户机透明的方式执行设备的模拟。
ps:什么叫做对客户机透明呢?比如:当客户机访问由虚拟设备提供的共享内存区域时,这些访问请求实际上被QEMU截获,并由 QEMU处理。然而,客户机认为它直接在与物理设备交互,并不需要知道底层的实际处理过程。
KVM
KVM(Kernel-based Virtual Machine)是Linux内置的一项开源虚拟化技术,为虚拟化软件提供硬件辅助,利用内置的CPU虚拟化技术减少虚拟化开销(缓存、I/O、内存),提高安全性。
借助 KVM,QEMU 只需创建一个具有处理器可识别的虚拟 CPU (vCPU) 的虚拟机,即可运行本机速度的指令。当 KVM 遇到特殊指令(例如与设备交互的指令或特殊内存区域)时,vCPU 会暂停并通知 QEMU 暂停的原因,从而允许虚拟机管理程序对该事件做出反应。
QEMU是如何与KVM通信的呢,QEMU打开 /dev/kvm 设备文件,这是与 KVM 内核模块通信的接口。并使用 ioctl(输入输出控制)系统调用与 KVM 设备通信,比如调用ioctl创建一个新的虚拟机实例,添加vCPU、添加内存(由qemu 分配,但从虚拟机的角度来看是物理内存),QEMU可以通过ioctl 调用向vCPU 发送中断,就像外部设备发送中断一样。
其中一个 ioctl 调用会启动实际的 KVM vCPU。这会阻塞 QEMU 进程,使 vCPU 运行,直到遇到需要硬件协助的指令为止。但是当 vCPU 遇到需要硬件协助的指令时,ioctl 调用返回(这被称为 vmexit,虚拟机退出),由QEMU接管,比如IO操作。
QEMU与virtio-net
virtio-net后端驱动程序的最基本要素是虚拟队列机制、消息通知机制和中断机制。
- 虚拟队列机制:连接着客户机和宿主机的数据交互
- 消息通知机制:主要用于从客户机到宿主机的消息通知
- 中断机制:主要用于从宿主机到客户机的中断请求和处理
图片来源:https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net
这个图片描述的太清晰了,先看一下什么是数据通路和控制通路:
- 数据通路:virtio 驱动程序必须能够分配内存区域,以便管理程序和设备都可以访问这些内存区域进行读写,即通过内存共享。我们将使用这些内存区域的数据通信部分称为数据平面
- 控制通路:设置这些内存区域的过程称为控制平面。
接下来再看下数据交互的过程:
virtio 网络设备是一个虚拟以太网卡,它支持TX/RX多队列。空缓冲区被放置在N 个virtqueue 中以接收数据包,而传出的数据包则被排队到另外N个virtqueue 中进行传输。另一个virtqueue 用于数据平面之外的驱动程序-设备通信,例如控制高级过滤功能、mac 地址等设置或活动队列的数量。作为物理 NIC,virtio 设备支持许多卸载等功能,并可以让真实主机的设备执行这些功能。
要发送数据包,驱动程序会向设备发送一个缓冲区,其中包含元数据信息(例如数据包所需的卸载),然后是要传输的数据包帧。驱动程序还可以将缓冲区拆分为多个收集条目,例如,它可以将元数据标头与数据包帧拆分开来。
这些缓冲区由驱动程序管理并由设备映射。在这种情况下,设备位于虚拟机管理程序“内部”。由于虚拟机管理程序 (qemu) 可以访问所有客户机的内存,因此它能够定位缓冲区并读取或写入它们。
以下流程图显示了 virtio-net 设备配置以及使用 virtio-net 驱动程序发送数据包的过程,该驱动程序通过 PCI 与 virtio-net 设备进行通信。在填满要发送的数据包后,它利用消息通知机制,触发“可用缓冲区通知”,将控制权返回给 QEMU,以便它可以通过 TAP(内核中的虚拟以太网设备)设备发送数据包。
然后,Qemu 通知客户机缓冲区操作(读取或写入)已完成,它通过将数据放入 virtqueue 并发送已使用通知事件来实现这一点,从而触发客户机 vCPU 中的中断。
接收数据包的过程与发送数据包的过程类似。唯一的区别是,在这种情况下,空缓冲区由客户机预先分配并提供给设备,以便设备可以将传入数据写入其中。
但是在这个模型中,由于宿主机、客户机和qemu之间的上下文频繁切换带来的多次数据拷贝和CPU切换,导致性能不尽如人意。可以看出,性能瓶颈处要体现在:
- 数据通路,从TAP设备到qemu的报文拷贝和qemu到客户机的报文拷贝,两次报文拷贝导致报文接受和发送上都有性能瓶颈
- 消息通知路径,报文到达tap设备时内核发出并送到qemu的通知信息,然后qemu利用ioctl向kvm请求中断,kvm发送中断到客户机,复杂的路径带来了不必要的性能开销。
vhost协议
Linux内核态vhost-net
为了对上述报文收发性能瓶颈进行优化,LInux内核设计了vhost-net模块,目的是通过卸载virtio-net在报文收发处理上的工作,使qemu从virtio-net的虚拟队列工作中解放处理,减少上下文切换数据包拷贝,进而提高报文收发的性能。除此之外,宿主机上的vhost-net模块还需要承担报文到达和发送消息通知及中断的工作。
如图所示,主要差异有两方面:
- 数据通路:从tap设备接受数据报文,通过vhost-net模块把该报文拷贝到虚拟队列中的数据区,从而使客户机接收报文。
- 消息通路:当报文从tap设备到达vhost-net时,通过KVM模块向客户机发送中断,通知客户机接收报文。如下图(发送通路):
图片来源:https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net
ps:eventfd是一种轻量级的 IPC(进程间通信方式) 执行方式。虽然 Unix 套接字允许发送和接收任何类型的数据,但 eventfd 只是一个整数,生产者可以更改,消费者可以轮询和读取。这使得它们更适合用作等待/通知机制,而不是信息传递。
vhost-net与OVS(联网)
客户机可以使用 Tap 设备与主机通信,但问题仍然是如何与同一主机上的其他虚拟机或主机外部的机器通信(例如:通过互联网)
我们可以使用内核网络堆栈提供的任何转发或路由机制(如标准 Linux 桥接器)来实现这一点。但是,更高级的解决方案是使用完全虚拟化、分布式、可管理的交换机,例如开放虚拟交换机(OVS)。
OVS datapath 在此场景中作为内核模块运行,ovs-vswitchd 作为用户空间控制和管理守护进程,ovsdb-server 作为转发数据库。
如下图所示,OVS datapath 在内核中运行,负责在物理网卡和虚拟TAP设备之间转发数据包:
用户态vhost
首先我们了解一下:DPDK 提供了一系列轮询模式驱动程序 (PMD),可实现用户空间和物理接口之间的数据包直接传输,从而完全绕过内核网络堆栈。通过消除中断处理和绕过内核堆栈,这种方法比内核转发显著提高了性能。
图片来源:https://www.redhat.com/en/blog/how-vhost-user-came-being-virtio-networking-and-dpdk
Linux内核态的vhost-net模块需要在内核态完成报文拷贝和消息处理,这会给报文处理带来一定的性能损失,于是就有了用户态的vhost,用户态vhost采用了共享内存技术,通过共享的虚拟队列来完成报文传输和控制,大大降低了vhost和virtio-net之间的数据传输成本。由于报文拷贝是在用户态进行,因此Linux内核的负担得到减轻。
而virtio/vhost又是如何和dpdk交互的呢?
vhot-user/virtio-pmd 架构
virtio 在主机用户空间和客户机用户空间都使用 DPDK,如图所示:
vhost-user(后端) - 作为 OVS-DPDK 用户空间应用程序的一部分在主机用户空间上运行。如前所述,DPDK 是一个库,而 vhost-user 模块是该库内的附加 API。OVS-DPDK 是与此库链接并调用 API 的实际应用程序。对于在主机上创建的每个客户机 VM,将实例化另一个 vhost-user 后端以与客户的 virtio 前端进行通信。
virtio-pmd(前端) - 在客户用户空间上运行,是一个轮询模式驱动程序,使用专用核心并执行无中断轮询。对于在用户空间上运行的应用程序,要使用 virtio-pmd,它还需要与 DPDK 库链接。
如果我们将这种架构与基于内核的 vhost-net/virtio-net 架构进行比较,vhost-net 被 vhost-user 取代,而 virtio-net 被 virtio-pmd 取代。
通过使主机用户空间能够通过共享内存绕过内核直接访问物理 NIC,并且通过在客户机用户空间上使用 virtio-pmd 也绕过内核,整体性能可以提高 2 到 4 倍。
然而,这在可用性方面是有代价的。在 vhost-net/virtio-net 架构中,从客户操作系统的角度来看,数据平面通信非常简单:只需将 virtio 驱动程序添加到客户内核,客户用户空间应用程序就会自动获得一个标准的 Linux 网络接口来工作。
相比之下,在 vhost-user/virtio-pmd 架构中,客户用户空间应用程序需要使用 virtio-pmd 驱动程序(来自 DPDK 库)来优化数据平面。实现比较复杂。