2. 分页机制原理:页框与页的概念、页表的作用、多级页表结构(x86_64 4级页表)

分页(Paging)是现代操作系统内存管理的核心机制,它将物理内存划分为固定大小的页框(Page Frame),并将进程的虚拟地址空间划分为相同大小的页(Page)。通过页表(Page Table)这一关键数据结构,CPU 能够将虚拟地址动态转换为物理地址,从而实现隔离、共享和按需加载。

2.1 页框与页的概念

在分页系统中,物理内存被分割成大小相等的块,称为页框(也称物理页)。每个页框是物理内存分配的最小单位。相应地,进程的虚拟地址空间也被分割成与页框大小相同的块,称为页(也称虚拟页)。

术语 所属空间 大小 说明
页框(Page Frame) 物理内存 通常 4KB 物理内存的固定大小分区,是内存分配的实际载体
页(Page) 虚拟地址空间 通常 4KB 虚拟地址空间的固定大小分区,与页框一一映射
页偏移(Offset) 页内 12 位(4KB) 用于在页内定位具体的字节地址
关键理解:页框是物理内存的“槽位”,页是虚拟内存的“容器”。分页机制的核心任务就是将虚拟页映射到物理页框上。页框大小通常为 4KB,但在大内存场景下,x86_64 也支持 2MB 和 1GB 的大页(Huge Pages),以减少页表层级和 TLB 缺失。

2.2 页表的作用

页表是存储在内存中的一种数据结构,用于记录虚拟页到物理页框的映射关系。每个进程拥有独立的页表,从而实现进程间的地址空间隔离。页表的核心作用包括:

  • 地址转换:将虚拟地址中的页号转换为物理页框号,结合页内偏移得到最终物理地址。
  • 权限控制:每个页表项(PTE)包含读、写、执行、用户/内核等权限位,实现细粒度的访问控制。
  • 按需分页:通过“存在位”(Present Bit)标记页是否在物理内存中,支持缺页异常和页面换入换出。
  • 共享内存:多个进程的页表项可以指向同一个物理页框,实现高效的数据共享。
页表项(PTE)关键字段:
  • Present (P): 页是否在物理内存中。
  • Read/Write (R/W): 是否可写。
  • User/Supervisor (U/S): 用户态是否可访问。
  • Dirty (D): 页是否被写入过。
  • Accessed (A): 页是否被访问过。
  • Page Frame Number (PFN): 物理页框号。

2.3 多级页表结构(x86_64 4级页表)

在 64 位系统中,如果使用单级页表,页表本身将占用巨大的物理内存(例如,48 位虚拟地址空间需要 2^36 个页表项,每个 8 字节,共 512GB)。为了解决这个问题,x86_64 架构采用了多级页表结构,通过分级索引来压缩页表体积,并支持按需分配中间级页表。

x86_64 在传统模式下使用 4 级页表,将虚拟地址划分为 5 个部分:

  • PML4(Page Map Level 4): 第 1 级索引,9 位,指向 PML4 表项。
  • PDPT(Page Directory Pointer Table): 第 2 级索引,9 位,指向页目录指针表项。
  • PD(Page Directory): 第 3 级索引,9 位,指向页目录表项。
  • PT(Page Table): 第 4 级索引,9 位,指向页表项。
  • Offset: 页内偏移,12 位(4KB 页)。

虚拟地址总长度为 48 位(高 16 位为符号扩展),结构如下:

| 63      48 | 47      39 | 38      30 | 29      21 | 20      12 | 11       0 |
|  符号扩展   |   PML4    |   PDPT    |    PD     |    PT     |   Offset  |
|   (16位)   |   (9位)   |   (9位)   |   (9位)   |   (9位)   |   (12位)  |
注意:虽然 x86_64 架构支持 64 位虚拟地址,但当前硬件实现仅使用低 48 位,高 16 位必须为第 47 位的符号扩展(全 0 或全 1),否则 CPU 会抛出异常。这形成了用户空间(0x0000000000000000 - 0x00007FFFFFFFFFFF)和内核空间(0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF)的经典划分。

4 级页表地址转换流程

