32. 结合 ENQCMD 的共享虚拟寻址 (SVA)

32.1. 背景

共享虚拟寻址 (SVA) 允许处理器和设备使用相同的虚拟地址,从而避免了软件将虚拟地址转换成物理地址的需求。SVA 就是 PCIe 中所称的共享虚拟内存 (SVM)。

除了设备使用应用程序虚拟地址带来的便利之外,它还不需要为 DMA 固定页面。PCIe 地址转换服务 (ATS) 连同页面请求接口 (PRI) 一起,允许设备以非常类似于 CPU 处理应用程序缺页异常的方式运作。有关更多信息,请参考 PCIe 规范第 10 章:ATS 规范。

使用 SVA 需要平台提供 IOMMU 支持。IOMMU 也是支持 PCIe 特性 ATS 和 PRI 所必需的。ATS 允许设备缓存虚拟地址的转换。IOMMU 驱动程序使用 mmu_notifier() 支持来保持设备 TLB 缓存与 CPU 缓存同步。当虚拟地址的 ATS 查找失败时,设备应使用 PRI 请求将该虚拟地址调入 CPU 页表中。设备在将地址用于转换前必须再次使用 ATS 来获取它。

32.2. 共享硬件工作队列

与单根 I/O 虚拟化 (SR-IOV) 不同,可扩展 I/O 虚拟化 (SIOV) 允许应用程序和虚拟机 (VM) 使用共享工作队列 (SWQ)。相比于可能导致利用率不足的硬分区资源,这能实现更好的硬件利用率。为了让硬件能够通过 SWQ 接口区分硬件中正在执行工作的上下文,SIOV 使用了进程地址空间 ID (PASID),这是一个由 PCIe SIG 定义的 20 位数字。

PASID 值被编码在来自设备的所有事务中。这使得 IOMMU 除了使用作为总线/设备/功能 (Bus/Device/Function) 的 PCIe 资源标识符 (RID) 之外,还能以按 PASID 的粒度跟踪 I/O。

32.3. ENQCMD

ENQCMD 是 Intel 平台上的一个新指令,它以原子方式向设备提交工作描述符。该描述符包括要执行的操作、所有参数的虚拟地址、完成记录的虚拟地址以及当前进程的 PASID(进程地址空间 ID)。

ENQCMD 采用非发布式(non-posted)语义工作,如果命令被硬件接受,则会返回一个状态。这使提交者能够知道是否需要重试提交,或者是否应提供其他设备特定机制来实现公平性或确保前向进展。

ENQCMD 是确保应用程序可以直接向硬件提交命令的纽带,同时也允许硬件通过使用 PASID 来感知应用程序上下文以执行 I/O 操作。

32.4. 进程地址空间标记

一个新的线程级作用域 MSR (IA32_PASID) 提供了用户进程与硬件其余部分之间的连接。当应用程序首次访问支持 SVA 的设备时,此 MSR 会被初始化为一个新分配的 PASID。设备驱动程序调用一个特定于 IOMMU 的 API,该 API 设置了 DMA 和页面请求的路由。

例如,Intel 数据流加速器 (DSA) 使用 iommu_sva_bind_device(),它将执行以下操作

  • 分配 PASID,并在 PASID 上下文条目中配置进程页表(%cr3 寄存器)。

  • 注册 mmu_notifier() 以跟踪任何页表失效,从而保持设备 TLB 同步。例如,当页表条目失效时,IOMMU 会将该失效传播到设备 TLB。这将强制设备对该虚拟地址的任何后续访问都参与 ATS。如果 IOMMU 响应页面不存在,设备将在执行 I/O 之前通过 PCIe PRI 协议请求将该页面调入。

此 MSR 作为“管理者状态(supervisor state)”与 XSAVE 特性集一起进行管理,以确保在上下文切换期间更新该 MSR。

32.5. PASID 管理

内核必须代表每个将使用 ENQCMD 的进程分配一个 PASID,并将其配置到新的 MSR 中,以将进程身份传达给平台硬件。ENQCMD 使用存储在此 MSR 中的 PASID 来标记来自该进程的请求。当用户使用 ENQCMD 指令向设备提交工作描述符时,描述符中的 PASID 字段会自动填充来自 MSR_IA32_PASID 的值。来自设备的 DMA 请求也会被标记上相同的 PASID。平台 IOMMU 使用事务中的 PASID 来执行地址转换。IOMMU API 使用 CPU 使用的进程地址(例如 x86 中的 %cr3 寄存器)在 IOMMU 中设置对应的 PASID 条目。

