当前位置:数码通 > 动态

DPDK内存管理的IOMMU和IOVA技术总结

来源于 数码通 2023-10-05 15:00

之前写过DPDK相关内存管理的代码分析,但是随着DPDK版本的迭代,内存管理也在不断演变。这里结合一些参考文章,对DPDK的内存使用情况以及开发变化做一个阶段性的总结。

大页面

DPDK通常使用大页内存。无论是2M大页还是1G大页,本质上都是通过更大的页大小来减少TLB未命中,提高TLB命中率,而TLB就是用来缓存页表缓存的。

DMA

我们知道,计算机设备,比如网卡硬件,无法处理用户空间的虚拟地址(只有CPU可以通过页表转换MMU来识别虚拟地址),因为它无法感知到任何虚拟地址。用户空间 进程和分配给它的用户空间虚拟地址。相反,它只能访问真实物理地址的内存。

出于效率原因,现代硬件几乎总是使用直接内存访问 (DMA) 事务。通常,为了执行 DMA 事务,内核需要参与创建支持 DMA 的存储区域、将进程内虚拟地址转换为硬件可以理解的真实物理地址,并启动 DMA 事务。这就是大多数现代操作系统中输入和输出的工作方式;然而,这是一个耗时的过程,需要上下文切换、转换和查找操作,不利于高性能的输入/输出。

DPDK的内存管理以一种简单的方式解决了这个问题。每当 DPDK 可以使用某个内存区域时,DPDK 就会通过询问内核来计算其物理地址。即DPDK维护了虚拟地址和物理地址的映射关系。由于DPDK使用了加锁(如vfio_pin_map_dma)内存(防止物理内存和虚拟内存的映射关系改变),使得底层内存区域的物理地址不会改变,所以硬件即使内存本身有一段时间没有被使用,也可以依赖这些物理地址始终有效。然后,DPDK 在准备由硬件完成的输入/输出事务时使用这些物理地址,并以允许硬件自行启动 DMA 事务的方式配置硬件。这使得 DPDK 能够避免不必要的开销并完全从用户空间执行输入/输出。

IOMMU 和 IOVA

默认情况下,任何硬件都可以访问整个系统的物理内存,因此它可以在任何地方执行 DMA 事务。这有很多安全隐患。例如,流氓和/或不受信任的进程(包括在VM(虚拟机)内运行的进程)可以使用硬件设备来读取和写入内核空间以及实际上任何其他存储位置。为了解决这个问题,现代系统配备了输入输出内存管理单元(IOMMU)。这是一种硬件设备,提供 DMA 地址转换和设备隔离功能,以便仅允许特定设备与特定内存区域(由 IOMMU 指定)执行 DMA 事务,并且无法访问指定之外的系统内存地址空间使用权。

由于IOMMU的参与,硬件使用的物理地址可能不是真正的物理地址,而是IOMMU分配给硬件的(完全任意的)输入输出虚拟地址(IOVA)。一般来说,DPDK 社区可以互换使用物理地址和 IOVA 这两个术语,但根据上下文,两者之间的区别可能很重要。例如,DPDK 17.11 和更新的 DPDK 长期支持 (LTS) 版本在某些情况下可能根本不使用实际物理地址,而是使用用户空间虚拟地址来实现 DMA。 IOMMU 负责地址转换,因此硬件永远不会注意到差异。

如上图所示,P1和P2进程分别使用虚拟地址P进行DMA。由于IOMMU将虚拟地址P映射到不同的物理地址,因此不存在冲突,P3访问的地址P在IOMMU中没有映射。关系,所以DMA访问会导致失败。

IO 虚拟地址 (IOVA) 模式

DPDK是一个用户模式应用程序框架。使用DPDK的软件可以像其他软件一样使用常规虚拟地址。但除此之外,DPDK还提供了用户态PMD和一组API来完全在用户态下实现IO操作。前面提到,硬件无法识别用户空间虚拟地址;它使用 IO 地址 - 物理地址 (PA) 或 IO 虚拟地址 (IOVA)。

DPDK API 不区分物理地址和 IO 虚拟地址。即使VA部分不是由IO内存管理单元(IOMMU)提供的,IOVA也用来表示这两个地址。不过DPDK会区分物理地址作为IOVA的情况和用户空间虚拟地址作为IOVA的情况。它们在DPDK API中被称为IOVA模式,可以分为两种类型:PA为IOVA模式,VA为IOVA模式。

