2. Linux 内存管理基础:虚拟地址空间、物理内存、MMU 与 TLB 的工作原理

在深入页表遍历性能瓶颈之前,必须彻底理解 Linux 内存管理的核心抽象层。现代操作系统通过 虚拟内存 机制,为每个进程提供独立的、连续的地址空间,而底层物理内存则被分页管理。MMU(内存管理单元)与 TLB(转译后备缓冲器)是连接虚拟地址与物理地址的关键硬件组件,它们的效率直接决定了内存访问的性能。

2.1 虚拟地址空间与物理内存

每个用户态进程都拥有独立的虚拟地址空间。在 64 位 x86 架构(x86_64)上,Linux 将虚拟地址空间划分为用户空间与内核空间两部分,典型的划分比例为 47 位用户空间 + 17 位内核空间(实际使用 48 位地址)。

关键概念: 虚拟地址是进程看到的地址,物理地址是硬件总线上的真实地址。MMU 负责将虚拟地址“翻译”为物理地址。

2.1.1 虚拟地址空间布局

以下表格展示了 x86_64 架构下 Linux 进程的典型虚拟地址空间布局(从低地址到高地址):

区域 起始地址(典型) 大小 说明
代码段(.text) 0x400000 可变 可执行指令,只读
数据段(.data/.bss) 0x600000 可变 全局变量、静态变量
堆(Heap) 0x601000 起 向上增长 动态内存分配(malloc)
内存映射区(mmap) 0x7f0000000000 可变 共享库、文件映射、匿名映射
栈(Stack) 0x7ffc00000000 向下增长 局部变量、函数调用帧
内核空间 0xffff800000000000 128 TB 内核代码、模块、直接映射区
性能提示: 堆与栈的增长方向相反,避免在连续分配时过早耗尽地址空间。mmap 区域是共享库和大型内存映射的所在地,也是 TLB 压力测试的重点区域。

2.1.2 物理内存与页帧

物理内存被划分为固定大小的块,称为 页帧(Page Frame)。Linux 默认页大小为 4 KB。内核维护一个 struct page 数组来描述每个物理页帧的状态(空闲、使用中、脏页等)。

物理内存的分配与回收由 伙伴系统(Buddy System) 管理,它按 2 的幂次阶(order)组织空闲页块,以减少外部碎片。

// 内核中分配物理页面的核心函数
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
// order = 0 表示分配 1 页 (4KB)
// order = 1 表示分配 2 页 (8KB)
// order = 9 表示分配 512 页 (2MB)

2.2 MMU 与地址转换

MMU(Memory Management Unit)是 CPU 内部负责地址转换的硬件单元。当 CPU 执行加载或存储指令时,发出的虚拟地址被 MMU 截获,MMU 通过查询 页表(Page Table) 将其转换为物理地址。

2.2.1 四级页表结构(x86_64)

x86_64 架构使用四级页表层次结构:PGD(Page Global Directory)、P4D(Page 4th Directory)、PUD(Page Upper Directory)、PMD(Page Middle Directory)、PTE(Page Table Entry)。每个层级包含 512 个表项,每个表项 8 字节。

虚拟地址被划分为多个位段,用于索引不同层级的页表:

虚拟地址 (48位) 分解:
| 63 ... 48 | 47 ... 39 | 38 ... 30 | 29 ... 21 | 20 ... 12 | 11 ... 0 |
|  符号扩展  |   PGD索引  |   PUD索引  |   PMD索引  |   PTE索引  |  页内偏移 |
|   (16位)   |   (9位)   |   (9位)   |   (9位)   |   (9位)   |   (12位)  |

地址转换过程如下:

  1. 从控制寄存器 cr3 获取顶级页表(PGD)的物理基地址。
  2. 使用虚拟地址的 PGD 索引 在 PGD 表中查找,得到下一级页表(P4D)的物理地址。
  3. 使用 P4D 索引 在 P4D 表中查找,得到 PUD 表的物理地址。
  4. 使用 PUD 索引 在 PUD 表中查找,得到 PMD 表的物理地址。
  5. 使用 PMD 索引 在 PMD 表中查找,得到 PTE 表的物理地址。
  6. 使用 PTE 索引 在 PTE 表中查找,得到最终的物理页帧号(PFN)。
  7. 将 PFN 与虚拟地址的 页内偏移 组合,形成完整的物理地址。
性能陷阱: 每次地址转换都需要 4 次(或 5 次,含 P4D)内存访问来遍历页表。如果页表不在 CPU 缓存中,这将导致严重的延迟(约 100-200 纳秒/次)。TLB 正是为了解决这个问题而设计的。

2.2.2 页表遍历流程 SVG 图

