当前位置:首页>Linux>Linux 内存管理的 5 条设计哲学

Linux 内存管理的 5 条设计哲学

  • 2026-10-11 01:41:15
Linux 内存管理的 5 条设计哲学

Linux 内存管理的 5 条设计哲学

你给服务器加了 64GB 内存,以为不会再有内存压力。结果一周后 free 显示 MemFree 还是只有几百 MB,系统反而比之前 16GB 时更慢了。

同事抱怨:"内存都去哪了?是不是泄漏了?" 你打开 vmstat,发现 si/so 为 0,但 bi/bo 高得吓人。原来 Linux 内核不是"不会用内存",而是在下一盘更大的棋——用你加的那 64GB 做缓存、做压缩、做预取,只是每一种设计都有代价。

本文总结 Linux 内存管理的 5 条核心设计哲学,帮你理解系统为什么"有时候不卡"、"有时候又突然卡"。

一、以空间换时间:最常见的缓存策略

本章结论:用 RAM 空间避免重复 I/O 或重复计算,是内核缓存最主流的设计哲学。

当你打开一个文件,Linux 不会每次都从磁盘读取。它会先把文件内容读到 RAM 里的 Page Cache,后续再读就直接命中。这就是最经典的"以空间换时间"——用 RAM 空间,换磁盘 I/O 时间。

同样的逻辑在 Swap Cache 中也一样:页被 swap-in 回到 RAM 后,保留 swap entry 不释放,后续再次访问时直接命中,免去了从 zRAM 解压或从磁盘读盘的成本。

Page Cache:磁盘文件 → RAM(换 I/O 时间)
Swap Cache:swap 后端 → RAM(换 I/O 时间)
Dentry Cache:磁盘路径 → RAM(换路径查找时间)
Buffer Cache:块设备数据 → RAM(换块 I/O 时间)

共同点:"我先把一份副本放在 RAM 里,你要的时候直接拿。"

代价:RAM 占用增加。当内存紧张时,这些缓存页会被回收——因为它们的后端都有备份,回收成本极低。

二、以时间换空间:压缩与合并的艺术

本章结论:用 CPU 时间减少内存占用,是内存紧张时的主动策略。

zRAM 是典型的"以时间换空间":

阶段 行为 代价 收益
swap out 压缩 → 写入 zRAM CPU 时间 内存占用 ↓
swap in 从 zRAM 解压 → RAM CPU 时间 获得原始页

如果没有 Swap Cache,页被回收后要压缩,又被访问时要解压,往复震荡——CPU 时间白白浪费。Swap Cache 的作用,就是在"以时间换空间"的压缩/解压之间,插入一个"以空间换时间"的缓冲层,避免重复支付压缩/解压的 CPU 成本。

类似的机制还有:

机制 "时间"花在哪里 "空间"省在哪里
zRAM 压缩/解压 CPU 减少 RAM 占用
透明大页(THP) 合并/拆分 CPU 减少 TLB miss,提升大页命中率
KSM (内存去重) 扫描+比较 CPU 合并相同内容页,减少重复数据

关键洞察:"以时间换空间"不是"浪费 CPU",而是在内存紧张时主动用 CPU 资源换取更多可用内存。

三、懒惰策略:不到万不得已不做

本章结论:Linux 内核大量采用"懒"策略——延迟执行直到不得不做,是性能优化的核心手段。

机制 "懒"在哪里 效果
延迟分配 malloc() 不立刻分配物理页 进程启动快,但写入时可能 OOM
COW (Copy-on-Write) fork() 时不复制父进程内存 子进程秒启动,写入时才真正复制
页错误处理 缺页时才加载内容到 RAM 程序启动快,但运行时可能有卡顿
Swap Cache 懒建立 swap out 时不建立,swap in 时才建立 只有被访问的页才有 Swap Cache

Swap Cache 的"懒建立"是最典型的例子:页被 swap out 时,内核不会为它建立 Swap Cache。只有当进程再次访问、触发缺页中断、从 zRAM/磁盘读回数据后,才会建立 Swap Cache 映射。

核心理念:"不到万不得已不做,做了就缓存起来。"

四、预取与预读:赌你会用,提前准备

本章结论:预取是"以时间换空间"的反面——用带宽和缓存空间赌未来的命中率。

readahead:你读第 1 页,内核预读第 2-4 页
prefetch:CPU 预测你接下来要用某段数据,提前加载到 L1/L2
预取类型 触发时机 赌什么 赌赢省什么 赌错浪费什么
文件预读(readahead) 顺序读取文件时 你会继续读下去 磁盘 I/O 时间 带宽和 Page Cache 空间
CPU 预取(prefetch) 循环访问数组时 你会访问下一个元素 内存访问延迟 缓存行和总线带宽

Swap Cache 不参与预取——它是"事后补救",不是"事前预测"。

关键洞察:预取和 Swap Cache 是互补的。预取是"我猜你会用",Swap Cache 是"你已经用过了,我帮你留着"。

五、局部性感知:把"附近的"放在一起

