2. 分页机制详解:页框与页的概念、页表项(PTE)结构、多级页表原理

分页(Paging)是现代操作系统内存管理的核心机制,也是缺页异常(Page Fault)能够被触发和处理的基础。理解分页机制,是深入分析缺页异常处理流程的前提。本节将系统阐述页框与页的概念、页表项(PTE)的详细结构以及多级页表的原理。

2.1 页框与页:物理内存与虚拟内存的划分

分页机制将物理内存和虚拟内存划分为固定大小的块:

  • 页框(Page Frame):物理内存被划分为大小相等的块,每个块称为一个页框(或物理页)。页框是物理内存分配的最小单位。
  • 页(Page):虚拟地址空间(进程看到的地址空间)被划分为与页框大小相等的块,每个块称为一个页(或虚拟页)。

页框和页的大小由硬件架构决定,在 x86 架构中通常为 4KB(也可支持 2MB/1GB 大页)。操作系统负责建立虚拟页到物理页框的映射关系,这种映射关系存储在页表中。

核心概念: 页框是物理内存的“格子”,页是虚拟地址空间的“格子”。缺页异常发生在 CPU 试图访问一个虚拟页,但该页尚未映射到物理页框时。

页框与页的对比

特性 页框(Page Frame) 页(Page)
所属空间 物理内存(RAM) 虚拟地址空间
大小 固定(如 4KB) 与页框大小一致
分配者 物理内存分配器(如 Buddy System) 虚拟内存区域(VMA)
状态 空闲、已分配、保留 未映射、已映射、换出
缺页关联 缺页时分配新的页框 缺页时建立映射或换入数据

2.2 页表项(PTE)结构:映射的基石

页表项(Page Table Entry, PTE)是页表中的基本单元,每个 PTE 描述一个虚拟页到物理页框的映射关系,并包含控制访问权限和状态的标志位。PTE 的结构因架构而异,但核心字段具有共性。

典型 PTE 字段解析(以 x86-64 架构为例)

位域 名称 描述
63:12 物理页框号(PFN) 物理地址的高 52 位(4KB 页框对齐),指向物理页框的基地址。
11:9 忽略/可用 供操作系统使用(如存储页的访问计数或 NUMA 信息)。
8 G(Global) 全局页标志。若置位,该页在 TLB 刷新时不会被清除(常用于内核空间映射)。
7 PAT(Page Attribute Table) 页属性表索引,用于控制内存缓存策略(如 Write-Through、Write-Back)。
6 D(Dirty) 脏位。若置位,表示该页已被写入过数据。操作系统在换出页时需检查此位。
5 A(Accessed) 访问位。若置位,表示该页已被读取或写入过。操作系统用于页面置换算法(如 LRU 近似)。
4 PCD(Page Cache Disable) 页缓存禁用位。置位时,禁止对该页进行硬件缓存。
3 PWT(Page Write-Through) 页写通位。置位时,对该页的写入操作采用写通策略。
2 U/S(User/Supervisor) 用户/超级用户位。决定该页是否可在用户态访问(0=仅内核,1=用户和内核)。
1 R/W(Read/Write) 读写位。决定该页是否可写(0=只读,1=可读写)。
0 P(Present) 存在位。核心标志!若为 0,表示该页不在物理内存中,访问将触发缺页异常。
关键提示: 缺页异常处理程序首先检查 PTE 的 Present 位(P 位)。如果 P 位为 0,则进一步判断是“未映射”还是“已换出”。如果 P 位为 1,但访问权限(U/S、R/W)不匹配,则触发保护错误(Protection Fault)。

PTE 结构示意图

x86-64 页表项(PTE)结构(4KB 页) 物理页框号(PFN)[位 63:12] G PAT D A PCD PWT U/S 忽略 / 可用(位 11:9) R/W P P 位(Present)为 0 时触发缺页异常;D 位(Dirty)和 A 位(Accessed)由硬件自动设置。

2.3 多级页表原理:解决页表过大的问题

