31. 使用 ENQCMD 的共享虚拟寻址 (SVA)¶
31.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 才能获取转换以供使用。
31.2. 共享硬件工作队列¶
与单根 I/O 虚拟化 (SR-IOV) 不同,可扩展 IOV (SIOV) 允许应用程序和虚拟机 (VM) 使用共享工作队列 (SWQ)。与可能导致利用不足的硬分区资源相比,这可以更好地利用硬件。为了允许硬件通过 SWQ 接口区分硬件中执行工作的上下文,SIOV 使用进程地址空间 ID (PASID),这是 PCIe SIG 定义的 20 位数字。
PASID 值编码在设备的所有事务中。这允许 IOMMU 除了使用 PCIe 资源标识符 (RID)(即总线/设备/功能)之外,还以每个 PASID 的粒度跟踪 I/O。
31.3. ENQCMD¶
ENQCMD 是 Intel 平台上的一条新指令,它可以原子地将工作描述符提交给设备。该描述符包括要执行的操作、所有参数的虚拟地址、完成记录的虚拟地址以及当前进程的 PASID(进程地址空间 ID)。
ENQCMD 使用非发布语义,并在命令被硬件接受时返回状态。这允许提交者知道是否需要重试提交,或者是否应提供其他设备特定机制来实现公平性或确保向前进展。
ENQCMD 是确保应用程序可以直接向硬件提交命令的粘合剂,并且还允许硬件了解应用程序上下文,以便通过使用 PASID 执行 I/O 操作。
31.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 使用 XSAVE 特性集作为“管理程序状态”进行管理,以确保在上下文切换期间更新 MSR。
31.5. PASID 管理¶
内核必须代表每个将使用 ENQCMD 的进程分配 PASID,并将其编程到新的 MSR 中,以将进程标识传达给平台硬件。ENQCMD 使用存储在此 MSR 中的 PASID 来标记来自此进程的请求。当用户使用 ENQCMD 指令向设备提交工作描述符时,描述符中的 PASID 字段会自动填充来自 MSR_IA32_PASID 的值。来自设备的 DMA 请求也使用相同的 PASID 进行标记。平台 IOMMU 使用事务中的 PASID 来执行地址转换。IOMMU API 在 IOMMU 中使用 CPU 使用的进程地址(例如,x86 中的 %cr3 寄存器)设置相应的 PASID 条目。
在任何应用程序线程可以与设备交互之前,必须在每个逻辑 CPU 上配置 MSR。属于同一进程的线程共享相同的页表,因此共享相同的 MSR 值。
31.6. PASID 生命周期管理¶
当创建进程时,PASID 初始化为 IOMMU_PASID_INVALID (-1)。
只有访问支持 SVA 的设备的进程才需要分配 PASID。当进程打开/绑定支持 SVA 的设备但未找到此进程的 PASID 时,会发生此分配。同一设备或其他设备的后续绑定将共享相同的 PASID。
虽然 PASID 是通过打开设备分配给进程的,但它在任何该进程的线程中都不是活动的。当线程尝试使用 ENQCMD 向设备提交工作描述符时,它会延迟加载到 IA32_PASID MSR 中。
第一次访问将触发 #GP 错误,因为在打开设备时分配给进程的 PASID 值尚未初始化 IA32_PASID MSR。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 错误,因此新创建的线程也可能以一致的方式处理。
由于在取消绑定中释放 PASID 和清除所有线程中的所有 IA32_PASID MSR 的复杂性,因此仅在 mm 退出时延迟释放 PASID。
如果进程执行设备文件描述符的 close(2) 和设备 MMIO 入口的 munmap(2),则驱动程序将取消绑定该设备。PASID 仍然在 PASID_MSR 中标记为 VALID,适用于进程中访问过该设备的任何线程。但这无害,因为在没有 MMIO 入口的情况下,它们无法向设备提交新工作。
31.7. 关系¶
每个进程都有许多线程,但只有一个 PASID。
设备具有有限数量(约 10 个到 1000 个)的硬件工作队列。设备驱动程序管理硬件工作队列的分配。
单个 mmap() 将单个硬件工作队列映射为“入口”,并且每个入口映射到单个工作队列。
对于进程与之交互的每个设备,必须有一个或多个 mmap() 的入口。
进程中的多个线程可以共享单个入口来访问单个设备。
多个进程可以单独 mmap() 同一个入口,在这种情况下,它们仍然共享一个设备硬件工作队列。
所有线程都使用单个进程范围的 PASID 来与所有设备交互。例如,没有针对每个线程或每个线程<->设备对的 PASID。
31.8. 常见问题解答¶
什么是 SVA/SVM?
共享虚拟寻址 (SVA) 允许 I/O 硬件和处理器在同一个地址空间中工作,即共享该地址空间。有些人称之为共享虚拟内存 (SVM),但 Linux 社区希望避免将其与已经流通的 POSIX 共享内存和安全虚拟机等术语混淆。
什么是 PASID?
进程地址空间 ID (PASID) 是一个 PCIe 定义的事务层数据包 (TLP) 前缀。PASID 是一个 20 位数字,由操作系统分配和管理。PASID 包含在平台和设备之间的所有事务中。
共享工作队列有何不同?
传统上,为了让用户空间应用程序与硬件交互,每个进程都需要一个单独的硬件实例。例如,将门铃视为通知硬件处理工作的机制。为了进程隔离,每个门铃需要间隔 4k(或页面大小)。这要求硬件配置该空间并将其保留在 MMIO 中。随着线程数量变得非常大,这种方式无法扩展。硬件还管理共享工作队列 (SWQ) 的队列深度,消费者无需跟踪队列深度。如果没有空间接受命令,设备将返回错误指示重试。
用户应检查设备上的可延迟内存写入 (DMWr) 功能,并且仅当设备支持时才提交 ENQCMD。在新的 DMWr PCIe 术语中,设备需要支持 DMWr 完成器功能。此外,它要求所有交换机端口支持 DMWr 路由,并且必须由 PCIe 子系统启用,就像 PCIe 原子操作的管理方式一样。
SWQ 允许硬件仅在设备中配置一个地址。当与 ENQCMD 一起用于提交工作时,设备可以区分提交工作的进程,因为它将包含分配给该进程的 PASID。这有助于设备扩展到大量进程。
这与用户空间设备驱动程序相同吗?
通过共享工作队列与设备通信比完整的用户空间驱动程序要简单得多。内核驱动程序完成硬件的所有初始化。用户空间只需要担心提交工作和处理完成情况。
这与 SR-IOV 相同吗?
单根 I/O 虚拟化 (SR-IOV) 专注于为虚拟化硬件提供独立的硬件接口。因此,它需要几乎完全功能化的软件接口,支持传统的 BAR、通过 MSI-X 中断的空间以及其自身的寄存器布局。虚拟功能 (VF) 由物理功能 (PF) 驱动程序辅助。
可扩展 I/O 虚拟化建立在 PASID 概念之上,为虚拟化创建设备实例。SIOV 要求主机软件协助创建虚拟设备;每个虚拟设备由一个 PASID 以及设备的总线/设备/功能表示。这允许设备硬件优化设备资源创建,并且可以根据需要动态增长。SR-IOV 的创建和管理本质上是非常静态的。请参阅下面的参考资料了解更多详细信息。
为什么不为每个应用程序创建一个虚拟功能?
创建 PCIe SR-IOV 类型的虚拟功能 (VF) 成本很高。VF 需要为 PCI 配置空间和中断(如 MSI-X)复制硬件。诸如中断之类的资源必须在创建时在 VF 之间进行硬分区,并且无法根据需要动态扩展。VF 并非完全独立于物理功能 (PF)。大多数 VF 都需要来自 PF 驱动程序的某些通信和协助。相比之下,SIOV 创建一个软件定义的设备,其中所有配置和控制方面都通过慢速路径进行调解。工作提交和完成无需任何调解即可进行。
这是否支持虚拟化?
ENQCMD 可以从访客虚拟机内部使用。在这些情况下,VMM 帮助设置一个转换表,以将访客 PASID 转换为主机 PASID。请参阅 ENQCMD 指令集参考了解更多详细信息。
内存是否需要锁定?
当设备支持 SVA 以及支持此类设备的平台硬件(如 IOMMU)时,无需为 DMA 目的锁定内存。支持 SVA 的设备也支持其他 PCIe 功能,这些功能消除了内存的锁定要求。
设备 TLB 支持 - 设备在通过地址转换服务 (ATS) 请求使用之前,请求 IOMMU 查找地址。如果映射存在但操作系统未分配页面,则 IOMMU 硬件会返回不存在映射。
设备请求通过页面请求接口 (PRI) 映射虚拟地址。一旦操作系统成功完成映射,它会将响应返回给设备。设备再次请求转换并继续。
IOMMU 与操作系统协同工作,管理设备页表的一致性。在删除页面时,它会与设备交互,以删除可能在从操作系统删除映射之前缓存的任何设备 TLB 条目。
31.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