第3章:页表基础理论——层级结构与PTE格式详解

页表,说白了就是一张地址翻译的“地图”。CPU发出来的虚拟地址,怎么找到物理内存里的真实位置?全靠这张地图。但现代系统动辄48位、52位地址空间,一张扁平的表根本装不下——你想想看,4级页表每级512项,总共能覆盖512^4=68亿个页,这才够用。

我刚开始接触SMMU时,总觉得页表层级越多越麻烦。后来调一个IO虚拟化性能问题,才发现多级页表其实是为了省内存——大部分页表项是空的,没必要全分配。嗯,这里要记住一个核心原则:按需分配,延迟映射。

3.1 页表层级结构(L0/L1/L2/L3)

ARMv8-A架构下,SMMU和CPU共享同一套页表格式。典型配置是4级页表:

层级 索引位宽 页表大小 映射粒度
L0 Bits[47:39] 4KB (512项) 1TB块
L1 Bits[38:30] 4KB (512项) 2MB块 / 下一级
L2 Bits[29:21] 4KB (512项) 4KB页 / 下一级
L3 Bits[20:12] 4KB (512项) 4KB页

为什么是512项?因为每个页表项占8字节,4KB/8=512。这个数字很巧妙——刚好一个物理页能装下一整级页表。

关键理解:L0和L1通常只分配少量项,L2和L3才真正密集。我见过一个项目,L0页表只用了2项,L1用了4项,但L2/L3分配了上千项。这就是“稀疏映射”的典型场景。

翻译过程是这样的:

  1. 从TTBR(Translation Table Base Register)拿到L0页表基地址
  2. 用虚拟地址[47:39]索引L0,拿到L1页表基地址
  3. 用[38:30]索引L1,拿到L2页表基地址
  4. 用[29:21]索引L2,拿到L3页表基地址
  5. 用[20:12]索引L3,拿到物理页帧号
  6. 最后加上[11:0]页内偏移,得到完整物理地址

每一步都可能提前终止——如果某级页表项标记为“块映射”(比如L1的2MB块),就不需要继续往下走了。这叫大页映射,性能好,TLB利用率高。

实战技巧:我调试SMMU性能问题时,第一件事就是检查页表是否用了大页。曾经有个NVMe SSD性能不达标,查了半天发现是4KB小页映射,改成2MB大页后IOPS直接翻倍。TLB miss少了,DMA效率自然就上来了。

3.2 页表项(PTE)格式详解

每个PTE是64位,但真正有用的位段需要仔细理解。我画了一张图,把关键字段标出来了:

ARMv8-A 64位页表项(PTE)格式 位[63:48] — 物理地址高16位 / 属性扩展 位[47:12] — 物理页帧号(PFN),36位 位[11:2] — 内存属性:SH[2位], AP[2位], AttrIndx[3位], NS, XN, PXN 位[1:0] — 描述符类型:00=无效, 01=表, 10=块, 11=页 关键位段说明 SH=共享属性 | AP=访问权限 | AttrIndx=内存属性索引 | XN=不可执行 | PXN=特权不可执行

每个字段都有它的脾气。我挑几个容易踩坑的讲讲:

3.3 关键位段深度解析

位[1:0] — 描述符类型:这是PTE的“身份证”。00表示无效项,硬件会直接触发缺页异常。01是表描述符(指向下一级页表),10是块描述符(大页),11是页描述符(4KB页)。

我曾经踩过的坑:有一次SMMU报Translation fault,查了半天发现是L2页表项的类型位写成了0b01(表),但实际应该写0b11(页)。硬件以为还有下一级,结果下一级页表根本没分配,直接崩了。记住:最后一级必须是页或块,不能是表。

位[11:2] — 内存属性:这里包含共享属性(SH)、访问权限(AP)、内存属性索引(AttrIndx)等。共享属性决定是否多核一致,AP决定读写权限,AttrIndx指向MAIR(Memory Attribute Indirection Register)中的配置。

我习惯把MAIR配置成:

MAIR0: 0x44  — 正常内存,写回,读分配
MAIR1: 0x04  — 设备内存,非缓存
MAIR2: 0xFF  — 正常内存,写通

然后AttrIndx=0对应MAIR0,AttrIndx=1对应MAIR1,以此类推。这样配置清晰,调试时一眼就能看出问题。

位[47:12] — 物理页帧号:这是翻译结果的核心。36位PFN加上12位页内偏移,组成48位物理地址。注意:PFN必须对齐到页边界——如果写了一个非对齐的PFN,硬件行为是未定义的。我见过有人把PFN写成0x1001,结果地址翻译出来永远错一位。

3.4 页表配置实战清单

配置页表时,我建议按这个顺序检查:

  1. TTBR配置:基地址必须64字节对齐,ASID和VMID是否正确
  2. 页表层级:TCR中的T0SZ/T1SZ决定地址宽度,确保和页表层级匹配
  3. PTE类型:最后一级必须是页或块,中间级不能是页
  4. 权限位:AP[2:1]不能同时为0(无效权限),否则访问会fault
  5. 共享属性:非共享内存做DMA,cache一致性会出问题

避坑指南:我曾经调试一个GPU驱动,SMMU总是报权限错误。查了三天,发现是AP位写反了——AP[2:1]=0b10表示只读,但驱动写成了0b01(只写)。硬件读操作自然就fault了。所以配置完页表,一定要先做一次读测试,再做一次写测试,确认权限双向正确。

3.5 页表与TLB的协作

页表是“慢速路径”,TLB是“快速缓存”。每次地址翻译,硬件先查TLB,miss了才走页表。页表配置错了,TLB miss后翻译出错误地址,数据就写到别人家里去了。

我调试时常用一个技巧:先关TLB缓存(把TCR的EPD置1),强制走页表。这样能快速定位是页表配置问题还是TLB刷新问题。等页表验证通过,再开TLB缓存测性能。