本章结论:利用访问局部性,把相关数据放在物理上接近的位置,是缓存设计的底层假设。

机制 局部性类型 作用
SLAB/SLUB 空间局部性 相同大小的对象放在一起,减少碎片,提升分配效率
NUMA 本地分配 空间局部性 内存分配在本地 CPU 节点,避免跨节点访问延迟
透明大页(THP) 空间局部性 连续小页合并成大页,减少 TLB miss
CPU 缓存对齐 空间局部性 数据按缓存行对齐,避免 false sharing

Swap Cache 虽然没有直接的局部性优化,但它利用了时间局部性:刚被访问过的页,很可能马上又被访问。保留它在 RAM 中,就是利用了"最近被访问的页,未来更可能被访问"的假设。

关键洞察:局部性感知是所有缓存策略的底层假设。没有局部性,缓存就失去意义。

源码参考

函数 / 符号 所在文件 作用
read_swap_cache_async() mm/swap_state.c swap-in 路径入口,为目标预留 Swap Cache 空间
__swap_cache_add_folio() mm/swap_state.c 将 swap entry 关联到 folio,更新 NR_SWAPCACHE
swap_cache_get_folio() mm/swap_state.c 按 swap entry 查找已驻留的 folio
readahead() mm/readahead.c 文件预读,提前加载后续页到 Page Cache
__do_page_fault() arch/x86/mm/fault.c 页错误处理,延迟分配 + COW 实现
ksm_scan_thread() mm/ksm.c KSM 内存去重,扫描并合并相同内容页
alloc_pages() mm/page_alloc.c 物理页分配,支持 NUMA 本地分配
kmem_cache_alloc() mm/slab.c / mm/slub.c SLAB/SLUB 分配器,按大小分桶

全文方法总结:5 条设计哲学的取舍矩阵

设计哲学 核心策略 典型场景 权衡
以空间换时间 RAM 空间 → 避免重复 I/O Page Cache、Swap Cache、Dentry Cache 空间 ↑,时间 ↓
以时间换空间 CPU 时间 → 减少内存占用 zRAM、THP、KSM 时间 ↑,空间 ↓
懒惰策略 延迟执行 → 减少不必要开销 延迟分配、COW、懒建立 启动快,突发开销
预取/预读 带宽/空间 → 赌未来命中 readahead、prefetch 赌对省时间,赌错浪费
局部性感知 物理邻近 → 提升命中率 SLAB、NUMA、THP、缓存对齐 命中率高,碎片可能增加

思考题

Q4:如果没有 Swap Cache,只有 zRAM 会怎样?

zRAM 压缩是典型的"以时间换空间",Swap Cache 是典型的"以空间换时间"。如果系统中没有 Swap Cache,只有 zRAM,会发生什么?这俩为什么必须配合?

没有 Swap Cache 的后果:

  1. 页被回收 → 压缩到 zRAM(花时间)
  2. 页又被访问 → 从 zRAM 解压(花时间)
  3. 页再次被回收 → 再次压缩(花时间)
  4. 往复震荡,CPU 时间白白浪费

Swap Cache 的作用:

阶段 有 Swap Cache 无 Swap Cache
第一次 swap out 压缩到 zRAM 压缩到 zRAM
再次访问 直接命中 Swap Cache 从 zRAM 解压
再次回收 直接丢弃 再次压缩到 zRAM
CPU 成本 低(只压缩一次) 高(反复压缩/解压)

核心结论:Swap Cache 是"以空间换时间"对"以时间换空间"的补偿机制。zRAM 压缩用 CPU 换内存,Swap Cache 用 RAM 换 CPU,二者配合才构成完整的内存回收策略。

Q5:内存紧张时,优先回收预取的 Page Cache 还是 Swap Cache?

预取(readahead)和 Swap Cache 都是缓存机制,但一个是"事前预测",一个是"事后补救"。如果系统内存极度紧张,应该优先回收预取的 Page Cache 还是 Swap Cache?为什么?

优先回收预取的 Page Cache。

判断依据:

缓存类型 本质 回收成本 回收策略
预取的 Page Cache "我猜你会用" 低(干净页直接丢弃) 优先回收,因为"猜错"了
Swap Cache "你已经用过了" 极低(直接丢弃) 也直接丢弃,但保留价值更高

为什么预取的 Page Cache 优先:

  1. 预取是"猜测":预取的内容可能是"猜错"的,用户根本不需要
  2. Swap Cache 是"事实":页已经被访问过,再次被访问的概率更高
  3. 时间局部性:刚被访问过的页,未来更可能被访问

实际回收顺序:

kswapd 扫描 inactive 链表:
  → inactive file(Page Cache)
    → 预取的 Page Cache:优先丢弃(成本低,且可能猜错)
    → 非预取的 Page Cache:其次
  → inactive anon(匿名页)
    → Swap Cache:直接丢弃(保留价值高,但后端有备份)
    → 普通匿名页:swap out(成本高)

核心结论:预取是"赌博",Swap Cache 是"保险"。内存紧张时,先清掉"赌错"的,保留"有 backup"的。

最新文章

随机文章