将晦涩难懂的技术讲的通俗易懂
分类: LINUX
2025-03-16 00:46:48
RDMA为什么要Pin内存?
首先,明确我想问的问题是什么?正如题目所说,使用RDMA得时候为什么要pin内存?当然仅仅回答这个问题很简单,因为网卡要直接内存访问(DMA),RDMA网卡的MTT(Memory Translation Table)需要进行地址翻译,准确的说是虚拟地址(VA)到物理地址的翻译(PA),而pin内存是为了不让这个地址映射关系发生变化,否则RDMA要访问虚拟地址A,本来对应的是物理地址a,如果不pin内存,这个时候操作系统把物理地址a进行swap,然后物理地址a又被另外一个进程拿去用了,这个时候RDMA再去访问虚拟地址A,经过MTT转换还是往a这个物理地址写,那就写了其他进程的内存,乱套了。。。
但这只是问题的开始。我接下来想问的是非RDMA场景(例如普通TCP)应用也有DMA操作,为什么不用pin内存?什么?你说内核帮忙进行pin了,那基于DPDK用户态的高性能协议栈可以不pin吗?我们平时写程序用的都是虚拟内存,都要经过MMU地址翻译成物理内存,怎么我们写普通程序就不用pin内存?另外,我们知道普通网卡DMA的时候也需要地址翻译,这是通过IOMMU进行的,那RDMA为什么还要搞个MTT翻译,为什么不直接用IOMMU?下面开始逐个展开。
IOMMU是系统级的硬件单元,负责保护主机内存免受DMA攻击(如恶意设备直接访问非授权内存区域),并提供虚拟地址到物理地址的转换(VA→PA)的。所以这里IOMMU有两个功能,内存保护和地址翻译。前者是检查DMA目的地址的合法性,比如如果没有IOMMU,程序直接使用物理地址(PA)进行DMA,如果这个程序被攻击者控制,传入的是系统的其他物理地址,那么DMA就会读取或写入这个地址了,进而产生攻击。又例如,在虚拟化场景下,一个VM控制网卡DMA写入的物理地址对应的是另一个VM的内存,也会产生DMA攻击。对于后者则类似CPU的MMU,MMU是VA到PA的翻译,IOMMU则实现IOVA到PA的翻译。
关于IOMMU的几个常见问题:
Q:IOMMU物理硬件位于哪个位置?
A:在CPU package的uncore部分,或者说我们常说的RC中;
Q:一个CPU有几个IOMMU单元?
A:有多个,这个与物理root port是1:1的关系,即每个CPU的物理PCI口都有一个独立的IOMMU单元
Q:一个IOMMU单元与PCI设备的管理关系是什么?
A:每个IOMMU单元负责root port下挂载的所有设备,包括switch,PF,以及PF生成的所有VF
MTT(Memory Translation Table)是RDMA硬件(如Mellanox InfiniBand网卡)内部的专用机制,用于将RDMA的虚拟内存地址(例如QP的虚拟地址)映射到物理内存页。所以同样是IO地址翻译,为什么不直接用IOMMU呢。这要从以IOMMU和MTT下几个方面的不同说起:
MTT和IOMMU的位置不同
如前所述,IOMMU是位于CPU RC中,每次地址反应是要经过CPU RC的,所以普通使用IOMMU的DMA不需要CPU参与,只是不需要CPU的计算core参与,但是还是要经过CPU uncore的,是root port下发的设备共享的,而MTT是位于网卡设备上的,因此MTT的访问会更加高效。虽然有些高级网卡可以支持ATS,但是也存在cache miss的情况。因此位置不同决定了MTT的访问性能更好;
通用和专用
IOMMU是设备通用的地址转换,而MTT是RDMA网卡内部的专用表结构,由驱动直接管理(绕过操作系统),支持更快的地址转换,而且不同网卡厂商可以针对RDMA场景定制优化。
举个例子,例如MTT 是位于主机内存(DDR)上的,如果硬件每次做地址转换都到主机内存查询一遍表,肯定会影响数据传输的效率。所以实际方案中还需要考虑一些机制来解决这种问题,比如有些网卡(如海思)引入了MR HEM,来先让硬件快速找到MR对应的数据结构,然后根据MR查找MTT,将数据缓存的前两个内存页的物理地址保存到MR HEM 表的对象中,如果应用程序要求传输的数据量比较少,都位于前两个内存页,就可以直接从MR HEM 表的对象中获取内存页的物理地址,从而简化查询MTT表的步骤。另外还可以将{BANNED}最佳近使用的MR HEM/MTT 表中的表项暂存到硬件内部,避免每次都到DDR 中查表。
此外,IOMMU通过Domain ID(如PCIe PASID)隔离设备内存访问,但无法感知RDMA的MR粒度。MTT提供了对每个MR的精确控制,避免IOMMU的过度泛化。
对于RDMA来说有了MTT是不是就不需要IOMMU了呢?要看什么场景,比如在物理机没有虚拟化的时候,确实使用RDMA也没必要开启IOMMU。但是在虚拟化场景,MTT虽然完成了地址翻译,但是还是需要IOMMU进行内存校验的,以确保MTT翻译的地址在合法范围内,避免DMA到其他虚拟机的内存。
再稍微延伸一点。GPUDirect包括 GPUDirect Storage、GPUDirect RDMA、GPUDirect P2P。以GPUDirect P2P 为例,使用后将数据从源 GPU 复制到同一节点中的另一个 GPU 不再需要将数据临时暂存到主机内存中。
上面提到IOMMU是集成在RC中的一个部件,当设备访问主机内存时,它将设备访问时使用的地址转换为主机内存的地址,在虚拟化场景下可以将设备访问的GPA转换为HPA。而PCIe P2P是两个设备的直接通信,自然不在经过RC,也就无法经过IOMMU的地址翻译。这就导致了安全隐患。为了解决这个问题,就需要开启ACS(Access Control Services)特性,ACS是为了增强I/O虚拟化安全性而引入的,开启ACS也会强制PCIe P2P绕行RC。
无论是ACS的开启,还是IOMMU,都需要绕行RC, 安全是解决了,可是性能呢?例如GPUDirect RDMA如果每次都绕RC,性能完全没有优势。因此有一种方式可以借助MTT实现类似IOMMU GPA-HPA的翻译,同时通过ACS DT(Direct Translated ) P2P实现PCIe switch对已翻译DMA请求的直接转发来解决绕行RC的问题。当然GPU间的GPUDirect P2P也有相关方案,这个有机会再展开。
RDMA要求应用程序在使用内存前显式注册内存区域(Memory Region, MR),此时驱动会:
a. Pin内存:锁定物理内存,阻止操作系统换出或移动。
b. 构建地址映射表(如MTT):将虚拟地址映射到物理地址,供网卡直接访问。
为什么要pin内存其实再文章的{BANNED}最佳开头已经提到了。主要是为了地址翻译。只要涉及IO地址翻译,物理是IOMMU还是MTT都需要pin内存。那么普通TCP需要吗?这要分两个场景:
● 内核协议栈
传统TCP通信通过操作系统内核协议栈处理数据:用户态数据 → 内核态Socket Buffer → 网卡DMA缓冲区。内核自身负责分配和固定用于DMA的内核缓冲区(如sk_buff),用户空间内存无需pin,因为数据会被复制到内核的固定区域。而内核网卡驱动分配出的内存本身就是物理地址固定的。并且通过内核的DMA映射接口(如dma_map_single())临时建立DMA映射,完成后立即解除映射。
● 用户态协议栈
如果是用户态协议栈,那用户态协议栈本身要不要pin内存呢。一般用户态协议栈都基于DPDK,而DPDK一般使用hugepage,hugepage是不支持swap的,因此不显式pin也是没有问题的。那如果就是用普通的匿名内存呢?这种方式还是需要pin的,否则可能导致IOMMU缺页。
接着上文,因为要地址翻译就要pin内存。那CPU{BANNED}最佳常用的MMU解决进程VA到PA的翻译怎么不需要进程都pin内存,MMU缺页是很正常的现象。这是因为内核处理了MMU的缺页中断,在其中进行了页面的重映射。而IOMMU一般不支持动态缺页处理,当设备尝试访问未映射的虚拟地址时,IOMMU不会触发类似CPU的缺页中断,而是直接导致DMA错误(如PCIe错误或系统崩溃)。
不过也有一些新硬件设备支持触发的缺页机制(如ARM SMMUv3的STALL模型),允许IOMMU暂停DMA操作并通知操作系统重新映射内存,或者一些intel新CPU支持再IOMMU缺页时产生MMU-notifier提供内核处理,然而,当然这些是依赖硬件支持的。