在 64 位系统中,如果使用单级页表,页表本身将占用巨大的物理内存。例如,虚拟地址空间为 48 位,页大小为 4KB,则页表项数量为 2^36(约 687 亿),每个 PTE 8 字节,页表总大小达 512GB,这显然不可行。多级页表(Hierarchical Page Table)通过分级索引,允许页表按需分配,大幅节省内存。

多级页表的工作机制

  1. 虚拟地址分解:CPU 将虚拟地址划分为多个索引字段和一个偏移量字段。每个索引字段对应一级页表的索引。
  2. 逐级查找:从顶级页表(如 PGD)开始,使用第一级索引找到下一级页表的物理地址;再使用第二级索引继续查找,直到最后一级页表(如 PTE)。
  3. 获取物理地址:最后一级页表项(PTE)中包含物理页框号(PFN),将其与虚拟地址中的偏移量组合,得到最终的物理地址。

x86-64 四级页表结构

级别 名称 索引位数 页表大小 描述
L4 PML4(Page Map Level 4) 9 位 512 项 × 8 字节 = 4KB 顶级页表,每个进程一个。
L3 PDPT(Page Directory Pointer Table) 9 位 512 项 × 8 字节 = 4KB 指向页目录表。
L2 PD(Page Directory) 9 位 512 项 × 8 字节 = 4KB 指向页表。
L1 PT(Page Table) 9 位 512 项 × 8 字节 = 4KB 包含最终的 PTE,指向物理页框。
注意: 多级页表虽然节省了内存,但增加了地址转换的延迟(每次访问内存都需要多次查表)。为此,CPU 引入了 TLB(Translation Lookaside Buffer) 作为页表项的硬件缓存,缓存最近使用的虚拟地址到物理地址的映射,大幅加速地址转换。

多级页表地址转换流程

x86-64 四级页表地址转换流程 虚拟地址(48 位) PML4 索引(9 位) PDPT 索引(9 位) PD 索引(9 位) PT 索引(9 位) 页内偏移(12 位) PML4 表 PDPT 表 PD 表 PT 表 物理页框 转换过程:CR3 寄存器指向 PML4 表基址 → 用 PML4 索引找到 PDPT 表 → 用 PDPT 索引找到 PD 表 → 用 PD 索引找到 PT 表 → 用 PT 索引找到 PTE → PTE 中的 PFN + 页内偏移 = 物理地址 如果任意一级页表项不存在或 P 位为 0,则触发缺页异常(Page Fault)。

2.4 多级页表的优势与缺页关联

  • 按需分配:只有实际使用的虚拟地址范围才需要分配页表。例如,一个进程只使用了 0-4MB 的虚拟地址,则只需分配 PML4 表项和少量下级页表,而不是整个 512GB 的页表。
  • 共享页表:多个进程可以共享相同的页表项(如内核空间映射),进一步节省内存。
  • 缺页触发点:在逐级查找页表的过程中,如果遇到任何一级页表项不存在(P 位为 0),CPU 会立即触发缺页异常。缺页异常处理程序根据缺失的级别和地址,决定是分配新的页表、分配物理页框还是从磁盘换入数据。
缺页异常与多级页表的关系: 多级页表的结构决定了缺页异常可以在多个层级发生。例如,如果 PML4 表项存在但 PDPT 表项不存在,则缺页异常处理程序需要先分配 PDPT 表,再分配后续的页表。Linux 内核通过 handle_mm_fault() 函数处理这种逐级缺页。

2.5 总结

分页机制通过页框和页的划分,实现了虚拟地址到物理地址的灵活映射。页表项(PTE)中的 Present 位、Dirty 位和 Accessed 位是缺页异常处理的核心依据。多级页表解决了页表过大的问题,但也使得地址转换过程更加复杂,缺页异常可能发生在页表查找的任何一级。理解这些基础概念,是后续分析缺页异常处理流程(如 do_page_fault()、handle_mm_fault())的必备知识。