主要参考文章:Linux网络虚拟化技术的演进之路 - 知乎

Trap-and-emulate -> VirtIO (2008) -> Vhost (2010) -> VFIO (2012) -> Vhost-user (2014) -> VFIO-mdev (2016) -> vDPA (2020) -> VDUSE (2021).

VFIO 能够直接将硬件资源透传给虚机使用,性能最佳,VirtIO 性能稍逊,但胜在更加灵活。那有没有可能将两者的优势结合起来呢?vDPA 技术框架,就是为了实现这一目标。

演进的顺序貌似是:全虚拟化->virtio->vhost->vfio->vdpa->vduse

为什么 VFIO/SRIOV 不支持热迁移而 VirtIO 可以?

什么是 VirtIO 里的数据链路(数据面 datapath)和控制链路(控制面 control path)?

控制面用于在 host/guest 之间协商设备能力。

  • 控制链路:控制链路用于设备管理和配置,例如初始化设备、设置参数、查询状态或发送控制命令。控制建立或删除 Front-end 和 Back-end 之间的数据路径,提供可管理的灵活性。Ring buffer 和 Descriptor table 的内存地址,驱动如何告知设备比如 irqfd & ioeventfd,那些 ,驱动感知设备支持的特性等等。控制面基于 VirtIO Spec 在 QEMU 中实现,但数据平面并没有。
  • 数据链路:在前端(驱动)和后端(设备)之间传输数据,性能优先。virtqueue 里面的各个 entry 所指向的内存地址所对应的内存区域的填写/读取。比如说当报文到达内核 TAP 设备时候,内核发出并送到 QEMU 的通知消息,然后 QEMU 需要把数据拷贝到 guest 内存中并通知 guest。

对应地,VirtIO 标准也可以分为两个部分:

  • VirtIO Spec:定义了如何在 Front-end 和 Back-end 之间构建控制路径和数据路径的标准,例如:数据路径规定采用环形队列缓冲区布局。
  • VHost Protocol:定义了如何降数据路径的高性能实现标准,例如:基于 Kernel、DPDK、SmartNIC Offloading 的实现方式。

什么是 VirtIO 数据链路(datapath)协议规范

VirtIO datapath 数据链路规范。

从全虚拟化到 VirtIO

请看 VirtIO 相比于 full virtualization I/O 的优势是什么?^

从 VirtIO 到 vHost

这里的 VirtIO 指的其实是通过 irqfdioeventfd 都加强了之后的 VirtIO。

ioeventfd 的重点在于解决 vCPU 线程被 block 的 vCPU 性能问题,而 vHost 解决的是设备的性能问题(存储,网络收包等等)。

VirtIO 的控制面基于 Spec 在 QEMU 中实现,但数据平面并没有。为什么呢?答案是性能。如果我们简单地在 QEMU 中实现 VirtIO 数据面,我们将为每个从 host kernel 到 guest 的数据包进行上下文切换,反之亦然。这是一项代价高昂的操作,会增加延迟并需要更多处理时间(请记住,QEMU 是另一个 Linux 进程),因此我们希望尽可能避免它。

这就是 vHost protocol 发挥作用的地方,使我们能够实现一个 VirtIO 数据面,绕过 QEMU 直接从 host kernel 进入 guest。 vhost protocol 本身只描述了如何建立数据面。它的实现者还需要实现 buffers/rings 内存布局来识别 guest/host 的数据缓冲区,并进行实际的数据包收发。 vhost protocol 既可以在内核态实现 (vhost-net) 也可以在用户态实现 (vhost-user)。

尽管 VM 主动通知设备的这个通知机制通过 ioeventfd^ 不需要 exit 到 vCPU 线程的 userspace 而是让 iothread 通过 ioeventfd 来进行处理了;但是从另一个数据流方向来看,当报文到达内核 TAP 设备时候,host 内核发出并送到 QEMU 的通知消息(return to userspace),然后 QEMU 利用 irqfd 向 KVM 发送中断(enter to sys again),KVM 发送中断到 VM,这样先从内核态到用户态、再从用户态回到内核态的路径带来了不必要的开销。这会导致 IO 性能不佳。

