2. 虚拟内存基础:进程地址空间布局、页表与TLB、缺页异常机制

虚拟内存是现代操作系统内核的基石,它为每个进程提供了独立的、连续的地址空间抽象。理解虚拟内存的布局、地址转换机制以及缺页异常处理,是掌握 mmap 和 DMA 缓冲区分配的前提。本章将深入剖析这些核心概念。

2.1 进程地址空间布局

在 64 位 Linux 系统中,每个用户态进程拥有独立的虚拟地址空间(通常为 128 TB)。内核空间与用户空间通过地址高位进行划分。典型的进程地址空间布局如下:

关键概念: 虚拟地址空间分为用户空间(0x0000000000000000 ~ 0x00007FFFFFFFFFFF)和内核空间(0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF)。用户空间布局由低地址到高地址依次为:代码段、数据段、堆、内存映射区、栈。
区域 起始地址(典型) 增长方向 主要用途
代码段 (.text) 0x400000 固定 存储可执行指令,只读
数据段 (.data/.bss) 0x600000 固定 存储全局变量、静态变量
堆 (Heap) 数据段之后 向高地址增长 动态内存分配(malloc、brk)
内存映射区 (mmap) 堆与栈之间 双向 文件映射、共享内存、动态库加载
栈 (Stack) 0x7FFFFFFF 附近 向低地址增长 局部变量、函数调用帧
进程地址空间布局示意图 进程虚拟地址空间布局 (x86-64) 内核空间 (Kernel Space) 0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF 栈 (Stack) 向低地址增长 内存映射区 (mmap) 文件映射、共享内存、动态库 堆 (Heap) 向高地址增长 数据段 (.data/.bss) 代码段 (.text) 用户空间:0x0000000000000000 ~ 0x00007FFFFFFFFFFF
实战提示: 使用 cat /proc/<PID>/maps 可以查看任意进程的详细地址空间布局。这是调试内存映射问题的第一利器。

2.2 页表与TLB

虚拟地址到物理地址的转换由 内存管理单元 (MMU) 硬件完成,转换依据是 页表。Linux 采用多级页表结构(x86-64 使用 4 级页表),以节省内存并支持大地址空间。

2.2.1 多级页表结构

多级页表的核心思想是:将虚拟地址划分为多个位段,每一段索引一级页表。只有实际使用的虚拟地址范围才会分配页表项,从而大幅减少页表占用的物理内存。

页表级别 名称 位段宽度 (x86-64) 覆盖范围
L1 PGD (Page Global Directory) 9 bits 512 GB
L2 P4D (Page 4th Directory) 9 bits 1 GB
L3 PUD (Page Upper Directory) 9 bits 2 MB
L4 PMD (Page Middle Directory) 9 bits 4 KB
L5 PTE (Page Table Entry) 9 bits 4 KB (页内偏移)
多级页表地址转换流程 x86-64 四级页表地址转换流程 虚拟地址 (48 bits) PGD (9 bits) PUD (9 bits) PMD (9 bits) PTE (9 bits) PGD 表 PUD 表 PMD 表 PTE 表 页内偏移 (12 bits) 物理地址 (Physical Address) 每级页表包含 512 个表项 (2^9),每个表项指向下一级页表或物理页帧

2.2.2 TLB (Translation Lookaside Buffer)

TLB 是 MMU 内部的高速缓存,用于缓存最近使用的虚拟地址到物理地址的转换结果。由于多级页表查询需要多次内存访问,TLB 的命中率直接影响内存访问性能。

性能数据: TLB 命中时,地址转换仅需 1 个 CPU 周期;TLB 未命中时,需要遍历 4 级页表,耗时约 10-100 个周期。TLB 缺失率每增加 1%,程序性能可能下降 5-10%。
  • TLB 条目结构: 虚拟页号 (VPN) + 物理页帧号 (PFN) + 权限位 + 状态位 (有效/脏/访问)。
  • TLB 刷新: 进程切换时,由于地址空间不同,需要刷新 TLB。Linux 使用 switch_mm() 函数触发 TLB 刷新。
  • 大页 (Huge Pages): 使用 2MB 或 1GB 的大页可以减少页表级数,并提高 TLB 覆盖范围,是 DMA 和数据库场景的优化利器。

2.3 缺页异常机制

当 CPU 访问的虚拟地址在页表中不存在有效的物理页映射时,MMU 会触发 缺页异常 (Page Fault)。Linux 内核的缺页异常处理程序是内存管理的核心路径,它决定了内存分配的时机和策略。

2.3.1 缺页异常分类

类型 触发原因 处理方式
有效缺页 (Minor Fault) 页表项存在但未建立映射(如共享内存首次访问) 快速分配物理页并建立映射,无需磁盘 I/O
无效缺页 (Major Fault) 页表项不存在,需要从磁盘加载数据(如文件映射、swap) 触发磁盘 I/O,进程进入睡眠等待,开销极大
保护错误 (Protection Fault) 访问权限不匹配(如写只读页、执行非可执行页) 发送 SIGSEGV 信号,通常导致进程崩溃
缺页异常处理流程 缺页异常处理流程 CPU 触发缺页异常 地址有效性检查 无效 有效 发送 SIGSEGV 信号 进程终止 (core dump) 检查页表项 (PTE) 是否在 Swap 中? 是 (Major Fault) 否 从磁盘/Swap 读取数据 分配物理页 (Minor) 建立页表映射

2.3.2 缺页异常与 mmap 的关系

mmap 的核心机制正是利用缺页异常实现 延迟加载 (Demand Paging):

  1. 建立 VMA: mmap 系统调用仅在进程的地址空间中创建一个 vm_area_struct (VMA) 结构体,记录映射的虚拟地址范围、权限和文件信息,但不分配物理页。
  2. 首次访问触发缺页: 当进程首次读写映射区域时,MMU 发现页表项无效,触发缺页异常。
  3. 文件回读: 缺页处理程序根据 VMA 中的文件信息,调用文件系统的 address_space_operations 中的 readpage 函数,从磁盘读取数据到物理页。
  4. 建立映射: 将物理页帧号填入页表项,并设置权限位,完成映射。
性能陷阱: 对于实时性要求高的 DMA 缓冲区,缺页异常带来的延迟是不可接受的。因此,DMA 缓冲区通常使用 mlock 或 mlockall 锁定物理页,或使用 MAP_POPULATE 标志预分配物理页,避免运行时缺页。

2.4 本章小结

  • 进程地址空间分为用户空间和内核空间,用户空间包含代码段、数据段、堆、mmap 区和栈。
  • 多级页表(x86-64 四级)将虚拟地址分段索引,有效节省页表内存;TLB 缓存地址转换结果,是性能关键。
  • 缺页异常分为有效缺页(Minor Fault)和无效缺页(Major Fault),后者涉及磁盘 I/O,开销巨大。
  • mmap 利用缺页异常实现延迟加载,但 DMA 缓冲区需要避免运行时缺页。