在任何应用程序线程能够与设备交互之前,必须在每个逻辑 CPU 上配置该 MSR。属于同一进程的线程共享相同的页表,因此也共享相同的 MSR 值。

32.6. PASID 生命周期管理

创建进程时,PASID 初始化为 IOMMU_PASID_INVALID (-1)。

只有访问支持 SVA 的设备的进程才需要分配 PASID。此分配发生在进程打开/绑定支持 SVA 的设备但发现该进程没有 PASID 时。对同一设备或其他设备的后续绑定将共享相同的 PASID。

尽管通过打开设备将 PASID 分配给了进程,但它在该进程的任何线程中都不是激活状态。当某个线程尝试使用 ENQCMD 向设备提交工作描述符时,它会被延迟加载到 IA32_PASID MSR 中。

第一次访问将触发 #GP 异常,因为 IA32_PASID MSR 尚未被初始化为打开设备时分配给进程的 PASID 值。Linux #GP 处理程序注意到已为该进程分配了 PASID,因此会初始化 IA32_PASID MSR 并返回,从而重新执行 ENQCMD 指令。

在 fork(2) 或 exec(2) 时,PASID 会从进程中移除,因为它不再具有与打开设备时相同的地址空间。

在 clone(2) 时,新任务共享相同的地址空间,因此能够使用分配给该进程的 PASID。IA32_PASID 不会被抢先初始化,因为 PASID 值可能尚未分配,或者内核不知道该线程是否会访问设备,并且清空的 IA32_PASID MSR 通过 xstate 初始化优化降低了上下文切换开销。由于必须在 PASID 分配给进程的 mm 之前创建的任何线程上处理 #GP 异常,因此新创建的线程也以相同的方式统一处理。

由于在解除绑定(unbind)时释放 PASID 并清除所有线程中的所有 IA32_PASID MSR 极其复杂,因此改为仅在 mm 退出时延迟释放 PASID。

如果进程对设备文件描述符执行了 close(2) 并且对设备 MMIO 门户(portal)执行了 munmap(2),则驱动程序将解除绑定设备。对于进程中访问过该设备的任何线程,其 PASID 在 PASID_MSR 中仍被标记为 VALID。但这并无害处,因为没有 MMIO 门户,它们无法向设备提交新的工作。

32.7. 关系

  • 每个进程有许多线程,但只有一个 PASID。

  • 设备拥有有限数量(约数十到数千个)的硬件工作队列。设备驱动程序负责管理硬件工作队列的分配。

  • 单次 mmap() 将单个硬件工作队列映射为一个“门户(portal)”,且每个门户都映射到底层的一个工作队列。

  • 对于进程与之交互的每个设备,必须存在一个或多个经过 mmap() 映射的门户。

  • 进程内的许多线程可以共享单个门户来访问单个设备。

  • 多个进程可以分别 mmap() 相同的门户,在此情况下它们依然共享同一个设备硬件工作队列。

  • 所有线程都使用进程范围的单一 PASID 来与所有设备交互。例如,并不是每个线程或每个“线程<->设备”对都有各自的 PASID。

32.8. 常见问题解答

  • 什么是 SVA/SVM?

共享虚拟寻址 (SVA) 允许 I/O 硬件和处理器在同一个地址空间中工作,即共享该空间。有些人称之为共享虚拟内存 (SVM),但 Linux 社区希望避免将其与当时已在流传的 POSIX 共享内存(POSIX Shared Memory)和安全虚拟机(Secure Virtual Machines)混淆。

  • 什么是 PASID?

进程地址空间 ID (PASID) 是 PCIe 定义的事务层数据包 (TLP) 前缀。PASID 是由操作系统分配和管理的 20 位数字。平台与设备之间的所有事务中都会包含 PASID。

  • 共享工作队列有何不同?

传统上,为了让用户空间应用程序与硬件交互,每个进程都需要一个单独的硬件实例。例如,以门铃(doorbells)作为向硬件通知待处理工作的机制。出于进程隔离的考虑,每个门铃之间必须间隔 4k(或页面大小)。这要求硬件提供该空间并将其在 MMIO 中保留。当线程数量变得非常大时,这种方法无法扩展。硬件还会管理共享工作队列 (SWQ) 的队列深度,因此消费者无需跟踪队列深度。如果没有空间来接受命令,设备将返回一个指示重试的错误。

