1. 内存管理基础:虚拟内存与物理内存映射、页表、TLB、缺页中断
1.1 虚拟内存与物理内存映射
现代操作系统(如 Linux)采用虚拟内存管理机制,为每个进程提供独立的、连续的虚拟地址空间(Virtual Address Space, VAS)。进程实际使用的内存最终必须映射到物理内存(Physical Memory, RAM)上。这种映射由 内存管理单元(MMU) 硬件和操作系统内核协同完成。
虚拟内存带来的关键优势:
- 隔离性: 每个进程拥有独立的虚拟地址空间,进程间无法直接访问对方的内存,提升了安全性和稳定性。
- 抽象性: 进程可以使用比物理内存更大的地址空间(通过磁盘交换),并且无需关心物理内存的碎片化布局。
- 共享性: 多个进程的虚拟页可以映射到同一物理页(如共享库 libc.so),节省物理内存。
1.2 页表(Page Table)
页表是操作系统为每个进程维护的核心数据结构,用于记录虚拟页(Virtual Page, VP)到物理页框(Physical Page Frame, PF)的映射关系。页表项(PTE)不仅包含物理页框号,还包含关键的权限和状态位。
| 字段 | 位宽(x86-64 典型) | 说明 |
|---|---|---|
| 物理页框号 (PFN) | 40 bits | 映射到的物理页框基地址的高位 |
| Present (P) | 1 bit | 该页是否在物理内存中(1=在,0=不在/已换出) |
| Read/Write (R/W) | 1 bit | 写权限(0=只读,1=可读写) |
| User/Supervisor (U/S) | 1 bit | 访问权限级别(0=内核态,1=用户态) |
| Accessed (A) | 1 bit | 硬件置位,表示该页被访问过(用于页面置换算法) |
| Dirty (D) | 1 bit | 硬件置位,表示该页被写入过(用于回写策略) |
/proc/[pid]/pagemap 和 /proc/[pid]/maps 文件查看进程的页表映射信息。这是排查内存泄漏和虚拟内存布局的重要入口。
多级页表
为了节省内存(尤其是 64 位系统),现代架构使用多级页表。x86-64 Linux 使用 4 级页表(PGD、PUD、PMD、PTE),ARM64 支持 3 级或 4 级。多级页表允许大范围的虚拟地址空间不分配页表项,只有实际使用的虚拟地址段才会创建对应的页表层级。
1.3 TLB(Translation Lookaside Buffer)
TLB 是 MMU 内部的一个硬件缓存,用于缓存最近使用的虚拟地址到物理地址的转换结果(即页表项)。由于页表存储在内存中,每次地址转换都需要多次内存访问(多级页表),TLB 可以显著加速这一过程。
TLB 的常见特性:
- 分离式 TLB: 现代 CPU 通常有独立的指令 TLB(ITLB)和数据 TLB(DTLB),以及统一的二级 TLB(L2 TLB)。
- ASID(Address Space Identifier): 用于区分不同进程的 TLB 条目,避免进程切换时完全刷新 TLB,提升性能。
- 大页支持: 使用 2MB 或 1GB 的大页(Huge Pages)可以减少页表层级,并提高 TLB 覆盖范围。
| 场景 | 延迟(典型) | 影响 |
|---|---|---|
| TLB 命中 | ~1 个 CPU 周期 | 地址转换几乎无开销 |
| TLB 未命中(硬件遍历) | ~10-100 个周期 | 需要访问内存中的页表,可能触发缓存缺失 |
| TLB 未命中(缺页) | 数百万个周期(ms 级) | 触发缺页中断,需要内核处理磁盘 I/O |
perf stat -e dTLB-load-misses,iTLB-load-misses 可以快速评估 TLB 性能。
1.4 缺页中断(Page Fault)
当 CPU 访问一个虚拟地址,而该地址对应的页表项中的 Present 位为 0(即该页不在物理内存中)时,MMU 会触发一个缺页中断(Page Fault),将控制权交给操作系统内核的缺页中断处理程序。
缺页中断的三种主要类型:
- 主要缺页(Major Page Fault): 所需页面不在物理内存中,且不在任何进程的工作集中,需要从磁盘(交换分区或文件映射)读取。这是最昂贵的缺页类型。
- 次要缺页(Minor Page Fault): 所需页面在物理内存中(例如被其他进程共享,或刚从磁盘读入但尚未建立映射),只需在页表中建立映射即可。
- 无效缺页(Invalid Page Fault / Segmentation Fault): 访问的虚拟地址没有合法的映射(如解引用空指针、访问已释放的内存),内核会向进程发送 SIGSEGV 信号,通常导致进程崩溃。
1.5 关键数据结构:VMA(Virtual Memory Area)
在 Linux 内核中,每个进程的虚拟地址空间由一组 VMA 描述。VMA 是内核管理进程地址空间的基本单位,记录了连续的虚拟地址段的属性(如起始地址、结束地址、权限、是否文件映射等)。
VMA 与页表的关系:
- VMA 是内核态的元数据,描述“应该映射什么”;页表是硬件使用的数据结构,描述“当前映射了什么”。
- 缺页中断发生时,内核首先查找发生缺页的地址属于哪个 VMA。如果找不到对应的 VMA,则触发段错误。
- 通过
/proc/[pid]/maps可以查看进程的所有 VMA,这是调试内存布局和泄漏的常用手段。
cat /proc/$(pidof your_program)/maps 输出示例(每行对应一个 VMA):
00400000-00452000 r-xp 00000000 08:01 1234567 /usr/bin/your_program
00651000-00652000 r--p 00051000 08:01 1234567 /usr/bin/your_program
00652000-00653000 rw-p 00052000 08:01 1234567 /usr/bin/your_program
7f8c1a000000-7f8c1a021000 rw-p 00000000 00:00 0 [heap]
7f8c1c000000-7f8c1c002000 rw-p 00000000 00:00 0 [anon]
7f8c1d400000-7f8c1d600000 r-xp 00000000 08:01 7654321 /usr/lib/libc.so.6
7fffdfe00000-7fffe0000000 rw-p 00000000 00:00 0 [stack]
1.6 本章小结
虚拟内存是现代内存管理的基石。理解虚拟地址到物理地址的映射机制、页表的多级结构、TLB 的加速作用以及缺页中断的处理流程,是进行后续内存调试和泄漏排查的前提。这些概念不仅影响程序的性能,也是理解 perf、valgrind、asan 等工具工作原理的基础。