3. 页表与转换表基础

好,咱们今天聊聊页表。这东西说白了就是 MMU 的「导航地图」。CPU 发出的虚拟地址怎么变成物理地址?全靠页表来翻译。

我记得刚接触 ARMv7 的时候,总觉得页表就是个简单的查表过程。后来踩了坑才发现——页表的结构、粒度、层级关系,每一个细节都直接影响系统性能和稳定性。

3.1 页表的概念

页表(Page Table)是一张映射表。它记录了虚拟地址到物理地址的转换关系。每个条目叫页表项(PTE),里面存着物理页框号、权限位、属性位等信息。

你想想看,如果整个 4GB 地址空间只用一张表来映射,那这张表得有多大?4GB / 4KB = 1M 个条目。每个条目 4 字节,光这一张表就要 4MB。这在嵌入式系统里太奢侈了。

所以 ARMv7 用了多级页表。常见的是两级结构:一级页表 + 二级页表。这样大部分内存不需要映射,页表可以按需分配,省空间。

核心思路: 一级页表做粗粒度索引,二级页表做细粒度映射。只有真正用到的虚拟地址区域才分配二级页表。

3.2 一级页表与二级页表

一级页表也叫 L1 页表。它把 4GB 地址空间分成 4096 个条目,每个条目覆盖 1MB 的范围。L1 页表项可以指向一个 1MB 的段(Section),也可以指向一个二级页表。

二级页表也叫 L2 页表。它把 1MB 的范围再细分成更小的页。根据你选的粒度,可以分成 256 个 4KB 页,或者 64 个 16KB 页,或者 16 个 64KB 页。

我在项目中遇到过一种情况:某个外设的寄存器区域只有 8KB,但用段映射直接占了 1MB 的虚拟地址空间。这太浪费了。后来改成二级页表,用 4KB 粒度映射,只占 8KB 虚拟空间,干净利落。

层级 覆盖范围 条目数 典型用途
L1 页表 4GB 4096 粗粒度索引,段映射
L2 页表 1MB 256 / 64 / 16 细粒度映射,小内存区域

3.3 页表项(PTE)结构

页表项的结构决定了你能控制什么。ARMv7 的 PTE 里包含这些关键字段:

  • 物理页框号(PFN):物理地址的高位部分,决定数据到底存在哪块物理内存里。
  • 访问权限位:AP[2:0],控制读/写/执行权限。我习惯把内核空间设成特权级访问,用户空间只给读和执行。
  • 域(Domain):ARMv7 特有的权限控制机制。每个域可以独立配置访问检查策略。
  • 缓存属性位:C(Cacheable)、B(Bufferable)、TEX[2:0],控制是否缓存、写策略等。
  • 共享属性位:S(Shareable),多核系统里必须注意这个位。设错了会导致缓存一致性问题。
  • XN 位:Execute Never,禁止执行。对付缓冲区溢出攻击的利器。
我的经验: 调试页表问题时,我习惯先检查 AP 位和 Domain 位。很多「莫名其妙」的访问异常,最后查出来都是权限配置错了。

3.4 转换粒度

ARMv7 支持三种页粒度:4KB、16KB、64KB。选哪个?看你的场景。

粒度 L2 条目数 TLB 覆盖范围 适用场景
4KB 256 小 精细控制,外设寄存器,小内存块
16KB 64 中 通用内存映射,平衡粒度与 TLB 效率
64KB 16 大 大块连续内存,减少页表占用

我个人习惯:代码段和数据段用 64KB 粒度,TLB 命中率高。外设寄存器区域用 4KB 粒度,精确控制访问权限。16KB 用得少,除非有特殊需求。

注意: 不同粒度不能混用在同一个 1MB 区域内。一个 L2 页表只能选一种粒度。我曾经因为没注意这个限制,在同一个 1MB 区域里混用了 4KB 和 64KB 页,结果 MMU 直接报错。

3.5 页表结构总览

下面这张图展示了 ARMv7 两级页表的完整转换流程。从虚拟地址出发,经过 L1 索引、L2 索引,最终找到物理地址。

ARMv7 两级页表转换流程 ARMv7 两级页表转换流程 虚拟地址 VA[31:0] L1 索引 [31:20] L2 索引 [19:12] 页内偏移 [11:0] L1 页表 4096 个条目,每个 4 字节 L2 页表 256/64/16 个条目 物理地址 PA[31:0] 关键说明 1. L1 索引:从 VA[31:20] 取出 12 位 2. L2 索引:从 VA[19:12] 取出 8 位(4KB 粒度) 3. 页内偏移:从 VA[11:0] 取出 12 位 4. L1 条目指向 L2 页表基地址 5. L2 条目包含物理页框号 6. 物理地址 = PFN + 页内偏移

嗯,这张图把整个流程串起来了。从虚拟地址出发,先拆成 L1 索引、L2 索引和页内偏移。然后查 L1 页表找到 L2 页表的基地址,再查 L2 页表找到物理页框号,最后加上页内偏移得到物理地址。

3.6 页表配置实战要点

配置页表时,有几个地方容易出问题:

  • 页表对齐:L1 页表必须 16KB 对齐,L2 页表必须 1KB 对齐。不对齐的话 MMU 直接罢工。
  • 页表内存属性:页表本身所在的内存必须配置为 Inner Write-Back, Write-Allocate。否则查表性能会很难看。
  • TLB 维护:修改页表后必须做 TLB 失效操作。我习惯用 DSB + ISB 保证修改生效。
  • 域访问控制:每个域有 2 位控制位,可以设成 No Access、Client、Manager。Manager 模式跳过权限检查,适合内核关键区域。
避坑指南: 我曾经在修改页表后忘了做 TLB 失效,结果新映射一直不生效。调试了两天才发现是 TLB 缓存了旧条目。从那以后,我每次改页表都先 DSB,再 TLBIALL,再 ISB,一步不落。

页表是 MMU 的基石。理解了一级二级结构、PTE 字段含义、粒度选择,后面配置页表、调试异常就顺了。别急,慢慢来,先把这些基础打牢。