3. 分页机制原理

分页机制是Linux内存管理的核心基石。它通过页表(Page Table)这一硬件-软件协同的数据结构,将虚拟地址空间映射到物理地址空间。本章节深入剖析页表的作用、多级页表的结构设计以及页表项中关键标志位的含义。

核心概念:页表是操作系统为每个进程维护的一张“地址转换地图”。CPU发出的虚拟地址通过页表查询,最终转换为物理地址,从而实现对物理内存的隔离、保护和高效利用。

3.1 页表的作用:虚拟地址到物理地址的桥梁

页表的核心作用可以概括为以下三点:

  • 地址转换:将连续的虚拟地址空间映射到离散的物理页框,解决物理内存碎片化问题。
  • 内存保护:通过页表项中的权限位(读/写/执行),实现进程间的地址空间隔离,防止越权访问。
  • 虚拟内存支持:页表项中的“存在位”标识页面是否在物理内存中,若不在则触发缺页异常(Page Fault),由内核从磁盘交换区加载。
关键理解:没有页表,每个进程都能直接访问物理地址,系统将毫无安全性可言。页表是虚拟化技术的基石。

3.2 多级页表结构:PGD / PUD / PMD / PTE

现代64位系统(如x86_64)使用4级页表结构,以解决单级页表占用连续大块物理内存的问题。Linux内核抽象出通用的4级页表模型,具体硬件实现可能有所不同(如ARM64支持3级或4级)。

4级页表的层级与名称如下:

层级 缩写 全称 作用
第1级 PGD Page Global Directory 顶级页表,每个进程一个,指向下一级目录
第2级 PUD Page Upper Directory 上层页目录,进一步细分地址空间
第3级 PMD Page Middle Directory 中间页目录,指向页表
第4级 PTE Page Table Entry 页表项,直接指向物理页框

虚拟地址被CPU硬件解析为多个索引字段,依次遍历这4级页表,最终找到物理页框号(PFN)。

地址转换流程:虚拟地址 → PGD索引 → PUD索引 → PMD索引 → PTE索引 → 物理页框 + 页内偏移 = 物理地址。

下图展示了x86_64架构下4级页表的地址转换过程:

4级页表地址转换流程图 4级页表地址转换流程(x86_64) 虚拟地址(64位) PGD索引 (9位) PUD索引 (9位) PMD索引 (9位) PTE索引 (9位) 页内偏移 (12位) PGD(全局目录) PUD(上层目录) PMD(中间目录) PTE(页表项) 物理页框 物理地址 说明: • 每级页表包含 512 个表项(2^9) • 页大小为 4KB(2^12) • 总虚拟地址空间:2^(9+9+9+9+12) = 2^48 = 256TB

3.3 页表项(PTE)标志位详解

每个页表项(PTE)不仅包含物理页框号(PFN),还包含一组标志位(Flags),用于控制页面的访问权限、缓存策略和状态。以下是Linux内核中常用的PTE标志位:

标志位 名称 含义 典型值
P Present 页面是否在物理内存中。若为0,访问该页会触发缺页异常。 0 / 1
R/W Read/Write 页面是否可写。0表示只读,1表示可读写。 0 / 1
U/S User/Supervisor 页面是否可在用户态访问。0表示仅内核态可访问,1表示用户态也可访问。 0 / 1
A Accessed 页面是否被访问过(读或写)。由硬件自动置位,内核定期检查用于页面置换。 0 / 1
D Dirty 页面是否被写入过。用于判断是否需要回写至磁盘交换区。 0 / 1
NX No-Execute 页面是否禁止执行代码。用于防止缓冲区溢出攻击(DEP/NX保护)。 0 / 1
G Global 页面是否为全局页(如内核空间)。全局页在TLB刷新时不会被清除,提升性能。 0 / 1
PAT Page Attribute Table 页面内存类型(如回写、不可缓存、写合并等),用于控制缓存行为。 0 / 1
安全提示:NX(No-Execute)位是现代操作系统安全机制(如Linux的NX、Windows的DEP)的核心。如果攻击者试图在数据页(如栈或堆)上执行代码,CPU会因NX位为1而拒绝执行,从而阻止缓冲区溢出攻击。

3.4 多级页表的优势与TLB加速

多级页表相比单级页表具有以下显著优势:

  • 节省内存:单级页表需要为整个虚拟地址空间分配连续的页表数组(例如4GB虚拟空间需要4MB连续页表)。多级页表允许按需分配,未使用的地址范围不需要分配中间页表。
  • 按需创建:进程创建时,内核只分配PGD和少量必要的中间页表。随着进程使用更多内存,再逐步分配PUD、PMD和PTE。
  • 共享页表:内核空间的高地址部分(如所有进程共享的内核映射)可以共享同一套页表,进一步节省内存。

为了加速页表查询,CPU内置了TLB(Translation Lookaside Buffer,转译后备缓冲器),即页表的高速缓存。TLB存储最近使用的虚拟地址到物理地址的映射。当CPU访问内存时,首先在TLB中查找,若命中则无需遍历多级页表,大幅提升性能。

性能关键:TLB命中率直接影响系统性能。多级页表虽然增加了单次查询的步骤,但通过TLB缓存,实际开销被大大降低。内核开发者会尽量优化页表布局,以提高TLB命中率。

下图展示了TLB在地址转换中的加速作用:

TLB加速地址转换流程图 TLB加速地址转换流程 CPU 虚拟地址 TLB 命中? 是 物理地址 否 遍历多级页表 回填TLB TLB命中时,直接获得物理地址,无需访问内存中的多级页表,延迟从几十纳秒降至几纳秒。

3.5 内核中的页表操作接口

Linux内核提供了一套宏和函数来操作页表,这些接口在arch/x86/include/asm/pgtable.h等头文件中定义。以下是几个关键操作:

/* 设置页表项 */
set_pte(pte_t *ptep, pte_t pte);

/* 清除页表项 */
pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep);

/* 检查页表项是否存在 */
int pte_present(pte_t pte);

/* 设置页表项为可写 */
pte_t pte_mkwrite(pte_t pte);

/* 设置页表项为用户态可访问 */
pte_t pte_mkuser(pte_t pte);

/* 设置页表项为不可执行(NX) */
pte_t pte_mknexec(pte_t pte);

/* 获取页表项中的物理页框号 */
unsigned long pte_pfn(pte_t pte);
内核编程提示:直接操作页表是内核中最底层的操作之一,通常只在内存管理子系统(如缺页异常处理、页面回收、内存热插拔)中使用。驱动开发者应使用更高级的API(如vmalloc、kmalloc),避免直接修改页表。

3.6 本章小结

分页机制是Linux内存管理的基石,理解页表的结构和标志位是深入内核内存管理的前提。本章要点:

  • 页表实现虚拟地址到物理地址的转换、内存保护和虚拟内存支持。
  • 4级页表(PGD/PUD/PMD/PTE)通过分层索引节省内存,支持按需分配。
  • PTE标志位(P、R/W、U/S、A、D、NX、G、PAT)控制页面的状态和权限。
  • TLB作为页表的高速缓存,是提升地址转换性能的关键硬件组件。