2、IOMMU核心概念:地址空间与翻译、I/O页表、TLB与IOTLB、ATS基础

好,咱们正式开始聊IOMMU的核心概念。说实话,这部分内容你要是能啃下来,后面理解SMMU、PCIe ATS什么的就会轻松很多。我当年刚接触IOMMU时,最头疼的就是这些概念之间的关联——地址翻译、页表、TLB,每个单独看都懂,但串起来就懵了。今天咱们就把它们彻底理清楚。

2.1 地址空间与翻译:从“两个世界”说起

先问一个问题:为什么需要IOMMU做地址翻译?

你想想看,CPU有MMU,进程看到的是虚拟地址,跑得挺开心。但DMA设备呢?它直接访问物理地址。这就出问题了——设备不知道虚拟地址是什么,它只认物理地址。那如果操作系统想给设备一个不连续的物理内存区域,或者想做内存保护,怎么办?

嗯,这就是IOMMU登场的原因。它给设备也造了一个“虚拟地址空间”,我们叫它I/O虚拟地址(IOVA)。设备看到的是IOVA,IOMMU负责把它翻译成真正的物理地址。

核心要点:IOMMU的本质,就是给DMA设备配了一个“专属MMU”。CPU有页表,IOMMU也有自己的页表——I/O页表。

我在项目中遇到过这样一个场景:一个网卡驱动分配了DMA缓冲区,但物理内存碎片化严重,导致大块连续物理内存分配失败。后来用了IOMMU,驱动只需要分配IOVA连续的地址空间,IOMMU自动映射到分散的物理页框。问题一下子就解决了。

地址翻译的过程,说白了就是三步:

  1. 设备发起DMA请求,携带IOVA地址
  2. IOMMU截获请求,查找I/O页表
  3. 输出物理地址,同时检查访问权限

这里要注意,IOMMU的翻译粒度通常是4KB页,但也支持2MB、1GB的大页。我个人习惯在需要大块DMA缓冲区的场景下,优先使用大页映射——能显著减少TLB miss。

2.2 I/O页表:不只是“抄作业”

I/O页表的结构,和CPU页表非常相似。以ARM SMMU为例,它支持VMSA(Virtual Memory System Architecture)页表格式,和CPU的页表格式完全兼容。这意味着什么?

意味着操作系统可以复用同一套页表管理代码。但别高兴太早——I/O页表有自己的特殊之处。

个人经验:我曾经在一个项目中,直接让IOMMU共享了进程的CPU页表。结果发现设备访问了不该访问的内核空间,系统直接panic。后来才意识到,I/O页表需要更严格的权限控制——设备不应该有内核空间的访问权限,除非你明确知道自己在做什么。

I/O页表的核心字段包括:

字段 说明 我的建议
物理页框号 映射的目标物理地址 注意对齐要求,4KB页必须4KB对齐
访问权限 读、写、执行控制 设备通常只需要读/写,别乱开执行权限
缓存属性 是否可缓存、一致性控制 这个容易踩坑,后面细说
共享属性 多核系统中的缓存一致性 非一致性设备要格外小心

页表级数也是个关键点。ARM SMMU支持1~4级页表,级数越多,能映射的地址空间越大,但翻译延迟也越高。我一般建议:如果设备只需要小范围DMA,用2级页表就够了;需要64位地址空间的,才考虑3级或4级。

2.3 TLB与IOTLB:缓存是性能的命根子

每次DMA都查一遍页表?那性能就完蛋了。所以IOMMU内部也有TLB,专门叫IOTLB(I/O Translation Lookaside Buffer)

IOTLB的工作原理和CPU TLB几乎一样:缓存最近使用的地址翻译结果。但有一个关键区别——IOTLB是软件管理的。什么意思?

CPU TLB在页表切换时,硬件会自动做TLB flush。但IOTLB不行,操作系统必须显式地告诉IOMMU:“嘿,这个映射失效了,把对应的IOTLB条目清掉。”

避坑指南:我曾经在调试一个NVMe驱动时,发现DMA数据总是读到旧数据。查了两天才发现,是IOTLB没有在页表更新后及时失效。从那以后,我每次修改I/O页表,都会强制调用IOMMU的TLB invalidation命令。记住:页表更新后,一定要做IOTLB失效,否则设备可能读到缓存中的旧翻译结果。

IOTLB的失效粒度也有讲究:

  • 全局失效:清空所有IOTLB条目,简单粗暴,但性能差
  • ASID-based失效:只失效某个地址空间ID的条目,更精细
  • VA-based失效:只失效某个虚拟地址的条目,最精确

我个人习惯在驱动卸载或DMA缓冲区释放时,使用全局失效;在动态映射更新时,尽量用VA-based失效,减少对其他设备的影响。

2.4 ATS基础:让设备自己“问路”

ATS(Address Translation Services)是PCIe规范中的一个可选特性。它的核心思想很简单:让设备自己向IOMMU请求地址翻译,而不是每次DMA都让IOMMU做翻译。

你想想看,如果没有ATS,设备每次DMA都要经过IOMMU翻译。如果设备频繁访问同一块内存,IOMMU就成了瓶颈。有了ATS,设备可以先把IOVA翻译成物理地址,然后缓存到设备内部的设备TLB(Device TLB)中。后续DMA直接使用物理地址,绕过IOMMU。

ATS的工作流程大致是这样的:

  1. 设备发送ATS请求(Translation Request)给IOMMU
  2. IOMMU返回翻译结果(Translation Completion)
  3. 设备缓存结果到Device TLB
  4. 后续DMA直接使用物理地址

注意:ATS是一把双刃剑。好处是减少了IOMMU的翻译压力,提升了性能。坏处是——如果页表更新了,设备TLB里的缓存可能过期。这时候需要IOMMU发送ATS Invalidation请求,通知设备清掉缓存。

我在项目中遇到过ATS相关的坑:一个支持ATS的网卡,在虚拟化环境下频繁出现DMA错误。排查后发现,是VMM在迁移虚拟机时更新了I/O页表,但没有发送ATS Invalidation给设备。设备TLB里还缓存着旧的物理地址,一DMA就写到了错误的内存位置。

所以我的建议是:如果设备支持ATS,一定要确保IOMMU驱动正确处理了ATS Invalidation。特别是在虚拟化热迁移、内存气球等动态调整内存的场景下,ATS缓存失效是必须处理的。

2.5 本章小结:一张图看懂核心概念

说了这么多,咱们用一张图把核心逻辑串起来:

IOMMU核心概念关系图 PCIe设备 设备TLB (ATS缓存) IOMMU IOTLB I/O页表 物理内存 ATS请求 ATS响应 DMA (IOVA) 图例说明 PCIe设备:发起DMA请求 IOMMU:地址翻译与权限检查 I/O页表:存储地址映射关系 物理内存:DMA最终访问的目标 ATS流程:设备主动请求翻译 DMA流程:设备发送IOVA请求

这张图把咱们今天讲的核心概念都串起来了。设备发起DMA请求,IOMMU查IOTLB,没命中就去查I/O页表,翻译出物理地址后访问内存。如果设备支持ATS,它还可以主动请求翻译,把结果缓存到设备TLB里。

嗯,这一章的内容就到这里。记住:地址空间是基础,页表是核心,TLB是性能关键,ATS是进阶优化。把这四个概念吃透了,IOMMU的原理你就掌握了七成。


公众号:蓝海资料掘金营,微信deep3321