0. 目录
1. 垫话
本文乃对 Red Hat 《How deep does the vDPA rabbit hole go? 》一文的翻译(系列文章第 9 篇)。
要点概述:
-
所谓的数据面 offloading,本质上就是指硬件(NIC)可以直接访问到 guest 的 virtqueue。而 guest 的 virtqueue 中是 GIOVA,设备 DMA 使用的是 GIOVA,但实际访问的内存是 HPA,所以问题规约为如何实现 GIOVA 到 HPA 的映射。 -
数据面是通过控制面来建立的,也就是说 GIOVA 到 HPA 的映射是由控制面建立起来的。 -
GIOVA 到 HPA 的映射,等同于:GIOVA 到 GPA 的映射 + GPA 到 HPA 的映射。GIOVA 到 GPA 的映射,由 guest 应用通过 guest 中的 VFIO 完成(应用通过 guest 中的 VFIO 接口完成 GIOVA 到 GVA 的映射)。guest 通过 guest 中的 VFIO(也就是通过 qemu 中模拟的 vfio 设备)做 GIOVA 到 GPA 的映射时,会 trap 到 qemu 中的模拟设备,由于 qemu 知道 GPA 到 HVA 的映射,故而 vfio 模拟设备可以通过 host 上的 VFIO 接口直接完成 GIOVA 到 HVA 的映射,进而也就完成了 GIOVA 到 HPA 的映射。至此也就完成了控制面中最重要的地址映射环节。 -
如果数据面、控制面皆由 guest 直接通过 virtio 前端与物理 NIC 完成,此方案就是 virtio 全硬件 offloading。 -
virtio 全硬件 offloading 要求有一个通用的控制面逻辑,然而诸如 scalable IOV 之类的技术是没有通用的控制面逻辑的。所以有些方案只能实现数据面的 offloading,也就是 vDPA。 -
“virtio 全硬件 offloading 意味着 virtio 数据面与控制面皆由硬件设备来实现。这也意味着,该设备可以直接被内核的 virtio-net 驱动或 DPDK virtio PMD probe。该方案的一种替代方案是使用 vDPA 设备,这意味着数据面仍然完全被 offload,但控制面由 vDPA API 进行管理。” -
应用使用标准驱动(“应用”指的是驱动的使用者,驱动分为用户态 VFIO 驱动和内核态 virtio 驱动。用户态 VFIO 驱动,对应 mdev 设备为 vfio-mdev 设备;内核态 virtio 驱动,对应 mdev 设备为 virtio-mdev 设备),所以应用通过标准驱动发出的是通用(而非 vendor specific 的)控制命令,但设备本身的驱动是 vendor specific 的(host 上),这就要求在 host 上通过软件,将来自 guest 的标准 virtio 控制命令翻译成 vendor specific 命令,并通过 vendor 驱动下发给物理设备。这个命令翻译软件(中介),也就是 mdev 框架。 -
“对于用户态,vhost-mdev 将其自身注册为一个新型 mdev 驱动,并在用户态 vhost 驱动和 virtio-mdev 设备之间充当桥梁的角色。其通过 vhost-mdev 接收兼容 vhost-net 的 ioctls,并将它们翻译为 virtio-mdev transport API。因为它是位于 VFIO group 中的,所有 DMA 相关设置都可以通过 container fd 的 VFIO API 来完成。” -
“vhost-vfio 是与 qemu 中的 virtio 设备模型配对的用户态 vhost 驱动,为 VM 提供一个虚拟 virtio 设备(比如说 PCI 设备)。通过 vIOMMU 接收 DMA 映射请求,并通过 VFIO container FD 进行转发,而 virtio 设备相关的设置和配置,通过 vhost-mdev fd 完成。” -
vfio-mdev 设备是用户态 VFIO ioctl 接口与 VF、PF 驱动的中介,virtio-mdev 设备是内核态 virtio 驱动与 VF、PF 驱动的中介,vhost-mdev 是用户态 vhost ioctl 与 virtio-mdev 设备之间的中介。 -
对于 mdev-bus 来说,mdev 设备是一个此总线上的设备;对于 PCIe-bus 来说,mdev 设备是一个驱动,其将翻译过来的命令发给物理硬件。
2. 前言
本文解构 virtio 全硬件 offloading 及 vDPA 解决方案所涉及的组件。虽然这些架构仍在演进中,有些 bits 在未来可能会发生改变,但管理组件应该不会有大的变化。
我们将展开对 VFIO、vfio-pci 以及 vhost-vfio 的讨论,这些组件都是为实现 guest 和 host 的用户态驱动而服务的。我们还会讨论构成 vDPA 方案的 MDEV、vfio-mdev、vhost-mdev 以及 virtio-mdev transport APIs。
本文适用于对 vDPA 架构实现细节感兴趣的同学,如果不了解 vDPA 背景,可以先阅读《vDPA 简介》。
另外强烈推荐先阅读《vhost-user 深入》,该文章中详细解释了 VFIO 和 IOMMU。
读罢本文,你可以建立起对 virtio 全硬件 offloading 及 vDPA 相关技术组件的清晰认识。这些技术组件在后续的文章中也会提到,用来支持 kubernetes 容器加速。
另外,本文阐述的 virtio-mdev transport APIs 作为 AF_VIRTIO 框架的基础,旨在通过 sockets(替代 DPDK) 为 kubernetes 容器提供加速。这将在本系列的最后一篇文章进行探讨。
3. SR-IOV PFs 与 VFs
Single Root I/O Virtualization(SR-IOV) 是 PCI Express(PCIe) 标准的一个扩展。SR-IOV 支持对设备(如网卡)的 PCIe 硬件 functions 资源进行独立访问。
PCIe 硬件 functions 有如下两种类型:
-
PCIe Physical Function(PF):该 function 为设备的基本 function,用来呈现设备的 SR-IOV capabilities。 -
Virtual Functions(VFs):一到多个 VFs,每个 VF 与设备的 PF 相关联。一个 VF 与 PF 和设备上的其他 VFs 共享一到多个设备的物理资源,比如内存、网络 port。
每个 PF 和 VF 被赋予一个独立的 PCIe requester ID(RID),通过 RID,I/O 内存管理单元(IOMMU)可以识别流量请求的发起者,从而在 PF 和 VFs 之间做内存与中断的 translation(译者注:参考本号《Intel VTD》系列文章),进而支持将流量直接投送给指定的 DMA 域。
效果就是,非特权数据流在不影响其他 VFs 的情况下从 PF 流向 VF。
4. VFIO 与 IOMMU
VFIO 是一个用户态驱动开发框架,支持用户态应用(比如一个非特权用户态驱动)在一个受 IOMMU 保护的环境下,直接与设备打交道(bypass 内核)。VFIO 的细节在《vhost-user 深入》一文中已展开讨论过,或者可以阅读相关 kernel doc(译者注:本号《VFIO – “Virtual Function I/O”》)。
IOMMU 差不多相当于 I/O 空间的 MMU,在设备做 DMA 访问时介入。其位于主内存与设备之间,为每个设备创建一片虚拟 I/O 空间,并自动将虚拟 I/O 地址映射到物理地址。故而,当驱动配置设备的 DMA 时,配置下发的是虚拟地址(译者注:IOVA),当设备尝试访问这些虚拟地址时,会经过 IOMMU 被重映射到物理地址。
IOMMU 的展开讨论见《vhost-user 深入》一文。
5. virtio 全硬件 offloading 的硬件组件
virtio 全硬件 offloading 设备是 vendor’s NIC 的一部分,可以说它是一种特殊类型的 VF。
通常 PF 基于 vendor ring 布局(译者注:virtio 全硬件 offloading 下,vendor ring 布局可以完全是 private 的,不一定非得是标准 virtio ring,因为数据面协议完全可以是 vendor 私有的)来构建 VF。然而,当 PF 创建一个 virtio 全硬件 offloading 设备时,ring 会采用 virtio ring 布局。内核驱动通过 PCIe 总线连接 PF 及 VFs。
图 1 展示了 PF 及 VF 硬件组件:
6. virtio 全硬件 offloading 的软件组件
vfio-pci 是一个 host/guest 的内核态驱动,向用户态提供访问 PCI/PCIe 硬件设备的 APIs。在本文例子中,这也是我们访问 virtio 全硬件 offloading 设备的方式。vfio-pci 在 VFIO 组件之下(译者注:这里说的“之下”,指的是图 2 架构中的位置,用户态操作 VFIO ioctl,VFIO 的部分 ioctl 功能通过 vfio-pci 完成),其给用户态提供操作设备(通过 vfio-pci 驱动)的 ioctl 命令(译者注:与用户态直接打交道的还是 VFIO,VFIO 转调 vfio-pci)。host 和 guest 上皆如此。
vfio-device 在 qemu 进程里(译者注:qemu 的一个模拟设备),其通过(host 内核中的)vfio-pci 来向 guest 呈现 virtio 全硬件 offloading 设备。vfio-device 负责向 guest 呈现一个虚拟设备,guest 通过该虚拟设备可以访问到真实的物理 NIC。
guest 内核中的 vfio-pci 通过 PCIe 访问 vfio-device(qemu 中的虚拟设备),而 host 内核中的 vfio-pci 通过 PCIe 访问 virtio 全硬件 offloading 设备。这里千万不要搞迷糊了,前者(也就是 guest 场景)中这是一个虚拟 PCIe,后者(也就是 host 场景)中这是一个连接物理 NIC 的物理 PCIe。
7. virtio 全硬件 offloading
行文至此,已经讨论了该架构所需的所有组件,现在看看这些东东是怎么揉在一起来实现 virtio 全硬件 offloading 方案的(其中 NIC 实现了 virtio 标准)。
图 3 展示了这些东东是怎么在一起工作的:
注意,本方案下 NIC 中包含一个 virtio-net VF,其完全兼容 virtio 数据面及控制面。后面会讨论到的 vDPA 方案中,我们会将 NIC VF 称为“vDPA 设备”,因为虽然其数据面支持 virtio,但控制面是 vendor specific 的(译者注:vDPA 方案下 VF 不能称为 virtio-net VF,是因为其控制面并非是 virtio 的,所以只能换一种称呼,也就是“vDPA 设备”)。
我们来捋一下 NIC 是怎么访问到 guest 应用的 queues 的:
-
要支持 DMA,先得在 NIC 和 guest 之间建立好映射。 -
为了建立映射,guest DPDK pmd 驱动通过 ioctl 命令对 DMA 及 VFIO 进行配置。对于 vIOMMU 来说,其完成 GIOVA(Guest IO Virtual Address) 到 GPA(Guest Physical Address) 的映射(译者注:vIOMMU 实现在 qemu 中,qemu 可以进一步将 GPA 转成 HVA,参考《vhost-user 深入》“11. vIOMMU:为 guest 而生的 IOMMU”)。 -
上一步完成后,vIOMMU(译者注:也就是 qemu)可以完成 GPA 到 HVA(Host Virtual Address) 的翻译。 -
于是,guest 完成了从 GIOVA 到 HVA 的映射。 -
host 侧会通过 VFIO(IOMMU)做类似映射,实现 GIOVA 到 HPA(Host Physical Address) 的映射(译者注:之所以要做这一层映射,是因为设备都是通过 GIOVA 发起 DMA 的,而最终实际所访问的是一个 HPA)。 -
一旦 NIC 使用 GIOVA 发起 DMA,其可以直接访问到属于该 guest 应用的 HPA。 -
如果我们为 guest 应用中的 virtqueues 建立好映射,硬件就能直接访问(读取、写入)到它们,因为通过上面这套组合拳,已经搞定了所有的映射问题。
virtio 控制面由 virtio-net-pmd 发起,流程差不多是:
-
virtio-net-pmd 通过 ioctl 命令与 guest 内核的 VFIO 搭上话。 -
guest VFIO 与 guest 内核 vfio-pci 搭上话。 -
guest 内核 vfio-pci 通过虚拟的 PCIe 总线与 QEMU vfio 设备搭上话。 -
QEMU vfio 设备通过 ioctl 命令访问 host 内核的 VFIO。 -
host 内核 VFIO 与 host 内核 vfio-pci 搭上话。 -
host 内核 vfio-pci 通过物理 PCIe 总线与 virtio 全硬件 offloading 设备搭上话。
通过 virtio 全硬件 offloading 设备,在 host 及 guest 中都只需要通用的驱动即可将硬件驱动起来(host 中使用标准的 VFIO 驱动,guest 中使用标准的 virtio 驱动),无需借助任何 vendor specific 的软件就能给 VMs 带来性能提升。
这套方案在工程实践中意味着啥呢?
此方案下,数据面和控制面都 offload 给硬件了,这要求 vendor 在 NIC 中实现完整的 virtio 标准(底层通过通用的传输接口,比如 PCI)。注意:各家 vendor 可以有其自己的实现(译者注:指的是 NIC 中 virtio 标准的实现)。
8. 对热迁的考虑
当使用 virtio 全硬件 offloading 时,为了支持热迁移,硬件必须支持 virtio 标准之外的一些 capability,比如脏页追踪,这就要求在 QEMU 和 NIC 之间提供新的 APIs(virtio version 1.1 标准尚不包含这些主题)。
9. virtio 数据面硬件 offloading
有些 vendor 会选择在硬件中只实现 virtio 设备的部分功能,主要是数据面相关的。这种方案称为 virtio 数据面加速器(virtio data path accelerator)或者 vDPA。在有些架构下这非常有用,比如 scalable IOV,它不同于 SR-IOV,并没有一个标准的硬件配置接口(下面会解释)。
vDPA 的设计哲学为控制面是 vendor specific 的,但向 guest 呈现出来的仍然是一个标准的 virtio 设备。这就需要通过软件在 NIC APIs 和 virtio 命令之间做模拟或中介翻译(mediation)。
通过 vDPA,VM guest 对设备的操作无需借助 vendor specific 的软件,但是,host 上还是需要一些 vendor specific 的软件。下面的章节会聚焦描述 mdev 框架,以及 host 上实现此功能的各组件。
10. mediator 设备(MDEV)
MDEV 支持设备模拟(纯软件的虚拟化)及中介翻译(mediation,在软件和硬件之间的虚拟化),比如将一个物理设备呈现为多个虚拟设备。这在多 VM 复用同一物理资源时会有用武之地(每个 VM 用到的是此物理设备的一部分)。
一个很自然的想法就是将数据面操作 offload 给硬件,通过 MDEV 框架给 VM guest 的控制面做模拟及中介翻译。
SR-IOV 支持在一个 NIC 上呈现多个 VFs,并通过标准接口来配置这些 VFs。IOMMU 可以限制每个 VF 对 virtio 驱动内存空间(VM guest 内存)的访问,这也就意味着 VF 无法访问 host 上的 MDEV 内存(译者注:VF 只能访问其 guest domain 内存,无法访问 host 内存)。当使用 SR-IOV 时,控制面操作需要借助 MDEV,因此这些操作要么与 PF(而不是 VF)打交道,要么将这些操作限制为只能从 MDEV 到 VF 的单向访问。为了实现更高的灵活性,MDEV 对一些先进技术(比如 scalable IOV)进行了支持(稍后详细讲解)。
回到 VFIO 驱动框架,它提供的 APIs 让用户态可以直接访问到设备,底层通过 IOMMU 对直接访问做安全保护。该框架可以应用于多种设备(GPUs,各种加速器等),但我们只关注网络设备。直接设备访问意味着 VMs 及用户态应用可以直接访问物理设备(译者注:就是设备直通啦)。下文可以看到,vfio-mdev 就是基于此框架构建的。
MDEV 可以分为如下几个部分:
-
中介核心驱动 APIs(mediated core driver APIs)。 -
中介设备 APIs(mediated device APIs)。 -
mdev-bus。 -
管理接口(management interface)。
中介核心驱动提供了一个通用的接口,驱动可以通过此接口来管理中介设备。它提供了如下操作的通用接口:
-
创建和销毁一个中介设备。 -
将一个中介设备添加到一个中介 bus 驱动上,或将其从中删除。 -
将一个中介设备添加到一个 IOMMU group 中,或将其从中删除。
中介设备 APIs 提供了注册 bus 驱动的接口。
mdev-bus 提供了使能驱动与设备之间通信的方法(大体上就是一组协商 APIs)。
管理接口提供了一个用户态 sysfs 文件路径,用户态通过这些文件来操作中介核心驱动。
MDEV 的第一个实现是 vfio-mdev。vfio-mdev 包含驱动部分和设备部分,驱动部分使用了中介核心驱动,设备部分实现了中介设备 API。“实现了中介设备 API”的底层含义是,设备实现了一组与驱动通信的 callback 函数。
之所以称其为 vfio-mdev,是因为一旦驱动被 probed,它会将设备添加到一个 vfio group 中,从而使得用户态可以访问 vfio APIs(译者注:意思是将一个设备添加至一个 vfio group 之后,用户就可以通过 vfio 的 ioctl 来控制该设备了)。这也就意味着从此刻开始,你可以像访问一个真实硬件设备那样安全地(IOMMU)访问中介设备。
译者注:总结一下,“中介核心驱动”驱动的是“中介设备”,“中介核心驱动”的用户接口是 sysfs,“中介设备”的用户接口是 vfio API,“中介设备”将来自用户的 VFIO 操作,翻译成 vendor specific 的驱动控制命令,通过 PCIe 给到物理设备,打通控制面。
图 4 展示了这些组件之间是怎么连起来的:
注意:
-
如前面所说,mdev bus 在内核中实现了软总线,支持在其上 attach 多个 mdev 设备和驱动。 -
vfio-mdev 驱动通过 mdev bus API 与 vfio-mdev device 通信,用户态通过 VFIO API 操作 vfio-mdev(译者注:注意 图 4 中的控制面,用户态 -> VFIO -> vfio-mdev -> vfio-mdev device)。 -
vfio-mdev device 向上层(vfio-mdev 驱动)实现真实的中介设备,并对下层(比如一个物理 PCI 总线)实现通用的 VFIO API(译者注:这句话的意思是,vfio-mdev 设备将其接收到的 VFIO 命令,转成设备 specific 的命令,并通过 PCI 总线给到物理设备)。可以把它理解为是一个将 VFIO 命令翻译成设备 specific 命令的驱动,对于物理设备所不支持的功能,vfio-mdev 设备会对其进行模拟。
译者注:vfio-mdev device 在 vfio-mdev 驱动和物理 PCI 总线(物理设备)之间充当一个翻译中介的角色,vfio-mdev 驱动操作这个 vfio-mdev device,vfio-mdev device 将驱动传过来的 VFIO 命令,翻译成设备 specific 的命令,并通过 PCI 总线将命令发给物理设备。
总之,mdev 框架支持内核向驱动呈现一个设备,驱动可以操作此设备。真正的 function 被 offload 给硬件,或者由软件来模拟。
回到 vDPA 的实现上,mdev 的能力很好地契合了我们将数据面 offload 给硬件的目标,并通过内核(译者注:VFIO、mdev 驱动及设备都是实现在 host 内核空间的)对控制面做中介,因此 vDPA 的实现离不开它。
关于 mdev 的更多信息,参考 kernel doc “vfio-mediated-device”。
11. virtio-mdev 框架
virtio-mdev 框架旨在提供一组标准的 APIs,vDPA NIC vendor 基于这组 APIs 实现他们自己的控制面驱动。如前文所述,mdev 框架支持将数据面交由硬件实现,而控制面交由软件实现(译者注:意思就是数据面直通,控制面还是走模拟、中介、翻译)。
驱动要么是用户态驱动(基于 VFIO),要么是内核 virtio 驱动,也就是说,驱动的使用者可能在用户态也可能在内核态(译者注:vfio-mdev 解决的是使用者在用户态,virtio-mdev 解决的是使用者在内核态)。本系列文章我们聚焦用户态驱动,但将来的系列文章我们也会讨论内核 virtio 驱动(作为 AF_VIRTIO future framework 的一部分)。
在 virtio-mdev 框架的支持下,控制面通过 mdev 软件组件完成,而数据面则 offload 给硬件。这可以简化 virtio mdev 设备的实现,因为无论是内核态还是用户态驱动,都只需要实现 virtio-mdev API 即可。
“virtio mdev transport API”是 virtio-mdev 框架的具体实现。该 API 包含如下功能:
-
设备配置空间的 set/get。 -
virtqueue 元数据的 set/get:vring 地址、大小。 -
kick 一个指定的 virtqueue。 -
为一个指定的 vring 注册中断回调。 -
特性协商。 -
脏页日志的 set/get。 -
设备的 start/reset。
这些功能是一个驱动能对 virtio 设备进行的操作的抽象(译者注:virtio 控制面基本上也就是这些功能,既然 virtio-mdev 前端对接的是 virtio 驱动,自然也要实现这些功能)。驱动可以使用这些功能的全部或部分。
一个具体例子是,在 VM 热迁场景下需要使用脏页追踪功能,但如果是使用内核驱动的 kubernetes 容器,则它们无需脏页追踪(假设是无状态容器)。
vhost-mdev 是一个内核模块,其负责下面两个功能:
-
将用户态 virtio 控制命令(译者注:vhost-net 的 ioctl)转发给 virtio mdev transport API。 -
使用 VFIO 框架来完成用户空间的 DMA map/unmap 请求,这是设备访问进程内存的前提条件。
从更高维度来看,vhost-mdev 的工作为:
-
将其自身注册为一种新类型的 mdev 驱动。 -
暴露若干个兼容 vhost-net 的 ioctl,用户态驱动通过这些 ioctl 来传递 virtio 控制命令。 -
其将命令翻译为 virtio mdev transport API,并将命令通过 mdev bus 发送给 virtio mdev device(稍后解释)。 -
当内核中创建了一个新的 mdev 设备时,尝试 probe 该设备。 -
probe 的过程中,vhost-mdev 会将 virtio mdev device 添加至 VFIO group,如此通过 VFIO container 的 FD(file descriptor,文件描述符)来发送 DMA 请求(译者注:During the probe process the vhost-mdev will attach the virtio mdev device to the VFIO group and then the DMA requests can go through the VFIO container FD。这里的 DMA request 我理解应该是“DMA 配置请求”,而不是 PCIe 总线上的“DMA 访问请求”)。
vhost-mdev 本质上是用户态驱动和 virtio-mdev device 之间的桥梁。它向用户态驱动提供两个 FD:
-
vhost-mdev FD:通过该 FD 接收来自用户态的 vhost 控制命令。 -
VFIO container FD:用户态驱动通过该 FD 初始化 DMA。
译者注:vhost-mdev 接收用户态 vhost-net ioctl,并将其转成 virtio mdev transport API(也就是 virtio-mdev 驱动对 virtio 设备可操作功能的抽象),并转发给 virtio-mdev 设备。
virtio-mdev 驱动是一个内核模块,它通过 virtio mdev transport API 实现 “virtio configs ops” 。“virtio configs ops”是一组 API,它们让 virtio 设备与 transport 实现(比如本例中的 PCI 或 mdev) 进行通信。
virtio-mdev 驱动将其自己注册为一种新类型的 mdev 驱动,并向 virtio 驱动呈现一个基于 mdev 的 transport。它是内核 virtio 驱动和 virtio mdev 设备实现之间的桥梁。因为它是 virtio 可以使用的另一种 transport(具体来说,它是 PCI 之外的一种 transport),无需做其他修改即可将其连接到目标 virtio-net 驱动。这在 AF_VIRTIO 中是很基本的组件。
virtio-mdev 设备是实际的 mdev 设备实现(相较于前面章节所讨论的 vfio-mdev),它可以被 virtio-mdev 驱动或 vhost-mdev 驱动所使用。注意 vDPA 流程会侧重 vhost-mdev 驱动流程。
站在 mdev-bus 角度,virtio-mdev 设备是一个设备,因为它实现了 mdev 设备 API。
站在 PCIe-bus 硬件角度,virtio-mdev 设备是一个驱动,因为它将命令发给真实的物理硬件。
译者注:本节有点麻烦,译者尝试梳理一下。内核态 virtio 驱动是标准的,非 mdev 架构下,其会通过 PCI transport 将控制命令给到设备。在 mdev 架构下,传统基于 PCI transport 的设备控制通道,被替换成 virtio-mdev 驱动,virtio-mdev 驱动与 virtio-mdev 设备之间的操作接口为 “virtio mdev transport API”,virtio-mdev 设备是命令的翻译中介,并将最终的控制命令通过 PCIe 总线给到物理 vDPA 设备。virtio-mdev 驱动通过 “virtio mdev transport API” 接口,进一步实现了所谓的 “virtio configs ops”,”virtio configs ops” 本身也是一组 API,我个人的理解,”virtio mdev transport API” 与 “virtio configs ops” 的区别在于,后者是基于前者实现的,前者是更 meta 的,是 virtio-mdev 驱动与 virtio-mdev 设备之间的接口,而后者是内核 virtio 驱动与 virtio-mdev 驱动之间的接口,是对内核 virtio 驱动传统 PCI transport 的替换(理解的不一定对)。
图 5 展示这些组件是怎么揉在一起的:
注意,virtio-mdev 驱动和 virtio-net 设备用来展示统一的 virtio-mdev transport APIs 所带来的好处,实际上它们并不属于 vDPA 内核的流程。
12. 基于内核实现的 vDPA
为理解基于内核实现的 vDPA ,需要先看另一个组件:vhost-vfio。
-
从 qemu 视角来看,vhost-vfio 是一种新型 qemu 网络后端,其与 virtio-net 设备模型(译者注:应该指的是 guest 中的 virtio-net 前端)搭配干活。 -
从内核视角来看,vhost-vfio 是 vhost-mdev 设备的用户态驱动。
它的主要任务是:
-
初始化 vhost-mdev 设备,具体包括:
-
从 virtio-net 设备模型(译者注:guest 中的 virtio-net 前端)接收数据面 offloading 命令(set/get virtqueue 状态,设置脏页日志,特性协商,等等),并将它们翻译为 vhost-mdev 设备的 ioctl。这只在驱动配置设备时才会发生。 -
接收 vIOMMU 的 map/umap 请求,并通过 VFIO DMA ioctl 执行这些映射操作,确保 mdev 接收到正确的映射。根据驱动的具体类型,映射可以是临时的(比如,内核驱动使用临时映射)或长期的(DPDK 驱动使用的静态映射)。
现在我们有了 vhost-vfio,图 6 展示基于内核实现的 vDPA:
值得注意的是,我们讨论 vDPA 时所涉及的 NIC VFs 语义,是基于 SRIOV 的。但这并不是必须的,如果 NIC vendor 通过其他方式在一个物理 NIC 上实现多个 vDPA 实例,vDPA 架构照样可以工作。这是因为,虽然 virtio 布局是在 SRIOV 之上实现的(译者注:意思就是 virtio 布局底层是通过 SRIOV 接口来协商的),但只要布局(暴露给 guest 或用户态进程)与 virtio 标准保持一致,则底层具体是由哪个组件来实现的并不重要。
具体来说,scalable IOV 技术中,NIC 并不是以 VF 为粒度进行切分的,而是以 queue 对(pairs)来进行切分的(对 NIC 资源进行了更细粒度的划分)。如果我们使用 scalable IOV(而不是 SRIOV),并假设 NIC vendor 实现了 virtio ring 布局,则数据面将保持不变。唯一的变化在控制面中,需要新的 virtio-mdev 设备以及 vendor PF 驱动(PF 表达的是对物理 NIC 的配置,译者注:host 用 PF 驱动来配置、使能物理 NIC 的 scalable IOV 功能)。
回到 图 6 的 vDPA 架构:
-
对于硬件层,vDPA 设备(NIC 中 VF 和 PF 中间的那个,译者注:也就是 图 6 NIC 中的 virtio-net VF)使用它专有的控制面路径直接访问 virtqueue。这与 virtio 全硬件 offloading 中的 virtio-net VF 不同(全硬件 offloading 中数据面和控制面都是 virtio)。 -
virtio-mdev 设备实现了驱动底层 vDPA 设备的功能。借助此 vDPA 设备,实现将 virtio 数据面 offload 给设备。virtio-mdev 设备通过 virtio-mdev transport API 接收 virtio-mdev 传输命令,对命令做翻译,进而对命令做模拟或中介(译者注:将命令转发给 vDPA 设备)。 -
virtio-mdev transport API(通过 mdev-bus)为用户态和内核驱动驱动(译者注:前一个“驱动”是驱动程序,后一个“驱动”是动词) virtio-mdev 设备提供了统一的 API。它向驱动提供了设备的抽象。 -
对于用户态,vhost-mdev 将其自身注册为一个新型 mdev 驱动,并在用户态 vhost 驱动和 virtio-mdev 设备之间充当桥梁的角色。其通过 vhost-mdev 接收兼容 vhost-net 的 ioctls,并将它们翻译为 virtio-mdev transport API。因为它是位于 VFIO group 中的,所有 DMA 相关设置都可以通过 container fd 的 VFIO API 来完成。 -
vhost-vfio 是与 qemu 中的 virtio 设备模型配对的用户态 vhost 驱动,为 VM 提供一个虚拟 virtio 设备(比如说 PCI 设备)。通过 vIOMMU 接收 DMA 映射请求,并通过 VFIO container FD 进行转发,而 virtio 设备相关的设置和配置,通过 vhost-mdev fd 完成。 -
guest 中,VFIO 用来给用户态驱动(比如 DPDK 应用中的 virtio-net PMD)提供设备 API 和 DMA API。
13. 基于 DPDK 的 vDPA 实现
DPDK 实现对 vDPA 的支持始于 v18.05,其基于 vhost-user 协议,为前端提供了统一的控制面。该 DPDK 实现旨在展示 vDPA 的能力,从而让基于 MDEV 框架的 vDPA 框架更加标准原生。
本节内容基于《vhost-user 深入》文章中,对使用 DPDK 的 virtio 网络组件的理解。基于此,我们添加两个额外的组件:
-
用户态 vDPA 驱动,用于控制 vDPA 硬件设备。 -
vDPA 框架,在 vhost-user socket 和 vDPA 驱动之间提供了桥梁。
图 7 展示了这些是怎么揉在一起的:
DPDK vDPA 框架提供了一组由 vendor vDPA 驱动实现的 callbacks,vhost-user 库会通过调用它们来建立数据面:
/**
* vdpa device operations
*/
struct rte_vdpa_dev_ops {
/** Get capabilities of this device */
int (*get_queue_num)(int did, uint32_t *queue_num);
/** Get supported features of this device */
int (*get_features)(int did, uint64_t *features);
/** Get supported protocol features of this device */
int (*get_protocol_features)(int did, uint64_t *protocol_features);
/** Driver configure/close the device */
int (*dev_conf)(int vid);
int (*dev_close)(int vid);
/** Enable/disable this vring */
int (*set_vring_state)(int vid, int vring, int state);
/** Set features when changed */
int (*set_features)(int vid);
/** Destination operations when migration done */
int (*migration_done)(int vid);
/** Get the vfio group fd */
int (*get_vfio_group_fd)(int vid);
/** Get the vfio device fd */
int (*get_vfio_device_fd)(int vid);
/** Get the notify area info of the queue */
int (*get_notify_area)(int vid, int qid, uint64_t *offset, uint64_t *size);
/** Reserved for future extension */
void *reserved[5];
};
这其中一半的 callbacks 与标准 vhost-user 协议是一一对应的关系,比如那些 set/get virtio 特性、获取所支持 vhost-user 协议特性,等等。
dev_conf callback 用来设置并启动设备,在 vhost-user 层接收到设置及使能数据面所需的所有信息之后被调用。在该 callback 中,默认 vDPA 设备驱动已经设置好 ring 地址、建好 DMA 映射并启动了设备。
在 vDPA 设备支持热迁脏页追踪特性的情况下,其驱动可能会实现 migration_done callback,用以通知热迁结束。否则,vDPA 框架会支持在热迁过程中将 ring 的处理切换到软件实现上【译者注:意思应该是,此场景下,热迁时可能会重新让软件接管数据面(slowpath),这样解决直通设备的热迁问题】。
最后,出于性能考虑,提出了一组 callbacks 用来在前端和设备之间共享 ring notification area。对这组 callbacks 的支持依赖于为此目的专门引入的 host-notifier vhost-user 协议请求。
14. 结合 vDPA 和 virtio 全硬件 offloading
前面章节说到,virtio 全硬件 offloading 意味着 virtio 数据面与控制面皆由硬件设备来实现。这也意味着,该设备可以直接被内核的 virtio-net 驱动或 DPDK virtio PMD probe。
该方案的一种替代方案是使用 vDPA 设备,这意味着数据面仍然完全被 offload,但控制面由 vDPA API 进行管理。
为啥要这么干呢?
此架构相较于使用 virtio PMD 直接驱动设备的优势在于:
-
灵活性:从 guest 视角来说,它支持透明地切换到其他 vDPA 设备,或者切换到其他软件实现的 ring 处理上。 -
热迁:它提供了热迁支持,通过将 ring 处理切换到软件实现上做脏页追踪。 -
标准化:其为 containers(DPDK vDPA 架构的情况下为 vhost-user socket)提供了统一的接口,无论底下的设备是全硬件 offloaded 的,还是 vDPA only,亦或者 vendor specific 的数据面。在 vendor specific 数据面场景下,virtio ring 的处理在软件实现中,并且 packets 被转发给 vendor PMD。
图 8 展示使用了 DPDK 的 vDPA 框架:
vDPA 驱动被命名为 virtio-vDPA,这是一个通用的 vDPA 驱动,在控制面和数据面上兼容所有符合 virtio 标准的设备。该驱动目前在 upstream 的 DPDK 中尚不可用,还在 review 中,应该会出现在 DPDK v19.11 中。
15. 总结
本文我们介绍了 virtio 全硬件 offloading 和 vDPA 的细节。
我们知道本文很复杂,包含了很多令人望而生畏的内核态及用户态组件。通过了解这些组件是怎么揉在一起工作的,读者现在应该可以理解 vDPA 方案通过一个开放标准的框架来替换掉现有 VM 加速技术(例如 SRIOV)的重要性。
期待其他新的技术(比如 scalable IOV),相信 vDPA 会因其架构的灵活性而大放异彩,无需 VM 或 kubernetes 侧的修改即可工作。
下一篇文章中我们将讨论 vDPA 的实操。鉴于 vDPA 卡仍在市场推广中,我们将在 guest 中使用半虚拟化 virtio-net 设备。该设备将模拟 vDPA 控制面,并使用现有的 vhost-net 来承载数据面。
后面的文章中,我们还将展示如何使用 vDPA 来加速 kubernetes 容器。
原文始发于微信公众号(窗有老梅):[译 9] vDPA 深入