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 附近 | 向低地址增长 | 局部变量、函数调用帧 |
实战提示: 使用
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 (页内偏移) |
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 信号,通常导致进程崩溃 |
2.3.2 缺页异常与 mmap 的关系
mmap 的核心机制正是利用缺页异常实现 延迟加载 (Demand Paging):
- 建立 VMA:
mmap系统调用仅在进程的地址空间中创建一个vm_area_struct(VMA) 结构体,记录映射的虚拟地址范围、权限和文件信息,但不分配物理页。 - 首次访问触发缺页: 当进程首次读写映射区域时,MMU 发现页表项无效,触发缺页异常。
- 文件回读: 缺页处理程序根据 VMA 中的文件信息,调用文件系统的
address_space_operations中的readpage函数,从磁盘读取数据到物理页。 - 建立映射: 将物理页帧号填入页表项,并设置权限位,完成映射。
性能陷阱: 对于实时性要求高的 DMA 缓冲区,缺页异常带来的延迟是不可接受的。因此,DMA 缓冲区通常使用
mlock 或 mlockall 锁定物理页,或使用 MAP_POPULATE 标志预分配物理页,避免运行时缺页。
2.4 本章小结
- 进程地址空间分为用户空间和内核空间,用户空间包含代码段、数据段、堆、mmap 区和栈。
- 多级页表(x86-64 四级)将虚拟地址分段索引,有效节省页表内存;TLB 缓存地址转换结果,是性能关键。
- 缺页异常分为有效缺页(Minor Fault)和无效缺页(Major Fault),后者涉及磁盘 I/O,开销巨大。
mmap利用缺页异常实现延迟加载,但 DMA 缓冲区需要避免运行时缺页。