2. Linux 内存管理基础:虚拟内存、物理内存、页表、MMU 工作原理

在深入 tracepoint 监控内存分配路径之前,必须理解 Linux 内存管理的核心抽象层。内存分配并非直接操作物理地址,而是通过 虚拟内存 (Virtual Memory)、页表 (Page Table) 和 内存管理单元 (MMU) 构成的间接层完成。这一机制提供了进程隔离、按需分页、共享内存和高效缓存的基础。

2.1 虚拟内存与物理内存

每个用户态进程都拥有独立的 虚拟地址空间 (Virtual Address Space),在 64 位系统上通常为 128 TB。进程看到的地址是连续的、从 0 开始的线性空间,但实际上这些虚拟地址并不直接对应物理内存中的位置。

概念 定义 关键特性
虚拟内存 进程看到的逻辑地址空间,由内核管理 连续、隔离、可大于物理内存
物理内存 硬件 RAM 中的实际存储单元 有限、共享、非连续分配
地址映射 虚拟地址 → 物理地址的转换关系 由页表和 MMU 共同完成
核心洞察: 虚拟内存允许每个进程认为自己拥有整个地址空间,而物理内存则通过页表按需分配。当进程访问一个尚未映射的虚拟页时,触发 缺页异常 (Page Fault),内核才真正分配物理页框。

2.2 页表 (Page Table) 与多级分页

页表是虚拟地址到物理地址的转换表。Linux 使用 多级分页 结构(如 x86_64 的 4 级页表:PGD → P4D → PUD → PMD → PTE),以节省存储大页表所需的内存。

  • 页框 (Page Frame):物理内存被划分为固定大小的块,通常为 4 KB。
  • 页表项 (PTE):包含物理页框号、权限位(读/写/执行)、存在位、脏位、访问位等。
  • TLB (Translation Lookaside Buffer):MMU 内部的硬件缓存,加速虚拟地址到物理地址的转换。
性能提示: 当进程频繁切换上下文时,TLB 会被刷新,导致转换开销增加。大页 (Huge Pages) 可以减少页表层级和 TLB 压力,是内存密集型应用的优化手段。
多级页表地址转换流程 (x86_64 4级页表) 虚拟地址 (48位) PGD (全局) P4D (4级) PUD (3级) PMD (2级) PTE (1级) 物理页框 MMU 逐级查找页表,最终获得物理页框号 + 偏移量

2.3 MMU 工作原理

内存管理单元 (MMU) 是 CPU 内部的硬件模块,负责自动执行虚拟地址到物理地址的转换。每次 CPU 发出内存访问请求时,MMU 都会介入。

  1. 地址分解:MMU 将虚拟地址拆分为页内偏移和多个页表索引。
  2. TLB 查找:首先在 TLB 中查找缓存。如果命中,直接获得物理地址。
  3. 页表遍历:如果 TLB 未命中,MMU 硬件遍历多级页表(或由内核软件处理)。
  4. 权限检查:检查 PTE 中的权限位,若违反则触发段错误 (Segmentation Fault)。
  5. 物理地址生成:将页框号与偏移量拼接,形成最终物理地址。
故障排查要点: 当 tracepoint 监控到 mm_page_alloc 或 mm_page_fault 事件时,通常意味着 MMU 触发了缺页异常。频繁的缺页异常(尤其是主缺页)会导致显著的性能下降,应结合 perf 或 ftrace 分析缺页类型和触发路径。
MMU 地址转换工作流程 CPU 核心 虚拟地址 MMU TLB 缓存 页表 (内存) 物理地址 TLB 命中:直接返回物理地址;TLB 未命中:MMU 遍历页表,更新 TLB,再返回物理地址

2.4 关键数据结构:struct page 与 mem_map

内核使用 struct page 描述每个物理页框。所有 struct page 实例构成全局数组 mem_map,是物理内存管理的核心元数据。

// include/linux/mm_types.h (简化)
struct page {
    unsigned long flags;          // 页状态标志 (PG_locked, PG_dirty, PG_referenced 等)
    atomic_t _refcount;           // 引用计数
    atomic_t _mapcount;           // 映射计数 (被多少个 VMA 映射)
    unsigned long private;        // 私有数据 (如用于 slab 分配器)
    struct address_space *mapping; // 关联的地址空间 (用于文件页缓存)
    pgoff_t index;                // 在映射中的偏移
    struct list_head lru;         // LRU 链表节点 (用于页面回收)
    void *virtual;                // 内核虚拟地址 (若直接映射)
};
tracepoint 关联: 当监控 mm_page_alloc 事件时,tracepoint 会输出分配的 struct page 指针、页阶 (order) 和分配标志 (gfp_flags)。通过 struct page 可以进一步追踪页面的使用场景(如匿名页、文件页、slab 页)。

2.5 内存区域 (Zones) 与分配器

物理内存被划分为不同的 内存区域 (Zones),以适应不同硬件的 DMA 限制。Linux 使用 伙伴系统 (Buddy System) 管理页框的分配与释放。

内存区域 范围 用途
ZONE_DMA 0 - 16 MB 老式 ISA 设备 DMA
ZONE_DMA32 0 - 4 GB 32 位设备 DMA
ZONE_NORMAL 16 MB - 896 MB (32位) / 全部 (64位) 常规内存分配
ZONE_HIGHMEM > 896 MB (仅 32 位) 用户态大内存映射
ZONE_MOVABLE 可配置 内存热插拔与防碎片
监控切入点: 在 64 位系统上,ZONE_NORMAL 是主要分配区域。通过 tracepoint mm_page_alloc_extfrag 可以监控因碎片化导致的跨 zone 分配或 fallback 行为,这是内存分配延迟的常见根源。

2.6 总结:从虚拟地址到物理页框的完整路径

当进程执行 malloc() 并首次访问分配的内存时,内核经历以下路径:

  1. 用户态:malloc() 调用 brk() 或 mmap() 系统调用,扩展堆或映射匿名页。
  2. 内核态:分配虚拟内存区域 (VMA),但尚未分配物理页框。
  3. 缺页异常:进程访问虚拟地址,MMU 遍历页表发现 PTE 无效,触发缺页异常。
  4. 物理分配:内核缺页异常处理程序调用伙伴系统分配物理页框,并建立 PTE 映射。
  5. 返回用户态:MMU 重新执行指令,TLB 缓存新映射,进程正常访问内存。