4、I/O页表格式:Stage-1翻译(VA->IPA)、Stage-2翻译(IPA->PA)、混合翻译模式、页表属性与权限控制

好,咱们今天来聊聊I/O页表的具体格式。说实话,这部分内容我第一次接触时也觉得头大——页表层级、翻译模式、属性位,一堆概念搅在一起。但后来我意识到,只要抓住一个核心问题:设备发起的DMA请求,到底是怎么一步步找到物理内存的?搞明白这个,剩下的都是细节。

4.1 为什么需要两阶段翻译?

先说说背景。在虚拟化场景下,虚拟机看到的地址空间是IPA(Intermediate Physical Address),不是真实的PA。设备要访问内存,得先知道这个IPA对应哪个PA。这就引出了两阶段翻译:

  • Stage-1:把设备发出的VA(虚拟地址)翻译成IPA。这个阶段由Guest OS的IOMMU驱动来管理。
  • Stage-2:把IPA翻译成真实的PA。这个阶段由Hypervisor/VMM来管理。

你想想看,如果没有Stage-2,虚拟机里的设备驱动直接操作物理地址,那整个系统就乱套了。我在一个ARM虚拟化项目中就遇到过这种情况——虚拟机里的NVMe驱动直接写死了物理地址,结果迁移后内存布局变了,数据全写到了别人的地盘上。嗯,从那以后我对两阶段翻译格外上心。

4.2 Stage-1翻译:VA到IPA

Stage-1的页表格式,说白了和CPU的页表非常像。以ARM SMMU为例,它支持多种页表粒度:4KB、16KB、64KB。我个人习惯用4KB粒度,兼容性最好。

页表层级通常是3级或4级。举个例子,4级页表的翻译过程:

VA[63:48] -> Level 0 Table (PGD)
VA[47:39] -> Level 1 Table (PUD)
VA[38:30] -> Level 2 Table (PMD)
VA[29:21] -> Level 3 Table (PTE)
VA[20:0]  -> Page Offset

每一级页表项(Descriptor)都包含指向下一级表的基地址,以及一些控制位。到了最后一级,页表项里存的就是IPA的基地址了。

关键点:Stage-1翻译出来的IPA,对设备来说是透明的。设备只知道自己发出了VA,IOMMU负责把它变成IPA。至于这个IPA怎么变成PA,那是Stage-2的事。

4.3 Stage-2翻译:IPA到PA

Stage-2的页表结构和Stage-1类似,但多了几个重要的控制位。我记得第一次看SMMU spec时,被Stage-2的权限模型绕晕了——它不仅要管读写权限,还要管访问类型(比如是否允许缓存)。

Stage-2页表项的关键字段:

字段说明
PA[47:12]物理地址的高36位,低12位是页内偏移
S2AP[1:0]Stage-2访问权限:00=无权限,01=只读,10=只写,11=读写
MemAttr[3:0]内存属性,比如是否可缓存、是否可共享
XN禁止执行(对I/O设备来说,通常用不到)

个人经验:配置Stage-2页表时,MemAttr字段特别容易踩坑。我曾经在一个项目中,设备DMA总是报错,查了两天才发现是Stage-2页表把内存属性设成了不可缓存,导致一致性协议出了问题。后来我养成了一个习惯:Stage-2的MemAttr一定要和Stage-1保持一致,除非你明确知道自己在做什么。

4.4 混合翻译模式

混合翻译模式,说白了就是Stage-1和Stage-2同时生效。设备发出VA,经过Stage-1变成IPA,再经过Stage-2变成PA。这种模式在虚拟化场景下是标配。

但这里有个坑:性能开销。每次DMA都要查两次页表,TLB miss的概率翻倍。我见过一些性能敏感的场景,比如高速网卡,混合翻译模式会导致吞吐量下降10%~20%。

怎么优化?

  • 使用大页:比如2MB或1GB的页,减少页表层级,降低TLB miss率。
  • 开启TLB预取:有些SMMU支持硬件预取,能提前加载页表项。
  • 考虑直通模式:如果设备不需要虚拟化,可以绕过Stage-2,直接用Stage-1翻译到PA。

注意:混合翻译模式下,Stage-1和Stage-2的权限控制是叠加的。设备要访问某块内存,必须同时通过两阶段的权限检查。比如Stage-1允许读,但Stage-2禁止读,那设备还是读不了。这个逻辑有点像防火墙的规则——最严格的规则说了算。

4.5 页表属性与权限控制

页表属性这块,我重点说说几个容易混淆的点。

首先是访问权限。Stage-1和Stage-2都有自己的AP(Access Permission)位。Stage-1的AP控制设备对IPA的访问,Stage-2的AP控制IPA到PA的映射权限。两者必须配合好。

其次是内存属性。包括:

  • Cacheability:是否允许缓存。对DMA来说,通常建议开启缓存,除非设备有特殊的一致性要求。
  • Shareability:是否允许其他核或设备共享。非共享模式下,每个核有自己的缓存副本,容易产生一致性问题。
  • Device属性:有些内存区域被标记为Device类型,比如MMIO寄存器。这种区域不允许缓存,也不允许推测访问。

我举个例子。有一次调试一个GPU驱动,发现DMA写入的数据总是被CPU读到旧值。查了半天,原来是页表把GPU的DMA缓冲区设成了Non-Cacheable,但CPU侧用的是Cacheable。两边不一致,导致数据不同步。解决方案很简单:把DMA缓冲区的页表属性改成Cacheable,并加上共享属性。

避坑指南:我曾经在配置页表属性时,把Device类型的内存误设成了Normal类型。结果设备访问MMIO寄存器时,SMMU做了缓存和重排,导致寄存器读写顺序错乱。嗯,从那以后我每次配置页表都会再三确认内存类型。

4.6 页表格式的SVG结构图

下面这张图展示了完整的I/O页表翻译流程。我画得比较简洁,但核心逻辑都在里面了。

I/O页表翻译流程 设备 VA Stage-1 翻译 VA → IPA IPA Stage-2 翻译 IPA → PA PA 内存 页表层级结构 Level 0 (PGD) Level 1 (PUD) Level 2 (PMD) Level 3 (PTE) 页表项(Descriptor)关键字段 • 基地址:指向下一级页表或最终物理地址 • AP[1:0]:访问权限(读/写/执行) • MemAttr[3:0]:内存属性(缓存/共享/设备类型) • XN:禁止执行位 • NS:安全位(非安全空间 vs 安全空间) 混合翻译模式:Stage-1 + Stage-2 同时生效,设备VA经过两次翻译到达物理内存

4.7 小结

这一章的内容比较多,但核心就三件事:

  • Stage-1:VA到IPA,由Guest OS管理。
  • Stage-2:IPA到PA,由Hypervisor管理。
  • 混合模式:两者叠加,权限取交集,属性要一致。

页表属性这块,我建议你多花点时间理解内存类型和权限控制。我在实际项目中见过太多因为属性配置不当导致的问题——数据不一致、性能下降、甚至系统崩溃。嗯,这些坑我都踩过,希望你能绕过去。


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