引言
Linux 内核的内存管理系统是整个操作系统的核心组件之一。当系统内存紧张时,内核必须在有限的时间内做出关键决策——如何回收内存、选择哪些页面换出、以及最后关头如何处理 OOM(Out-Of-Memory)状况。本文将从内核源码层面深入解析 Linux 的页面回收机制、LRU 算法演进、页面置换策略,以及 OOM Killer 的决策逻辑,并结合 2026 年最新内核特性进行分析。
一、页面回收(Page Reclaim)的整体架构
Linux 内核的页面回收路径涉及多个子系统的紧密协作。当系统内存低于特定阈值时,内核会通过以下路径触发回收:
kjournald / userspace process
↓
balance_pages() [mm/vmscan.c]
↓
kswapd (后台守护进程) ← → 直接回收 (Direct Reclaim)
↓
shrink_node() -> shrink_lruvec() -> shrink_list()
全局内存回收的核心入口位于 mm/vmscan.c,其中定义了三个关键水位标记(watermark):
- WMARK_MIN(min):内存紧急阈值,低于此值系统将阻塞进程进行直接回收
- WMARK_LOW(low):kswapd 唤醒阈值,后台守护进程开始异步回收
- WMARK_HIGH(high):系统健康阈值,kswapd 认为内存充足后可休眠
这些水位由内核根据各 NUMA 节点的物理内存总量动态计算,init_per_zone_wmarks() 函数将各区的最小空闲内存值按比例分配到三个水位,确保大内存节点和小内存节点均能得到合理的回收触发点。
二、LRU 算法的演进:从传统 LRU 到多级 LRU
2.1 传统 LRU 的局限性
经典 LRU(Least Recently Used)使用两个链表(active/inactive)维护页面热度。但这种方法存在两个根本问题:
- 冷热页面判断滞后:页面首次加入 inactive 链表,只有在第二次访问时才能提升到 active,这意味着单次扫描页面可能永远无法进入 active 列表
- 链表维护开销大:在高内存系统中,LRU 链表可能包含数百万个页面,链表操作本身就是瓶颈
2.2 Multigen LRU(MGLRU):Linux 6.1+ 的革命性突破
自 6.1 版本起引入选项式的 Multigen LRU,由 Google 工程师 Yu Zhao 主导开发。它解决了传统 LRU 的固有缺陷:
// 核心数据结构:多代 LRU
struct lru_gen_folio {
unsigned long blooms[BLOOM_FILTER_SIZE]; // Bloom 过滤器快速判断访问
unsigned long folios[EVICTION_SIZE]; // 待驱逐页面候选
int nr_folios; // 候选数量
unsigned int gen; // 当前代数
};
MGLRU 的核心理念是用年龄(generation)取代精确的访问顺序:
- 新页面加入最年轻的 Generation(gen 0)
- 周期性"老化":每次扫描将所有页面 age+1,但在活跃访问路径上重置回当前 gen
- 驱逐时优先回收最老 generation 中的页面
- 用 Bloom 过滤器以 O(1) 时间复杂度跟踪页面访问历史,无需全链表扫描
Google 内部大规模部署数据显示,MGLRU 将页面失效率(page fault rate)降低了约 25%,在数据库负载和容器密度场景下效果尤为显著。
2.3 Bloom 过滤器的妙用
MGLRU 在 Bloom 过滤器中编码页面的多维历史信息——不仅记录"是否被访问",还记录"在第几代被访问"。这使得驱逐决策不仅考虑页面最近使用时间,还综合考虑页面的访问频率和模式,比传统 LRU 的冷热二分法更加精细。
// Bloom 过滤器判断页面是否在某一代被访问过
static bool lru_gen_test_access(struct lruvec *lruvec, unsigned long nr_pages, int gen)
{
struct lru_gen_folio *lrugen = &lruvec->lrugen;
// Bloom 查找: O(1) 检查页面在第 gen 代是否被引用
return test_page_bloom(lrugen, nr_pages, gen);
}
三、页面类型与回收策略
3.1 匿名页 vs 文件页
Linux 将待回收页面分为两大类,采用截然不同的回收路径:
- 匿名页(Anonymous Page):来源于进程堆/栈/匿名 mmap 映射。脏页必须写入 Swap 设备后才能释放,回收优先级较低(换出代价高),扫描成本低(无需 I/O)
- 文件-backed 页(Page Cache):来自文件 mmap 或 read/write 缓冲。干净页可直接丢弃回收,回收优先级较高,脏页扫描可能引发 I/O 等待
内核通过 get_scan_count() 计算对 folio 类型列表的扫描强度比值(对应 /proc/sys/vm/swappiness)。在数据库服务器上通常设置 swappiness=1,尽量避免匿名页换出。
3.2 Folio 机制:Linux 5.16+ 的复合页抽象
2022 年引入的 folio 机制统一了 compound page 的接口,使基础内存操作统一在"可能的大页"层面进行,降低了 LRU 链表 .scan 中频繁 split/join 的 overhead。2026 年,几乎所有 core VM 路径(swap、page cache、THP)已完全迁移到 folio 入口。
四、Swap 系统与 zRAM/zSwap
4.1 zSwap 内存压缩缓冲
zSwap 将待换出的页面压缩后存储在内存缓冲区中,只有当缓冲区满后才真正写入磁盘 Swap。在内存受限的嵌入式/容器场景中,额外的内存通过压缩比率换取(典型压缩比 2:1 到 3:1),有效减少 I/O 阻塞。
4.2 Swap Readahead 自适应关闭
在 NVMe/SSD Swap 上,预读(swapin_readahead)的正收益减小,反而可能引入额外的 I/O 碎片。Linux 6.6+ 内核将此策略优化为仅在顺序访问模式下启用,随机访问模式直接逐页换入,避免无效 I/O。
五、Shrinker 与可回收内核内存
5.1 shrink_slab 路径
当匿名页和文件页仍无法满足需求时,内核会调用注册在各子系统的 shrinker,回收以下内核内存:
- dentry cache / inode cache:目录项和 inode 条目缓存,通过
slabtop可观察其大小 - BTRFS/XFS 元数据:文件系统专用缓存
- TCP/drm/i915:特定驱动注册的内核内存池
- bpf/ksm 等子系统:日益增多的内核子系统注册 shrinker
5.2 cgroup-aware Shrinker(Linux 6.7+)
新式 shrinker 从全局链表迁移到 cgroup 感知结构体,使容器环境中的内存回收能够独立地为每个 cgroup 计算压力并维护公平性,解决了全局 shrinker 导致容器间接互相干扰的问题。
六、OOM Killer 及其替代路径
6.1 OOM Badness 评分机制
当系统彻底无法回收足够内存时,out_of_memory() 调用 oom_badness() 函数对每个进程评分:
- 基础分由 RSS + Swap 占用决定,长期运行进程通过平方根函数增速放缓
- 特权级调整:SYS_ADMIN / SYS_RAWIO 进程轻微保护(分数降低 3%)
- oom_score_adj:用户可配置偏移量 [-1000, 1000],-1000 表示完全豁免 OOM 终止
6.2 PSI:用压力信息取代 OOM 滞后响应
Linux 4.20 引入的 Pressure Stall Information (PSI) 改变范式:不再等 OOM 发生,而是在任务被阻塞前就采取预防措施。通过 cgroup v2 的 memory.pressure 配置阈值并注册 eventfd 通知,可以在真正的失速触发前进行干预——驱逐容器、调度降级、弹性伸缩。
七、2026 年新趋势:Container-Aware Reclaim
7.1 MGLRU + Memory Tiering 联合调度
Linux 7.0 引入flat tier demotion:多级内存(DRAM + CXL 或 PMem)的战略降级与 MGLRU 的页面 age 机制直接打通。gen_age 直接决定页面应降级到哪一 Tier,不再需要启发式扫描判断热度,大幅降低了 Tier 间迁移的延迟和不确定性。
7.2 Memory Qos 云原生化
6.13 内核中的 Memory Quality of Service 接口已作为虚拟设备在多个公有云厂商暴露,允许 guest VM 向 Hypervisor 反馈其内存使用模式,与 QEMU virtio-mem 联动完成产品化落地。
八、实战性能调优速查表
不同场景的推荐 sysctl 配置:
- 数据库服务器:
vm.swappiness=1、vm.vfs_cache_pressure=50、vm.watermark_scale_factor=200(更积极回收文件页) - 容器/Pod 环境:
vm.swappiness=0、vm.vfs_cache_pressure=100、cgroup v2 设置memory.low保障关键容器带宽 - 桌面/开发机:
vm.swappiness=60(默认)、MGLRU enabled、CPU 密集型 workload 设置oom_score_adj=-500保护编译进程 - 大内存服务器(64GB+):
vm.swappiness=10、vm.vfs_cache_pressure=200、vm.watermark_scale_factor=50(减少后台回收开销)
结语
理解并优化 Linux 内核的内存回收机制,是高性能系统运维和工程化的基石。从传统 LRU 的双链表到 MGLRU 的 Bloom 过滤器与多代年龄模型,再到 PSI 压力预判机制和 cgroup-aware 公平回收,Linux 内核的内存管理策略正变得越来越精细、可预测。在容器化、异构内存等新硬件拓扑下,内存回收不再是单一阈值问题,而是一个涉及多层级、多维度的系统工程。

发表评论 取消回复