4. 多级页表引入:为什么需要多级页表、节省内存的原理、层级结构概览

在单级页表(线性页表)中,虚拟地址空间有多大,页表就需要预留多大的连续物理内存来存放映射条目。对于 32 位系统(4 GB 虚拟空间、4 KB 页),单级页表需要约 4 MB 的连续物理内存;对于 64 位系统,这个数字会膨胀到无法接受的程度。多级页表正是为了解决这一根本矛盾而设计的。

4.1 为什么需要多级页表

单级页表的核心缺陷在于:必须为整个虚拟地址空间预先分配页表空间,即使其中绝大部分虚拟地址从未被使用。多级页表通过引入层级索引,实现了按需分配页表中间节点和叶子节点,从而大幅降低内存占用。

对比维度 单级页表(线性页表) 多级页表(以二级为例)
页表大小 固定 4 MB(32 位,4 KB 页) 仅分配使用的部分,通常远小于 4 MB
连续性要求 需要连续物理内存 各级页表可分散在物理内存中
进程空闲时开销 仍然占用全部页表空间 几乎不占用页表空间
硬件 MMU 支持 简单,但扩展性差 需要 TLB 和多级遍历逻辑
核心洞察: 多级页表的核心思想是“用时间换空间”——通过增加地址翻译的层级数(增加访存次数),换取页表内存占用的指数级下降。

4.2 节省内存的原理

多级页表节省内存的原理可以概括为三点:

  1. 按需分配中间节点: 只有当一个虚拟地址区间内至少有一个有效页时,才会分配对应的页目录项和页表项。对于大段未映射的地址空间,对应的页目录项被标记为无效,其下的整个页表都不需要分配。
  2. 共享大页支持: 多级结构天然支持大页(如 2 MB、1 GB),只需在较高级别设置一个映射项,即可覆盖大块连续内存,减少页表项数量。
  3. 进程间共享: 多个进程可以共享同一份页表中间节点(如内核空间的页目录),避免重复分配。
典型场景: 一个 32 位进程只使用了 4 MB 代码段和 4 MB 数据段,其余地址空间未映射。单级页表仍需 4 MB 连续内存;二级页表仅需 1 个页目录(4 KB)+ 2 个页表(各 4 KB)= 12 KB,节省超过 99%。

4.3 层级结构概览

多级页表将虚拟地址划分为多个位段,每一段对应一级页表的索引。以 ARMv8-A 的 4 级页表(4 KB 页,48 位虚拟地址)为例:

虚拟地址 (48 位):
+--------+--------+--------+--------+--------+
| 页全局 | 页上级 | 页中间 | 页表   | 页内   |
| 目录   | 目录   | 目录   |        | 偏移   |
| (L0)   | (L1)   | (L2)   | (L3)   |        |
| 9 位   | 9 位   | 9 位   | 9 位   | 12 位  |
+--------+--------+--------+--------+--------+

每一级页表都是一个包含 512 个表项(9 位索引,2^9 = 512)的物理页框,每个表项 8 字节。翻译过程从 L0 开始,逐级向下,直到 L3 找到物理页框号,最后加上页内偏移得到物理地址。

注意: 不同架构的层级数和位段划分可能不同。x86-64 使用 4 级页表(PML4、PDPT、PD、PT),ARMv8-A 也使用 4 级(L0~L3),但 RISC-V Sv39 使用 3 级。适配硬件 MMU 时必须严格遵循目标架构的页表格式。

4.4 多级页表地址翻译流程(二级示例)

下图展示了一个二级页表(10 位页目录 + 10 位页表 + 12 位页内偏移)的地址翻译过程:

二级页表地址翻译流程 二级页表地址翻译流程(10-10-12 划分) 虚拟地址 (32 位) 页目录索引 (10 位) 页表索引 (10 位) 页内偏移 (12 位) 页目录 (L1) 1024 项 × 4 字节 页表 (L2) 1024 项 × 4 字节 物理页框 (4 KB) 基址 + 页内偏移 页内偏移直接拼接 物理地址 (32 位) 翻译步骤 1. 从虚拟地址提取 页目录索引 2. 查找页目录项 3. 从页目录项获取 页表基址

4.5 各级页表的职责与粒度

以 ARMv8-A 的 4 级页表为例,各级页表的覆盖范围和粒度如下:

页表级别 索引位数 表项数 覆盖虚拟地址范围 映射粒度
L0(页全局目录) 9 位 512 512 × 512 GB = 256 TB 512 GB 块
L1(页上级目录) 9 位 512 512 × 1 GB = 512 GB 1 GB 块
L2(页中间目录) 9 位 512 512 × 2 MB = 1 GB 2 MB 块
L3(页表) 9 位 512 512 × 4 KB = 2 MB 4 KB 页
关键点: 在 L1 或 L2 级别设置块映射(Block Entry)可以跳过后续级别,实现大页映射。这既减少了页表层级遍历次数,也降低了 TLB 压力。ARM MMU 在 L1 支持 1 GB 块,在 L2 支持 2 MB 块。

4.6 多级页表与硬件 MMU 的兼容性要点

多级页表的设计必须与硬件 MMU 的遍历逻辑严格匹配。以下是适配 ARM MMU 时的关键注意事项:

  • 页表基址寄存器: ARMv8-A 使用 TTBR0_EL1(用户空间)和 TTBR1_EL1(内核空间)存放 L0 页表的物理基址。
  • 表项格式: 每个页表项的低位包含类型位(Table/Block/Page)、访问权限位、属性位等。MMU 根据这些位决定是否继续遍历或直接返回物理地址。
  • 粒度大小: ARM 支持 4 KB、16 KB、64 KB 三种页粒度,不同粒度对应不同的页表层级数和位段划分。操作系统必须与 MMU 配置一致。
  • TLB 一致性: 修改页表后必须执行 TLB 失效操作(如 TLBI VMALLE1),否则 MMU 可能使用过时的缓存翻译结果。
实践建议: 在移植操作系统或编写裸机 MMU 初始化代码时,先查阅目标 ARM 核的《Technical Reference Manual》中关于页表格式和遍历算法的章节,再编写页表构建代码。错误的页表项格式会导致 MMU 触发同步异常(Translation Fault)。