VirtIO 通信机制从原本的 QEMU 用户态 I/O 线程和 guest driver 通信直接变成了 vHost 内核 I/O 线程和 guest driver 通信。vHost 内核 I/O 线程拿到数据包之后,直接走内核协议栈和网卡驱动进行处理,从而优化掉了 QEMU 到内核态的额外开销。相当于数据通路没有 QEMU 什么事,直接 bypass 掉了。

仍然有两个通路:

  • 数据通路是从 tap 设备接收数据报文,内核的 vhost-net 模块把该数据报文拷贝到 virtqueue,从而使得客户机接收报文。
  • 消息通路是当报文从 tap 设备到达 vhost-net 时候,QEMU irqfd 进行通知。

原始的 VirtIO 控制面数据面都是在 QEMU 里实现的。irqfd 仅仅是把处理从 vCPU 线程 offload 到 IO 线程了,并没有避免 user-sys context switch。vHost 把数据面 offload 到了 kernel 中,这样数据的转发不需要 user-sys context switch。

QEMU 不参与通信,但也没有完全退出舞台,它还要负责一些控制层面的事情,比如和 KVM 之间的控制指令的下发等。

Deep dive into Virtio-networking and vhost-net

VirtIO 和 vHost 在实现上的区别:

  • 基于 shared memory 的 vring/virtqueue 以前是 QEMU 这里的 device 端,申请在自己的用户态内存空间中的,现在直接由 vHost 内核线程申请,放在 kernel space 了;
  • irqfd ioeventfd 以前是 QEMU 注册,现在应该是 vhost 内核线程来打开并注册了。
VirtIO vHost

vhost-netvhost-user

Move the dataplane from the kernel to userspace.

vhost-user 相比于 vhost-net 并不是改进关系,而是各自有各自的使用场景。

vHost 适用数据源是 TAP 设备,也就是直接从内核过来的数据包;vhost-user 适合的数据源是 OVS,工作于 userspace,所以我们应该从 userspace 来接数据,那就没必要把数据面再放到 kernel space 了。

由于:

  • QEMU 和 vHost 的线程模型对 I/O 性能的优化并不友好,而且
  • 由每个虚机单独分出线程来处理 I/O 这种方式从系统全局角度来看可能也并不是最优的(一个 QEMU 虚拟机都有自己的 io kernel thread,有必要一个虚拟机一个吗?)

因此,vhost-user 提出了一种新的方式,即将 virtio 设备的数据面 offload 到另一个专用进程来处理。这样,

  • 由于是专用进程,线程模型不再受传统 QEMU 和 vhost 线程模型制约,可以任意优化,
  • 可以以 1: M 的方式同时处理多个虚机的 I/O 请求,不需要每一个虚拟机都有自己的 IO 线程,
  • 而且相较于 vhost 这种内核线程方式,用户进程在运维方面更加具备灵活性。

DPDK 就是利用了 vhost-user。

纯软件的优化,基本上到了 vhost 和 vhost-user 已经是极致了,后面的一些技术比如 vDPA 都要用到硬件卸载设备直通了。

vhost(vhost-user)网络I/O半虚拟化详解:一种 virtio 高性能的后端驱动实现-CSDN博客

Linux网络虚拟化技术的演进之路 - 知乎

Deep dive into Virtio-networking and vhost-net

VFIO

就是支持设备直通的一个 Linux 框架。

VFIO 与用户态驱动^

UIO 不支持 DMA,所以通过 DMA 传输大流量数据的 IO 设备,如网卡、显卡等设备,无法使用 UIO 框架,VFIO 作为 UIO 的升级版,主要就是解决了这个问题。通过用户态配置 IOMMU 接口,可以将 DMA 地址空间映射限制在进程虚拟空间中。(所以 IOMMU 不只是可以用在虚拟化中,因为是进程内存)。

从设备角度来看,VM 也是一个执行了设备驱动的进程。因此直通到 VM 里的进程还是到用户态驱动进程没有什么区别,都是直通。

VFIO-mdev

