2. 虚拟内存与分页机制
2.1 虚拟地址空间与物理内存的抽象
现代操作系统通过虚拟内存为每个进程提供独立的、连续的地址空间,使进程无需关心物理内存的实际布局。CPU 发出的地址是虚拟地址(VA),经过内存管理单元(MMU)转换为物理地址(PA)。这种抽象隔离了进程,并允许程序使用比物理内存更大的地址空间。
核心概念:虚拟地址空间被划分为固定大小的页(Page),物理内存被划分为相同大小的页框(Page Frame)。页与页框之间的映射关系由页表(Page Table)维护。
2.2 页框与页表项(PTE)
页框(Page Frame)是物理内存的最小分配单元,大小通常为 4KB(x86 架构)或 16KB(ARM 大页)。页表项(PTE)是页表中的条目,每个 PTE 对应一个虚拟页,包含以下关键字段:
| 字段 | 位宽(典型) | 描述 |
|---|---|---|
| 物理页框号(PFN) | 20–52 位 | 映射到的物理页框基地址 |
| 存在位(Present) | 1 位 | 该页是否在物理内存中(否则触发缺页异常) |
| 读写位(R/W) | 1 位 | 允许读/写权限 |
| 用户/超级用户位(U/S) | 1 位 | 区分用户态与内核态访问 |
| 脏位(Dirty) | 1 位 | 页面是否被写入(用于换出策略) |
| 访问位(Accessed) | 1 位 | 页面是否被访问(用于页面置换算法) |
性能提示:PTE 的访问位和脏位由硬件自动设置,操作系统定期扫描这些位以决定哪些页面可以换出。频繁的脏位检查会增加 TLB 缺失开销。
2.3 多级页表结构
对于 64 位地址空间,单级页表会占用巨大内存(例如 2^48 个 PTE × 8 字节 = 2 PB)。因此现代架构采用多级页表,将虚拟地址划分为多个索引层级,每级索引对应一个页表。只有实际使用的虚拟地址范围才会分配页表,大幅节省内存。
以 x86-64 的 4 级页表为例,虚拟地址被划分为:
- PML4(Page Map Level 4):9 位索引,指向第 2 级页表
- PDPT(Page Directory Pointer Table):9 位索引,指向第 3 级页表
- PD(Page Directory):9 位索引,指向第 4 级页表
- PT(Page Table):9 位索引,指向物理页框
- Offset:12 位页内偏移(4KB 页)
2.4 多级页表遍历与 TLB 缺失
当 CPU 访问一个虚拟地址时,MMU 从最高级页表开始逐级查找:
- 从 CR3 寄存器(x86)或 TTBR(ARM)获取 PML4 基地址。
- 用虚拟地址的 PML4 索引找到对应条目,获取 PDPT 基地址。
- 用 PDPT 索引找到对应条目,获取 PD 基地址。
- 用 PD 索引找到对应条目,获取 PT 基地址。
- 用 PT 索引找到对应条目,获取物理页框号。
- 组合页框号 + 页内偏移得到物理地址。
每次页表遍历需要 4 次内存访问(4 级页表)。如果 TLB 未命中,这些访问会直接增加内存延迟。TLB 缺失率越高,页表遍历开销越显著。
性能陷阱:多级页表遍历的 4 次内存访问可能全部命中 L1/L2 缓存,也可能全部缺失导致主存访问。在数据库、HPC 等大内存工作负载中,TLB 缺失导致的页表遍历是主要瓶颈之一。
2.5 页表遍历路径优化方向
针对页表遍历路径的优化通常从以下角度切入:
| 优化方向 | 方法 | 预期效果 |
|---|---|---|
| TLB 覆盖范围 | 使用大页(2MB/1GB)减少页表层级 | 减少 TLB 缺失次数 |
| 页表缓存 | 利用 MMU 的页表缓存(PWC) | 加速页表遍历中间结果 |
| 预取 | 硬件 TLB 预取 / 软件预取 | 降低 TLB 缺失延迟 |
| 页表结构 | 使用虚拟化扩展(如 EPT/NPT) | 减少虚拟机场景的页表遍历开销 |
工程实践:在 Linux 中,通过
hugepages 或透明大页(THP)可以显著降低 TLB 缺失率。对于内存密集型应用(如 Redis、数据库),建议显式配置大页。
2.6 本章小结
- 虚拟内存通过页表实现虚拟地址到物理地址的映射。
- 页表项(PTE)包含 PFN、权限位、状态位等关键信息。
- 多级页表(如 x86-64 的 4 级页表)节省内存,但增加遍历开销。
- TLB 缺失会触发完整的页表遍历,是性能优化的核心关注点。
- 大页、页表缓存、预取等技术可有效优化页表遍历路径。