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. 参考资料¶
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