PA AS IOVA 模式

PA as IOVA模式下,分配给整个DPDK存储区域的IOVA地址都是实际的物理地址,虚拟内存的分配与物理内存的分配相匹配。这种模式的一大优点是简单:它可以在所有硬件上工作(即不需要 IOMMU),并且可以在内核空间中工作(将真实物理地址转换为内核空间地址的开销微不足道) 。事实上,这就是 DPDK 长期以来的运作方式,并且在很多方面它被认为是默认选项。

然而,IOVA模型作为PA也存在一些缺点。一是它需要root用户权限——无法访问系统的页面映射(因为DPDK维护着虚拟地址和物理地址IOVA的对应关系),DPDK无法获取内存区域的真实物理地址。因此,如果系统中没有 root 权限,则无法在此模式下运行。

PA 作为 IOVA 模式

PA 作为 IOVA 的一种模式还有一个值得一提的限制 - 虚拟内存分配必须遵循物理内存分配。这意味着,如果物理内存空间是分段的(分为许多小段而不是几个大段),那么虚拟内存空间也必须遵循相同的分段。在极端情况下,可能会分割得太严重,导致划分出太多物理上连续的碎片,耗尽DPDK用于存储这些碎片相关信息的内部数据结构,导致DPDK初始化失败。

IOVA 模式下 PA 分段作为 PA 的示例。

针对这些问题,DPDK社区提出了解决方案。例如,减少碎片影响的一种方法是使用更大的分页 - 问题并未解决,但单独的 1 GB 段的性能比单独的 2 兆字节 (MB) 段更好。大幅减少段数。另一种广泛使用的解决方案是引导系统并在启动时而不是运行时保留大页面。但以上方案都不能从根本上解决问题,整个DPDK社区都在用来解决这些问题。每个DPDK用户(有意无意)在使用时都会采取相同的思维模式——“我需要X MB内存,但我保留X + Y MB以防万一!”

VA AS IOVA 模式

相比之下,VA作为IOVA模型不需要遵循底层物理内存的分布。相反,会重新分配物理内存以匹配虚拟内存的分配。 DPDK EAL 依靠内核基础设施来实现这一点。内核基础设施又使用 IOMMU 来重新映射物理内存。

IOVA 模式为 VA。

这种方法的优点很明显:在VA as IOVA模式下,所有内存都是VA和IOVA连续的。这意味着所有需要大量 IOVA 连续内存的内存分配更有可能成功,因为对于硬件而言,即使底层物理内存可能不存在,内存也会显示为 IOVA 连续。由于重新映射,IOVA 空间碎片问题变得无关紧要。无论物理内存的分段程度如何,它始终可以重新映射为 IOVA 连续的内存块。

以VA的IOVA模式下的分割为例。

VA作为IOVA的一种模式还有另一个优点,它不需要任何权限。这是因为它不需要访问系统页面映射。这允许 DPDK 以非 root 用户身份运行,并且可以更轻松地在不欢迎特权访问的环境(例如云​​原生环境)中使用 DPDK。

当然,IOVA模型作为VA也有缺点。由于各种原因,有时可能无法选择使用 IOMMU。这可能包括:

1)硬件不支持IOMMU;

2)平台本身可能没有IOMMU(例如,没有模拟的IOMMU VM);

3)软件设备(例如DPDK的Kernel NetworkInterface (KNI)PMD)不支持IOVA模式作为VA;

4)某些IOMMU(通常是模拟IOMMU)可能具有有限的地址宽度,虽然这并不妨碍IOVA模式用作VA,但限制了其有效性;

5) 在Linux*以外的操作系统上使用DPDK;

然而,这些情况仍然比较罕见。大多数情况下,VA可以正常工作为IOVA模式。

IOVA模式选择

在很多情况下,DPDK默认使用IOVA模式作为PA,因为从硬件角度来看这是最安全的模式。所有给定的硬件(或软件)PMD 至少保证支持 IOVA 模式作为 PA。尽管如此,如果条件允许,强烈建议所有DPDK用户使用IOVA模式作为VA。毕竟这种模式有着不可否认的优势。

但是,用户不必选择其中之一。自动检测最合适的IOVA模式,默认选项绝对适合大多数情况,因此用户无需做出此选择。如果默认选项不合适,用户可以使用 --iova-mode EAL 命令行 参数 尝试使用 EAL 标志(适用于 DPDK 17.11 及更高版本)而不是 IOVA 模式:

1./app --iova-mode=pa # 使用 IOVA 作为 PA 模式

2./app --iova-mode=va # 使用 IOVA 作为 VA 模式

在大多数情况下,VA 和 PA 模式并不相互排斥,可以使用任一模式,但在某些情况下,PA 作为 IOVA 的模式是唯一可用的选项。当无法使用 IOVA 作为 VA 模式时,即使 EAL 参数要求使用 IOVA 作为 VA 模式,DPDK 也会自动切换到 IOVA 作为 PA 模式。 DPDK 还提供了一个 API 来查询运行时使用的 IOVA 模式,但通常不会在用户应用程序中使用,因为只有 DPDK PMD 和总线驱动程序会询问此信息。

UIO 和 VFIO 支持 IOVA

lIgb_uio

DPDK 代码库中最古老的内核驱动程序是 igb_uio 驱动程序。该驱动自DPDK开发初期就已存在,因此是DPDK开发者使用最广泛、最熟悉的驱动。

该驱动程序依赖于内核用户空间 IO (UIO) 基础设施进行操作,并为所有中断类型(INT、MessageMessageSignal中断 (MSI) 和 MSI-X)提供支持,并创建虚拟中断功能。它还通过 /dev/uio 文件系统公开硬件设备注册和中断句柄,然后由 DPDK EAL 使用它们将它们映射到用户空间并使它们可供 DPDK PMD 使用。

gb_uio驱动程序非常简单并且没有做太多事情,因此它不支持使用IOMMU。或者更确切地说,它确实支持 IOMMU,但仅在传输模式下,它在 IOVA 和物理内存地址之间创建 1:1 映射。 igb_uio 不支持使用完整的 IOMMU 模式。因此,igb_uio 驱动程序仅支持 PA 作为 IOVA 模式,并且在 IOVA 作为 VA 模式 中根本不起作用。

内核中提供了类似于 igb_uio 的驱动程序:uio_pci_generic。它的工作原理与 igb_uio 非常相似,只是它的功能更有限。例如,igb_uio 支持所有中断类型(传统、MSI 和 MSI -X),而 uio_pci_generic 仅支持传统中断。更重要的是,igb_uio可以创建虚函数(Virtual Function,VF),而uio_pci_generic则不能;因此,如果您在使用DPDK物理功能(PF)驱动程序时创建VF,这是必需的步骤,igb_uio是唯一的选择。

因此,在大多数情况下,igb_uio 与 uio_pci_generic 相同或更优。有关使用 IOMMU 的所有限制同样适用于 igb_uio 和 uio_pci_generic 驱动程序 - 它们无法使用完整的 IOMMU 功能,因此仅支持 IOVA 作为 PA 模式。

lvfio

vfio-pci 驱动程序是虚拟功能 I/O (VFIO) 内核基础结构的一部分,在 Linux 版本 3.6 中引入。 VFIO 使设备 寄存器 和设备中断可供用户空间应用程序使用,并且 IOMMU 可用于设置 IOVA 映射以从用户空间执行 IO。后一部分至关重要,该驱动程序是专门为与 IOMMU 一起使用而开发的,在较旧的内核上,如果不启用 IOMMU,它甚至无法工作。

与直觉相反,使用 VFIO 驱动程序的 允许将 PA 用作 IOVA ,并将 VA 用作 IOVA 模式 。这是因为,虽然建议使用 IOVA 作为 VA 模式以利用该模式的所有优势,但没有什么可以阻止 DPDK 将 IOMMU 映射的 EAL 设置为遵循物理内存布局 1:1;毕竟 IOVA 映射是任意的。在这种情况下,即使使用IOMMU,DPDK也可以在IOVA中以PA模式工作,从而允许DPDK KNI等工作。但是,使用 IOVA 作为 PA 模式仍然需要 root 权限。

在较新的内核(4.5+,向后移植到某些旧版本)上,有一个enable_unsafe_noiommu_mode选项,允许在没有IOMMU的情况下使用VFIO。此模式的所有意图和目的与基于 UIO 的驱动程序相同,并且具有相同的优点和限制。

17.11及更早版本