四级页表地址转换流程 四级页表地址转换流程(x86_64) 虚拟地址 (48位) 符号扩展 (16位) PGD (9位) PUD (9位) PMD (9位) PTE (9位) 页内偏移 (12位) cr3 寄存器 PGD 表 512 项 PUD 表 512 项 PMD 表 512 项 PTE 表 512 项 物理页帧 (4KB) PFN + 偏移 = 物理地址 说明: 每次地址转换需要 4 次内存访问(PGD→PUD→PMD→PTE)。如果使用大页(2MB/1GB),可跳过中间层级,减少遍历次数。

2.3 TLB:转译后备缓冲器

TLB(Translation Lookaside Buffer)是 MMU 内部的一个硬件缓存,用于存储最近使用的虚拟地址到物理地址的映射关系。它的目的是避免每次内存访问都进行完整的页表遍历。

2.3.1 TLB 的结构与操作

TLB 通常分为 指令 TLB(ITLB) 和 数据 TLB(DTLB),部分架构还包含 统一的二级 TLB(L2 TLB)。TLB 表项(TLB Entry)包含虚拟页号(VPN)、物理页帧号(PFN)、权限位、脏位、访问位等。

TLB 的查找过程:

  1. CPU 发出虚拟地址。
  2. MMU 并行查询 TLB(全相联或组相联)。
  3. TLB 命中: 直接返回物理地址,延迟极低(约 1-2 个 CPU 周期)。
  4. TLB 未命中: 触发硬件页表遍历(Page Walk),将结果写入 TLB,然后重试指令。
性能数据: TLB 命中延迟约 1 纳秒,TLB 未命中并触发页表遍历的延迟约 100-200 纳秒。TLB 未命中率是衡量内存访问效率的关键指标。

2.3.2 TLB 刷新与上下文切换

当进程切换(context switch)时,由于不同进程使用不同的虚拟地址空间,TLB 中的旧映射必须失效。Linux 通过以下方式处理:

  • 全局页(Global Pages): 内核空间页表项通常标记为全局,在进程切换时不清除,减少 TLB 刷新开销。
  • 进程上下文标识符(PCID): 现代 CPU 支持 PCID,允许 TLB 同时缓存多个进程的映射,避免完全刷新。
  • 显式刷新指令: 如 x86 的 INVLPG(刷新单个页)和 MOV CR3(刷新全部非全局 TLB)。
// 内核中 TLB 刷新相关函数
void flush_tlb_all(void);           // 刷新所有 TLB(SMP 环境下广播)
void flush_tlb_mm(struct mm_struct *mm); // 刷新指定进程的所有 TLB
void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr); // 刷新单个页

2.3.3 TLB 与页表遍历的交互 SVG 图

TLB 与页表遍历交互流程 TLB 与页表遍历交互流程 CPU 核心 发出虚拟地址 TLB 快速查找缓存 TLB 命中 直接返回物理地址 TLB 未命中 硬件页表遍历 4 次内存访问 物理地址 回填 TLB 表项 关键路径: TLB 命中是快速路径(~1ns),TLB 未命中触发页表遍历(~100-200ns)。perf 工具可以统计 TLB 未命中次数(如 dTLB-load-misses),帮助定位页表遍历瓶颈。

2.4 大页(Huge Pages)与页表遍历优化

大页是减少页表遍历开销和 TLB 压力的重要手段。Linux 支持多种大页机制:

特性 标准 4KB 页 透明大页(THP) 显式大页(Hugetlbfs)
页大小 4 KB 2 MB(默认) 2 MB / 1 GB
页表层级 4 级(PGD→PUD→PMD→PTE) 3 级(跳过 PTE) 2 级(跳过 PMD 和 PTE)
TLB 覆盖范围 小(64 个 TLB 项覆盖 256 KB) 中(64 个 TLB 项覆盖 128 MB) 大(64 个 TLB 项覆盖 128 MB / 64 GB)
分配方式 自动 内核自动合并/分裂 应用程序显式申请(mmap)
适用场景 通用 内存密集型应用(数据库、JVM) 性能敏感型应用(DPDK、虚拟化)
调优建议: 对于内存访问模式随机且工作集巨大的应用(如 Memcached、Redis、数据库),启用透明大页或使用 Hugetlbfs 可以显著降低 TLB 未命中率,提升吞吐量。但 THP 的碎片整理可能引入延迟抖动,需要根据场景权衡。

2.5 本章小结

  • 虚拟地址空间 为进程提供隔离和连续性,物理内存 被分页管理,页帧是基本分配单位。
  • MMU 通过四级页表结构将虚拟地址转换为物理地址,每次转换需要 4 次内存访问。
  • TLB 是 MMU 内部的硬件缓存,用于加速地址转换。TLB 未命中是页表遍历性能瓶颈的直接体现。
  • 大页 通过减少页表层级和扩大 TLB 覆盖范围,是优化页表遍历效率的核心手段。
  • 理解这些基础原理,是后续使用 perf 工具定位 TLB 未命中、页表遍历开销以及进行系统调优的前提。