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 结构示意图
2.3 多级页表原理:解决页表过大的问题
在 64 位系统中,如果使用单级页表,页表本身将占用巨大的物理内存。例如,虚拟地址空间为 48 位,页大小为 4KB,则页表项数量为 2^36(约 687 亿),每个 PTE 8 字节,页表总大小达 512GB,这显然不可行。多级页表(Hierarchical Page Table)通过分级索引,允许页表按需分配,大幅节省内存。
多级页表的工作机制
- 虚拟地址分解:CPU 将虚拟地址划分为多个索引字段和一个偏移量字段。每个索引字段对应一级页表的索引。
- 逐级查找:从顶级页表(如 PGD)开始,使用第一级索引找到下一级页表的物理地址;再使用第二级索引继续查找,直到最后一级页表(如 PTE)。
- 获取物理地址:最后一级页表项(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) 作为页表项的硬件缓存,缓存最近使用的虚拟地址到物理地址的映射,大幅加速地址转换。
多级页表地址转换流程
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())的必备知识。