任何使用 DPDK 库版本 17.11 或更早版本的应用程序都必须提前了解其内存要求。这是因为,对于这些版本的 DPDK,无法在初始化后请求额外的大页内存或将其释放回系统。因此,DPDK 应用程序可能使用的任何内存都必须在应用程序初始化时保留,并在应用程序的整个生命周期中由 DPDK 保留。

在决定保留多少内存时,留出一些空间通常是个好主意。一些 DPDK 内存将在各种内部分配中被“浪费”,其数量将根据您的配置(DPDK 将使用的设备数量、启用的功能等)而有所不同。

此外,DPDK17.11 中的大多数 API 都需要大量 IOVA 连续内存。这是因为在 DPDK 17.11 中,虚拟内存布局始终与物理内存布局匹配。换句话说,如果将PA用作IOVA,则要求PA是连续的。这是DPDK17.11内存管理中众所周知的问题之一:很少有应用程序实际上需要PA连续内存,并且分配大量内存可能会因缺乏足够的IOVA连续内存而失败。

IOVA模式对比

当然,上述限制仅适用于 IOVA 作为物理地址 (PA) 模式,因为在该模式下,DPDK 的虚拟地址 (VA) 空间遵循 PA 空间的布局。在 PA 模式 IOVA 中,可用 IOVA 连续内存的数量取决于 DPDK 控制之外的许多因素。虽然DPDK会尝试保留尽可能多的IOVA连续内存,但根据可用内存量和系统配置,可能没有足够的IOVA。满足所有分配的连续内存。

在 VA 模式 IOVA 中,这不是问题,因为在这种情况下,IOVA 空间布局将与 VA 空间的布局匹配(而不是相反),并且所有物理内存都将重新映射为与硬件连续的 IOVA。

18.11及以上版本

动态内存管理

DPDK 18.11最大的变化是能够在运行时增加和减少内存使用量,从而消除了DPDK内存映射是静态的情况,带来了很多可用性的改进。

在 DPDK 17.11 中,运行不带任何环境抽象层 (EAL) 参数的 DPDK 应用程序会保留所有可用的大页内存供其使用,并且不会为其他应用程序或其他 DPDK 实例留下任何大页内存。 DPDK18.11的情况有所不同,DPDK只保留应用程序运行所需的内存量。从这个意义上说,DPDK 现在性能更好,并且需要更少的工作来使 DPDK 与其他应用程序良好配合。

同样,应用程序的内存需求不再需要提前知道,DPDK的内存映射可以动态增加和减少,因此DPDK内存子系统可以根据需要自动增加其内存使用量,并在不再需要时将内存返回给系统。这意味着部署 DPDK 应用程序所需的工作量更少,因为现在 DPDK 可以自行管理其内存需求。
DPDK 18.11 和 IOVA 连续性

DPDK 18.11 的基本背景变化之一是虚拟地址 (VA) 连续内存不保证是 IOVA 连续的;两者之间不再有任何关联。 在 VA as IOVA 模式下,IOVA 布局仍然遵循之前的 VA 布局,但在 PA as IOVA 模式下,PA 布局是任意的(物理页向后映射的情况并不少见)

DPDK 版本之间的 IOVA(PA 模式)布局比较

这一变化并不像看起来那么具有破坏性,因为实际上并没有多少数据结构需要 IOVA 连续内存。所有软件数据结构(环、内存池、哈希表等)只需要VA连续内存,不关心底层物理内存布局。这使得大多数使用早期 DPDK 版本的用户应用程序可以无缝过渡到新版本,而无需更改代码。

尽管如此,某些数据结构确实需要 IOVA 连续内存(例如硬件队列结构),并且对于这些情况引入了新的分配器标志。使用此标志,您可以使 memzone 分配器尝试分配 IOVA 连续内存。

单个文件段

较旧的 DPDK 版本在 Hugetlbfs 文件系统中为每个大页存储一个文件,这适用于大多数用例,但有时会导致问题,特别是 vhost-user 后端的 Virtio 会与后端共享文件,并且有一个硬性限制可共享文件描述符的数量。当使用大页面(例如 1 GB 页面)时,它工作得很好,但使用较小的页面大小时,文件数量可能很快就会超过文件描述符限制。

为了解决这个问题,18.11版本中引入了一种新模式,单文件段模式,该模式通过--single-file-segmentsEAL命令行标志启用,这使得EAL在hugetlbfs中创建更少的文件,并使Virtio具有vhost-user即使页面大小最小,后端也能正常工作。

-->
登录后参与评论