人人都会AI编程

6.2 分页与页表:内存分页、MMU 地址翻译、多级页表

更新时间:2026-07-12

现代操作系统普遍采用分页机制来管理物理内存,实现进程间的地址隔离和虚拟内存。Linux 也不例外,它将每个进程的地址空间划分为固定大小的页,通过硬件内存管理单元与内核共同维护的页表,完成虚拟地址到物理地址的高效转换。

1. 为什么需要分页

早期的计算机使用段式管理,容易产生外部碎片,且不利于内存的保护与共享。分页将物理内存视为一组大小相等、连续编号的页框,同时将进程的虚拟地址空间也切分为同样大小的页。在 64 位 x86 架构上,Linux 通常使用 4KB 的基本页大小。这样,内存分配的基本单位被标准化,外部碎片不复存在,内核只需维护一张映射表,就能灵活地将任意虚拟页映射到任意物理页框,甚至暂时映射到磁盘(交换空间),实现内存的超卖和按需加载。

2. MMU 与地址翻译过程

内存管理单元(MMU,Memory Management Unit) 是 CPU 内部用于执行地址翻译的硬件部件。每当进程访问一个虚拟地址,MMU 都会自动将虚拟地址拆分为两部分:虚拟页号页内偏移。然后,MMU 利用页号去查找页表,找到对应的物理页框号,将物理页框号与页内偏移拼接,得到真正的物理地址,再向系统总线发出内存访问请求。整个翻译过程对用户态程序完全透明。

以 x86-64 的 4KB 分页为例,假设虚拟地址为 0x7ffe1a3c5000,其低 12 位为页内偏移(0x000),高位为虚拟页号。MMU 会从页表根寄存器(CR3)指向的顶层页表开始,逐级查找该虚拟页号对应的物理页框号。如果该页不存在或权限不足,MMU 将触发缺页异常,交由内核处理;若翻译成功,则直接生成物理地址访问内存。

为了加速地址翻译,现代 CPU 内部还集成了 转译后备缓冲器(TLB,Translation Lookaside Buffer)。TLB 是一个高速缓存,用于存储最近使用过的虚拟页号到物理页框号的映射。绝大部分地址翻译都能在 TLB 中直接命中,从而避免频繁访问内存中的多级页表,显著降低翻译开销。

3. 多级页表的设计动机

如果为每个进程完整保存所有可能的虚拟页映射,页表会极其庞大。一个 64 位进程的虚拟地址空间理论上有(2^64 / 4096 ≈ 4 亿亿)个条目,根本不可能全部存储。但实际进程使用的地址空间非常稀疏——堆在较低地址、栈在较高地址、中间是大片未使用区域。多级页表正是利用这种稀疏性节省内存。

多级页表将页表本身也拆分成多个层级,每个层级都由一个物理页框存储。

  • 最顶层(如 x86-64 的 PML4,即 4 级页表映射)仅包含指向下一级页表的指针,下一级页表(页目录指针、页目录等)再逐级指向更细分的表,直到最后的页表项才指向实际的数据页。
  • 如果某一级页表的全部条目都表示“未映射”,那么这一整页的页表就不需要分配物理内存,其父级条目会被标记为不存在。这样,对于一个只使用了堆、栈和少量库映射的进程而言,实际占用的页表内存只需要几十 KB,而不是巨量的连续结构。
  • 页表项中除了物理页框号,还包含丰富的权限位:存在位(Valid)、读/写位、用户/超级用户位、执行位、缓存模式位等。当进程试图越权访问(例如写只读页),MMU 会立即产生保护故障,从而强制实施内存保护。

4. 实际查看与调试

在系统中,可以通过 /proc/<pid>/maps 查看进程的虚拟内存区域及其权限:

cat /proc/self/maps

可以通过 /proc/<pid>/pagemap 和内核的 page-types 工具了解具体页的物理映射关系(需要 root 权限)。此外,即便硬件支持透明大页(2MB 或 1GB 页),Linux 也利用中间级页表项直接指向大页的基地址,以减少 TLB 条目占用并提升大型数据集的访问性能。

理解分页与多级页表的工作原理,对于排查内存泄漏、分析性能瓶颈(如 TLB 抖动)、理解共享内存与 copy-on-write 机制都具有基础性的指导意义。这种设计用极小的页面内开销换来了保护、隔离和弹性内存管理,是现代虚拟内存系统的支柱。