跳到主要内容

内存管理:从虚拟地址到硬件分页

内存管理的目的是为每个进程提供一个巨大、私有且连续的地址空间,同时高效地共享有限的物理 RAM。这需要内核与硬件 内存管理单元 (MMU) 之间的复杂协作。

本章探讨内存抽象的层级结构,从底层分配到现代的请求分页 (Demand Paging) 和交换 (Swapping) 技术。


1. 虚拟内存抽象

现代操作系统不允许进程直接访问物理 RAM。相反,它们提供虚拟内存

1.1 为什么需要虚拟内存?

  • 隔离性:进程 A 甚至无法“命名”进程 B 的内存。
  • 超量使用 (Over-commitment):所有进程使用的虚拟内存总和可以超过物理 RAM(通过交换技术实现)。
  • 重定位:进程可以被加载到 RAM 的任何位置,而其内部指针无需改变。
  • 碎片控制:可以将细小、不连续的物理块映射为一个巨大的、连续的虚拟空间。

1.2 地址转换周期

当 CPU 执行 MOV EAX, [0x12345] 时,0x12345 是一个虚拟地址

  1. CPU 将此地址发送给 MMU
  2. MMU 在其缓存 (TLB) 中查找转换结果。
  3. 如果未找到(TLB 缺失),MMU 会在 RAM 中“行走”页表 (Page Tables)
  4. MMU 找到对应的物理地址(例如 0x98765)并获取数据。

2. 硬件机制:分页与分段

2.1 分页 (Paging - 现代标准)

内存被划分为固定大小的单位:页 (Pages)(虚拟)和 页框 (Frames)(物理)。

  • 标准大小:通常为 4 KB。
  • 偏移量:地址的最后 12 位(针对 4KB 页)在转换过程中保持不变。

2.2 多级页表 (x86-64)

一个针对 64 位地址空间的单级扁平页表将达到拍字节 (PB) 级别。为了节省空间,我们使用层级树结构:

  • PML4 (页映射 4 级)
  • PDPT (页目录指针表)
  • PD (页目录)
  • PT (页表)
  • 收益:如果大范围的虚拟内存未被使用,内核就无需分配下层页表,从而节省海量 RAM。

2.3 分段 (Segmentation - 传统方法)

将内存划分为逻辑单位(代码、数据、栈)。

  • 问题:导致外部碎片 (External Fragmentation)(段之间的间隙太小,无法利用)。
  • 现状:在 64 位系统上基本已过时,现代系统使用“扁平内存模型”,分页处理一切。

3. 内核内存架构

内核如何管理它自己的内存并跟踪你的内存?

3.1 mm_struct (Linux 内核)

每个进程都有一个 mm_struct,定义了其完整的内存视图:

  • pgd:指向顶级页全局目录的指针。
  • mmap虚拟内存区域 (VMA) 的链表。
  • mm_rb:VMAs 的红黑树,用于快速查找。

3.2 虚拟内存区域 (VMA)

VMA 是具有相同权限的一段连续虚拟地址范围(例如“堆”、“栈”或“映射的库”)。

  • 字段start_addr, end_addr, permissions (读/写/执行), 以及 backing_file (备份文件)。

4. 物理内存分配

内核必须以极快的速度管理物理页框。

4.1 伙伴系统 (Buddy System - 大块分配)

物理 RAM 被划分为大小为 20,21,...,2102^0, 2^1, ..., 2^{10} 页的块。

  • 分配:如果你请求 4 页 (222^2),而只有一个 8 页的块可用,内核会将 8 页块拆分为两个 4 页的“伙伴”。
  • 合并:当一个块被释放时,内核检查其“伙伴”是否也空闲。如果是,它们合并回更大的块。
  • 结果:极大地减少了外部碎片。

4.2 SLAB/SLUB 分配器 (小对象分配)

伙伴系统对于 struct task_structstruct inode 这样的小对象来说粒度太粗。

  • Slabs:针对特定对象类型预分配的“缓存”。
  • 收益:无内部碎片;对象被重用,避免了频繁初始化的开销。

5. 请求分页与交换

只有当页面真正被需要时,它才会被加载到物理 RAM 中。

5.1 缺页中断 (Page Fault) 生命周期

  1. 访问:进程尝试读取某个虚拟地址。
  2. 异常:MMU 发现页表中的“存在位 (Present Bit)”为 0,触发缺页中断
  3. 内核干预
    • 地址是否有效?(检查 VMAs)。
    • 如果有效,寻找一个空闲的物理页框。
    • 将数据从磁盘读取到该页框。
  4. 恢复:更新页表(设置 Present=1)并重新启动 CPU 指令。

5.2 抖动 (Thrashing) 与工作集 (Working Set)

  • 抖动:当 OS 花费所有时间在换入/换出页面,而不是执行代码时。
  • 工作集:进程在过去 TT 秒内活跃使用的页面集合。
  • 规则:如果 工作集之和 > RAM,系统就会发生抖动。

6. 高级性能优化

6.1 巨页 (HugePages - 2MB / 1GB)

对于现代数 GB 级别的数据库来说,标准的 4KB 页太小了。

  • 问题:小页意味着庞大的页表和频繁的 TLB 缺失。
  • 解决方案:使用巨页。单个 TLB 条目现在可以覆盖 2MB,显著提升内存密集型应用的性能。

6.2 TLB 击落 (TLB Shootdown)

在多核系统中,如果核心 A 更新了页表,核心 B 的本地 TLB 中可能仍保留着旧的转换。

  • 击落:核心 A 向所有其他核心发送处理器间中断 (IPI),强制它们刷新 TLB。这很昂贵,是主要的扩展瓶颈。

6.3 KSM (内核同页合并)

内核扫描 RAM 寻找完全相同的页面(例如运行相同 OS 的多个虚拟机),并将它们合并为一个标记为写时复制的单页。


7. 内存调试与监控

工具关注点关键概念
free -m系统级总量/可用 RAM
/proc/meminfo内核级详细分解(Slabs, HugePages)
valgrind应用级检测泄露和释放后使用 (use-after-free)
numastat硬件级检查 NUMA (非统一内存访问) 不平衡
pmap -x <pid>进程级显示特定进程的内存分段

8. 核心概念复习清单

  • 解释 4 级页表如何减少内存占用。
  • TLB 的作用是什么,为什么它至关重要?
  • 伙伴系统 vs Slab 分配器:哪个管理页,哪个管理对象?
  • 追踪一次缺页中断,从硬件异常到指令重启。
  • 为什么“抖动”会导致 CPU 利用率降低?

第 04 章结束。继续前往:第 05 章:文件系统