1. 页表基础概念

页表是操作系统内存管理子系统的核心数据结构,负责将进程看到的连续虚拟地址空间映射到物理内存中离散的物理页帧上。没有页表,多进程隔离、按需分页、共享内存、写时复制等现代 OS 特性将无法实现。

核心认知:页表是虚拟内存系统的“翻译引擎”——每次 CPU 发出的虚拟地址都必须经过页表查询才能得到真实的物理地址。页表配置错误或 TLB 缺失频繁,会直接导致性能悬崖。

1.1 页表的作用

页表承担以下三项关键职责:

  1. 地址翻译:将虚拟页号(VPN)转换为物理页帧号(PFN),加上页内偏移得到完整物理地址。
  2. 权限控制:每个页表项(PTE)记录读/写/执行权限,阻止用户态访问内核空间。
  3. 状态管理:标记页面是否在内存中(有效位)、是否被修改(脏位)、是否被访问(访问位),支撑页面置换和写回策略。
页表核心作用与对应硬件/OS 机制
作用 硬件支撑 操作系统行为
地址翻译 MMU + TLB 建立/销毁页表映射
权限控制 PTE 中的 U/S、R/W、NX 位 设置页表项权限位
状态管理 PTE 中的 Present、Dirty、Accessed 位 缺页处理、页面换出、写回
页表在地址翻译中的位置 CPU 发出虚拟地址 MMU 查询 TLB / 页表 物理内存 读取/写入数据 页表 存储 VPN→PFN 映射 TLB 命中时 MMU 直接使用缓存结果;TLB 缺失时 MMU 必须遍历页表(硬件或软件方式) 页表存储在物理内存中,由操作系统维护,MMU 通过页表基址寄存器(如 CR3)找到页表入口

1.2 虚拟地址到物理地址的映射原理

以典型的 4KB 页面、两级页表(x86 32-bit 经典模型)为例,地址翻译过程分为三步:

  • 拆分地址:32 位虚拟地址被拆分为 页目录索引(10位) | 页表索引(10位) | 页内偏移(12位)。
  • 查页目录:MMU 从 CR3 寄存器获取页目录基址,加上页目录索引 × 4 得到页目录项(PDE),从中读出页表基址。
  • 查页表:用页表索引 × 4 在页表中找到页表项(PTE),PTE 中包含物理页帧号(PFN)。
  • 合成物理地址:物理地址 = PFN × 页面大小 + 页内偏移。
性能要点:每次地址翻译需要两次内存访问(页目录 + 页表),TLB 正是为了消除这两次访问而设计的硬件缓存。多级页表虽然节省了页表内存,但增加了翻译路径长度。
虚拟地址到物理地址的映射流程 32 位虚拟地址:页目录索引 (10 bit) | 页表索引 (10 bit) | 页内偏移 (12 bit) ① 查页目录 CR3 + 页目录索引 × 4 ② 查页表 页表基址 + 页表索引 × 4 ③ 合成物理地址 PFN × 4KB + 偏移 页目录项 (PDE) → 页表基址 页表项 (PTE) → 物理页帧号 (PFN) 示例:虚拟地址 0x00401000 → 页目录索引=1, 页表索引=0, 偏移=0 PDE[1] 指向页表 X → PTE[0] 中 PFN=0x100 → 物理地址 = 0x100000 + 0 = 0x100000 多级页表每级索引位数由硬件和 OS 共同决定(x86-64 使用 4 级页表)

1.3 页表项(PTE)结构

页表项是页表的基本单元,每个 PTE 对应一个虚拟页到物理页的映射。以 x86-64 架构的 4KB 页面为例,每个 PTE 占 8 字节(64 位),其关键字段如下:

x86-64 页表项(PTE)典型字段布局
位域 宽度 名称 说明
0 1 bit Present (P) 页面是否在物理内存中;0 表示缺页
1 1 bit Read/Write (R/W) 0=只读,1=可读写
2 1 bit User/Supervisor (U/S) 0=仅内核可访问,1=用户态也可访问
3 1 bit Page-Level Write-Through (PWT) 控制 Cache 写策略
4 1 bit Page-Level Cache Disable (PCD) 禁止该页被缓存
5 1 bit Accessed (A) 硬件置位,表示该页被访问过
6 1 bit Dirty (D) 硬件置位,表示该页被写入过
7 1 bit Page Attribute Table (PAT) 与 PWT/PCD 配合控制内存类型
8 1 bit Global (G) 全局页,TLB 刷新时不被清除
9–11 3 bits Ignored / Available 供 OS 自定义使用(如记录页面状态)
12–51 40 bits Physical Page Number (PFN) 物理页帧号,指向 4KB 对齐的物理页
52–62 11 bits Reserved / Ignored 硬件保留或供未来扩展
63 1 bit No-Execute (NX) 禁止执行该页上的代码(DEP 保护)
调试陷阱:PTE 中的 Present 位为 0 时,MMU 会触发缺页异常(#PF)。但 OS 可能利用 Present=0 的 PTE 存储交换分区信息(如磁盘块号),此时 PTE 的剩余位被 OS 重新解释。调试时务必区分“真正的缺页”和“页面已换出”两种情况。
PTE 位域结构图 P R/W U/S PWT PCD A D PAT G Avail 物理页帧号 (PFN) 40 bits Reserved NX 8 3 40 11 1 PTE 总大小 64 位(8 字节),PFN 占 40 位,可寻址最大物理内存 2^40 × 4KB = 2^52 字节 OS 可利用 Avail 位存储页面优先级、NUMA 节点、swap 状态等调试信息 NX 位是 DEP/NX 保护的基础,阻止攻击者在数据页上执行代码

1.4 页表配置对性能的影响

页表配置直接影响 TLB 缺失率和地址翻译开销,以下是三个关键维度:

  • 页面大小:大页面(2MB/1GB)能覆盖更大虚拟地址范围,减少 TLB 项数需求,降低缺失率。但大页面可能导致内部碎片。
  • 页表层级:层级越多,TLB 缺失后遍历页表的代价越高(每次遍历需访问内存)。x86-64 的 4 级页表在 TLB 缺失时最多需要 4 次内存访问。
  • TLB 刷新策略:进程切换时刷新 TLB(如 x86 的 INVLPG、MOV CR3)会清空缓存,导致后续大量 TLB 缺失。使用 PCID(Process-Context IDentifiers)可避免全局刷新。
不同页面大小对 TLB 覆盖范围的影响
页面大小 TLB 项数(典型) TLB 覆盖范围 适用场景
4 KB 64 256 KB 通用桌面/服务器
2 MB 32 64 MB 数据库、HPC、大内存应用
1 GB 4 4 GB 虚拟化、大型内存数据库
性能优化提示:在性能热点代码中,如果发现大量 TLB 缺失(通过 perf stat -e dTLB-load-misses 观察),优先考虑启用透明大页(THP)或手动使用 mmap 的 MAP_HUGETLB 标志。对于数据库和搜索引擎等大内存工作负载,2MB 大页通常能降低 30%–50% 的 TLB 缺失率。