当 CPU 访问一个虚拟地址时,MMU(内存管理单元)执行以下步骤:

  1. 从控制寄存器 CR3 中获取当前进程的 PML4 表基地址。
  2. 使用虚拟地址的 PML4 索引(位 47:39)在 PML4 表中查找对应的 PML4 表项,获取 PDPT 表的基地址。
  3. 使用 PDPT 索引(位 38:30)在 PDPT 表中查找对应的 PDPT 表项,获取 PD 表的基地址。
  4. 使用 PD 索引(位 29:21)在 PD 表中查找对应的 PD 表项,获取 PT 表的基地址。
  5. 使用 PT 索引(位 20:12)在 PT 表中查找对应的 PT 表项,获取物理页框号(PFN)。
  6. 将 PFN 与 Offset(位 11:0)拼接,形成最终的物理地址。
性能优化:多级页表虽然增加了地址转换的步骤,但通过 TLB(Translation Lookaside Buffer)缓存最近使用的页表项,可以大幅减少实际的内存访问次数。此外,Linux 内核在 4 级页表基础上,还支持 5 级页表(用于 57 位虚拟地址空间)和多种大页映射,以适应不同场景的性能需求。

4 级页表地址转换流程图

x86_64 4级页表地址转换流程 x86_64 4级页表地址转换流程 虚拟地址 (48位) 符号扩展 PML4 (9位) PDPT (9位) PD (9位) PT (9位) Offset (12位) CR3 寄存器 PML4 表 (512 项) PDPT 表 (512 项) PD 表 (512 项) PT 表 (512 项) 物理页框 物理地址

2.4 多级页表的优势与权衡

优势 说明
节省内存 只有实际使用的虚拟地址范围才需要分配中间级页表,未使用的区域无需分配,大幅降低页表占用的物理内存。
按需分配 进程创建时只需分配 PML4 表,其他级页表在缺页时动态分配,减少初始化开销。
支持大地址空间 4 级页表可覆盖 48 位虚拟地址空间(256TB),5 级页表可覆盖 57 位(128PB)。
灵活的权限控制 每级页表项均可设置权限位,支持细粒度的内存保护。
权衡:多级页表增加了地址转换的延迟(每次转换需要 4 次内存访问),但 TLB 缓存可以显著缓解这一问题。此外,TLB 缺失时的页表遍历开销也高于单级页表。Linux 内核通过大页映射(如 2MB 透明大页)来减少页表层级,从而降低 TLB 缺失率。

2.5 Linux 内核中的页表管理

Linux 内核在 x86_64 上实现了对 4 级页表的完整支持,并提供了统一的页表操作接口。内核通过 pgd、p4d、pud、pmd、pte 等类型来抽象不同级别的页表项,使得代码可以跨架构复用。

// Linux 内核页表层级类型(x86_64 4级页表)
typedef struct { pgdval_t pgd; } pgd_t;   // PML4 表项
typedef struct { p4dval_t p4d; } p4d_t;   // 第4级(x86_64 上未使用)
typedef struct { pudval_t pud; } pud_t;   // PDPT 表项
typedef struct { pmdval_t pmd; } pmd_t;   // PD 表项
typedef struct { pteval_t pte; } pte_t;   // PT 表项

// 页表遍历宏示例
#define pgd_offset(mm, addr)    ((mm)->pgd + pgd_index(addr))
#define p4d_offset(pgd, addr)   p4d_offset(pgd, addr)
#define pud_offset(p4d, addr)   pud_offset(p4d, addr)
#define pmd_offset(pud, addr)   pmd_offset(pud, addr)
#define pte_offset_kernel(pmd, addr) pte_offset_kernel(pmd, addr)
内核开发者注意:在遍历用户空间页表时,必须使用 mmap_sem(或 mmap_lock)进行保护,防止页表在遍历过程中被并发修改。此外,直接修改页表项后,需要调用 flush_tlb_page 或 flush_tlb_mm 来刷新 TLB,否则 CPU 可能使用过期的缓存映射。

2.6 本章小结

  • 页框与页是分页机制的基本单位,页框属于物理内存,页属于虚拟地址空间。
  • 页表是地址转换的核心数据结构,记录虚拟页到物理页框的映射关系,并包含权限和控制位。
  • 多级页表(x86_64 4级页表)通过分级索引压缩页表体积,支持大地址空间和按需分配,是 Linux 内核内存管理的基石。
  • 理解页表结构是后续学习 TLB 刷新优化、大页映射、KSM(内核同页合并)等高级主题的前提。