mdev 本质上是在 VFIO 层面实现 VF 功能。算是对没有 SRIOV 情况的一个补充。

有些场景下,我们希望能把后端的硬件设备切分成更小的实例提供给更多的 VM 来使用,但是这个设备又没有 SRIOV 或者 SIOV 的能力 (比如 GPU,NVME) 那该怎么办呢?别着急,VFIO-mdev 框架就是为了解决这样的问题而生的。

如果一个设备需要给多个进程提供用户态驱动的访问能力,这个设备在 probe 的时候可以注册到 mdev 框架中,成为一个 mdev 框架的 pdev。之后,用户程序可以通过 sysfs 创建这个 pdev 的 mdev。

vDPA (VirtIO Data Path Acceleration)

RedHat 提出的(2019 ~ 2020?)。vDPA 的介绍网站:vDPA - virtio Data Path Acceleration - vDPA - virtio Data Path Acceleration

是把 vHost 内核线程做的事情 offload 到硬件上,进一步提升性能了吧?

VFIO 能够直接将硬件资源透传给虚机使用,性能最佳,VirtIO 性能稍逊,但胜在更加灵活。那有没有可能将两者的优势结合起来呢?vDPA 技术框架,就是为了实现这一目标。

注意,vDPA 并没有设备直通,只是在性能上接近设备直通的性能。也就是设备还是在 host 上管理。

它表示一类设备:这类设备的数据面处理是严格遵循 VirtIO 协议规范的,即驱动和设备会按照第三节提到的 VirtIO 通信流程来进行通信,但控制路径,比如:通信流程里面提到的 ring buffer 和 descriptor table 的内存地址,驱动如何告知设备,设备支持的特性,驱动如何感知,这些都是厂商自定义的。这样做的好处是,可以降低厂商在实现这类设备时的复杂度。

vDPA 相比于直通普通设备的好处在于灵活性,那么灵活在哪里呢?

Guest 对于一类设备只需要一种驱动(net, blk),不需要每一个厂商都维护自己的驱动了。好处就是不用写驱动了?

vDPA 设备能在 host 上运行吗?

毕竟 host 上并没有 VirtIO 驱动,只有普通驱动吧。除非 host 也可以使用 kernel 里已经 merge 的 VirtIO 驱动来直接和设备通信(但是控制面谁来管理?已经没有 QEMU 了)。

MLX 的 ConnectX 支持 vDPA。

从 vDPA 到 vDUSE

vDUSE 是字节在 2020 年提出的。vDPA device in userspace.

vHost

The vhost drivers in Linux provide in-kernel virtio device emulation. It allows offloading the data plane to kernel (control plane is still in QEMU). 没有 vhost 的时候,data plane 也是在 QEMU 的,这就导致有很多不必要的从 kernel 到 QEMU 的 context switch,从而影响了性能。

We can split virtio into two parts:

  • virtio spec - defines how to create a control plane and the data plane between the guest and host. For example the data plane is composed of buffers and rings layouts which are detailed in the spec.
  • vhost protocol - A protocol that allows the virtio dataplane implementation to be offloaded to another element (user process or kernel module) in order to enhance performance.

Introduction to virtio-networking and vhost-net

QEMU 代码里 qemu/hw/virtio/vhost* 仅仅是 vhost 的 control plane。

可以看这篇文章再深入了解一下:Virtio and Vhost Architecture - Part 2 · Better Tomorrow with Computer Science

It is usually called virtio when used as a front-end driver in a guest operating system or vhost when used as a back-end driver in a host.

So IMO they are basically the same thing in different context.

因为 frontend driver 和 backend driver 都实现在了 kernel 中,为了进行区分,我们把 host 部分的代码叫做 vhost。

有一个目录叫做:drivers/vhost/,里面包含了所有 vhost 相关的代码。而 guest 相关的代码大部分在 drivers/virtio/ 下面。

vhost kernel module is implemented in linux/drivers/vhost, and control plane is implemented in hw/virtio/vhost*.

Stefan Hajnoczi: QEMU Internals: vhost architecture

Virtio and Vhost Architecture - Part 1 · Better Tomorrow with Computer Science