第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分配了上千项。这就是“稀疏映射”的典型场景。
翻译过程是这样的:
- 从TTBR(Translation Table Base Register)拿到L0页表基地址
- 用虚拟地址[47:39]索引L0,拿到L1页表基地址
- 用[38:30]索引L1,拿到L2页表基地址
- 用[29:21]索引L2,拿到L3页表基地址
- 用[20:12]索引L3,拿到物理页帧号
- 最后加上[11:0]页内偏移,得到完整物理地址
每一步都可能提前终止——如果某级页表项标记为“块映射”(比如L1的2MB块),就不需要继续往下走了。这叫大页映射,性能好,TLB利用率高。
实战技巧:我调试SMMU性能问题时,第一件事就是检查页表是否用了大页。曾经有个NVMe SSD性能不达标,查了半天发现是4KB小页映射,改成2MB大页后IOPS直接翻倍。TLB miss少了,DMA效率自然就上来了。
3.2 页表项(PTE)格式详解
每个PTE是64位,但真正有用的位段需要仔细理解。我画了一张图,把关键字段标出来了:
每个字段都有它的脾气。我挑几个容易踩坑的讲讲:
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 页表配置实战清单
配置页表时,我建议按这个顺序检查:
- TTBR配置:基地址必须64字节对齐,ASID和VMID是否正确
- 页表层级:TCR中的T0SZ/T1SZ决定地址宽度,确保和页表层级匹配
- PTE类型:最后一级必须是页或块,中间级不能是页
- 权限位:AP[2:1]不能同时为0(无效权限),否则访问会fault
- 共享属性:非共享内存做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缓存测性能。