用户应检查设备上的可延迟内存写入 (DMWr) 功能,并且仅当设备支持时才提交 ENQCMD。在新的 DMWr PCIe 术语中,设备需要支持 DMWr 完成者(completer)功能。此外,它要求所有交换机端口都支持 DMWr 路由,并且必须由 PCIe 子系统启用,这很像 PCIe 原子操作的管理方式。

SWQ 允许硬件仅在设备中配置单个地址。当与 ENQCMD 结合使用来提交工作时,由于其中会包含分配给该进程的 PASID,设备能够区分出提交工作的进程。这有助于设备扩展支持大量的进程。

  • 这与用户空间设备驱动程序相同吗?

通过共享工作队列与设备进行通信,要比编写一个功能齐全的用户空间驱动程序简单得多。内核驱动程序负责完成硬件的所有初始化。用户空间只需要关心提交工作和处理完成事件。

  • 这与 SR-IOV 相同吗?

单根 I/O 虚拟化 (SR-IOV) 专注于为虚拟化硬件提供独立的硬件接口。因此,它要求成为一个对软件而言几乎功能完备的接口,支持传统的 BAR、通过 MSI-X 的中断空间以及自身的寄存器布局。虚拟功能 (VF) 由物理功能 (PF) 驱动程序协助。

可扩展 I/O 虚拟化基于 PASID 概念构建,用于为虚拟化创建设备实例。SIOV 需要宿主机软件来协助创建虚拟设备;每个虚拟设备都由一个 PASID 以及设备的“总线/设备/功能”来表示。这允许设备硬件优化设备资源的创建,并能根据需求动态增长。而 SR-IOV 的创建和管理本质上非常静态。有关更多详情,请参阅下方的参考资料。

  • 为什么不直接为每个应用程序创建一个虚拟功能(VF)呢?

创建 PCIe SR-IOV 类型的虚拟功能 (VF) 成本很高。VF 需要为 PCI 配置空间和中断(例如 MSI-X)复制硬件。诸如中断之类的资源必须在创建时在各个 VF 之间进行硬分区,且无法按需动态扩展。VF 与物理功能 (PF) 并非完全独立。大多数 VF 都需要来自 PF 驱动程序的某些通信和协助。相比之下,SIOV 创建的是一个软件定义的设备,其中所有的配置和控制方面都通过慢速路径(slow path)进行中介。而工作的提交和完成则无需任何中介直接进行。

  • 这支持虚拟化吗?

可以在客户机虚拟机 (VM) 内部使用 ENQCMD。在这种情况下,VMM 会协助建立一个转换表,以实现从客户机 PASID 到宿主机 PASID 的转换。有关更多详情,请参阅 ENQCMD 指令集参考。

  • 内存需要被固定(pinned)吗?

当设备支持 SVA,且诸如 IOMMU 之类的平台硬件也支持此类设备时,出于 DMA 的目的,就不需要固定(pin)内存。支持 SVA 的设备同时也支持其他消除了内存固定需求的 PCIe 特性。

设备 TLB 支持 - 设备在使用地址前,会通过地址转换服务 (ATS) 请求让 IOMMU 查找该地址。如果映射存在,但操作系统未分配页面,则 IOMMU 硬件会返回映射不存在的结果。

设备请求通过页面请求接口 (PRI) 来映射虚拟地址。一旦操作系统成功完成映射,就会将响应返回给设备。设备再次请求地址转换并继续运行。

IOMMU 与操作系统协同工作,以管理页表与设备之间的一致性。在移除页面时,它会与设备交互,以清除在从操作系统中移除映射之前可能已被缓存的任何设备 TLB 条目。

32.9. 参考资料

VT-D: https://01.org/blogs/ashokraj/2018/recent-enhancements-intel-virtualization-technology-directed-i/o-intel-vt-d

SIOV: https://01.org/blogs/2019/assignable-interfaces-intel-scalable-i/o-virtualization-linux

ISE 中的 ENQCMD: https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf

DSA 规范: https://software.intel.com/sites/default/files/341204-intel-data-streaming-accelerator-spec.pdf