现代 Linux 内存管理的演进与变革:从传统 LRU 到多代架构 MGLRU

前言:

"内存回收"(Reclaim)是指寻找可以从当前使用者手中收回并投入到系统中更好用途的内存的任务,它是操作系统内存管理全局图景中的核心部分。

长期以来,Linux 内核一直依赖"传统 LRU"算法来维系内存的生存周期。然而,随着硬件规模的爆发,传统 LRU 逐渐不堪重负。为此,内核引入了全新的"多代架构 LRU(MGLRU)"。但这项本意为了提供更好回收实现的创新,如今却让内核代码的局面变得更加复杂。

在 2026 年 Linux 存储、文件系统、内存管理和 BPF 峰会(LSFMM+BPF)上,内存管理专题中足足有三个小节聚焦于 MGLRU,旨在更全面地整合它、提升其性能,并解决在 Android 系统中遇到的一些现实问题。本文将结合本次峰会的最新讨论,为您全面解构这场正在 Linux 内核深处上演的内存管理技术变革。

一、 传统 LRU:双回路的"活跃"与"非活跃"

传统的 LRU 算法在 Linux 中并非一个简单的线性链表。为了防止"预读垃圾"(例如某些文件仅被读取一次,随后便不再使用)污染链表,Linux 设计了一套双回路机制。

1. 核心架构:四大链表

传统 LRU 将内存页按状态和性质划分,核心维护着 4 个链表:

  • Active Anon / Inactive Anon(活跃/非活跃 匿名页:如进程堆栈、动态分配的数据)

  • Active File / Inactive File(活跃/非活跃 文件页:如页面缓存 Page Cache)

新产生的内存页通常先进入 Inactive(非活跃)链表。只有当它在非活跃链表中被再次访问时,内核才会将其升级(Promote)到 Active(活跃)链表。

2. 传统 LRU 的致命痛点

随着现代服务器内存迈入 TB 级、智能手机内存迈入 16GB+,传统 LRU 的固有缺陷开始暴露:

  • 粒度太粗(只有两代): 页面非黑即白,要么"活跃",要么"非活跃"。操作系统无法在庞大的物理内存中精准识别出哪些是"极热数据",哪些是"次热数据"。

  • 反向映射(rmap)开销巨大: 为了判断一个页面最近是否被访问,传统 LRU 必须通过反向映射(Reverse Mapping)去遍历所有引用该页面的进程页表项(PTE),检查其中的 Accessed 位。当大量进程共享内存或遭遇高并发时,这种遍历会引发可怕的锁竞争,导致系统在内存紧张时 CPU 飙升至 100%,引发系统抖动(Thrashing)甚至卡死。

  • 文件页与匿名页平衡困难: 内核依赖偏向静态的 swappiness 机制和一些特殊的启发式算法来决定回收哪类内存。这种"盲人摸象"的算法经常产生误判,例如错误地驱逐了频繁使用的文件缓存,导致系统频繁发生磁盘 I/O 停顿。

二、 MGLRU:精细化的"多代同堂"

为了彻底解决传统 LRU 的历史包袱,Google 工程师主导设计了 MGLRU(多代架构 LRU),并于近年正式合入 Linux 内核主线。

1. 核心原理:划分"世代"

MGLRU 颠覆了传统 LRU 的二元划分法,它引入了类似于人类社会的"世代(Generations)"概念(默认通常为 4 个世代,未来可扩展至更多)。

  • 最新一代(Youngest Generation): 存放刚刚被访问过、热得发烫的数据。

  • 最老一代(Oldest Generation): 存放长期无人问津的"冷数据"。

  • 内存回收时,内核只从"最老一代"中挑选页面驱逐。 随着时间的推移,页面会在世代之间平滑地"老化"或因再次访问而"重获新生"。

2. 颠覆性的技术改进

MGLRU 不仅仅是增加了链表数量,更在底层技术上进行了大刀阔斧的革新:

  • 页表扫描(Page-table Scanning): MGLRU 放弃了高开销的反向映射。它采用"正向线性扫描"进程页表的方式来收集页面的访问信息,这种方式对 CPU 缓存(Cache)极其友好,几乎没有锁竞争。

  • 布隆过滤器(Bloom Filter): 为了避免无脑扫描那些空闲或根本不活跃的页表区域,MGLRU 引入了布隆过滤器。如果过滤器判断某个内存区域近期没有访问,内核就会直接跳过扫描,极大降低了 CPU 损耗。宋楷瑞在 2026 峰会上指出,实验证明该过滤器对聚焦内存热点区域"非常有帮助"。

  • PID 控制器动态平滑: 在平衡文件页和匿名页的回收比例时,MGLRU 引入了现代工业控制中的 PID(比例-积分-微分)控制器,动态、平滑地根据近期重分配(refault)的代价来调整比例,避免了系统表现大起大落。

三、 传统 LRU 与 MGLRU 的直观对比

特性维度 传统 LRU (Traditional LRU) MGLRU (Multi-Gen LRU)
世代粒度 仅 2 代 (Active / Inactive) 多个世代 (通常 4 代,宋楷瑞正在研究扩展至 64 代)
活跃度检测 反向映射遍历 (rmap walk),高并发下锁竞争严重 页表正向扫描 (Page-table scan) + 布隆过滤器
高负载表现 容易触发锁饱和,导致 CPU 飙升、系统"抖动" 锁粒度极低,内存超发时系统依然能保持响应
水位线控制 持续驱逐页面直到达到低水位线(Lower Watermark) 采用更复杂的负载均衡算法
回收速度与 OOM 触发 OOM Killer 的速度较慢,容易导致长时间卡顿 触发 OOM 速度更快,但这被视作防止系统抖动的优秀特性
特殊保护机制 避免让可执行页面降级(Protect Executable Pages) 优先考虑通过 mmap() 映射的文件页
重分配页面处理 将快速重分配的页面直接移至活跃列表(Active List) 仅仅标记那些快速重分配的页面
核心应用场景 传统小内存架构、老旧/小众嵌入式设备(内核必须支持的"不重要架构") 现代海量内存服务器、云原生高并发环境、高超发布局的移动端(如 Android)

四、 2026 峰会焦点之一:统一回收代码的博弈

正如 Shakeel Butt 在峰会上尖锐地指出:"内存回收在内核中是一团糟。"目前,这两个完全独立的淘汰算法共同挤在长达 8000 多行的 mm/vmscan.c 文件中。

1. "一地鸡毛"的现状与挑战

  • 双重维护开销: mm/vmscan.c 中有 40% 是 MGLRU 特有的代码,它们与传统路径功能高度重复。每一次漏洞修复、优化或新特性都必须做两次,否则就只能对一半的用户生效。这被社区普遍认为是"不可持续的"。

  • 词汇与算法的割裂: Emil Tsalapatis 指出,两套实现有很多共同特性,却使用了不同的词汇。例如:

    • 重分配距离(refault distance): 计算方式截然不同,原因不明。传统 LRU 的实现"久经沙场",未来 MGLRU 可能会切换到传统路径。

    • 指标与计数器: 维护着不同的统计计数器,如果想共享代码,必须先统一计数器。

    • 平衡启发式策略: 传统 LRU 靠 swappiness 驱动,MGLRU 靠 PID 控制器。目前尚不清楚 PID 控制器是否真的带来了显著益处。

    • /proc/meminfo 指标波动: MGLRU 对活跃/非活跃指标管理不理想,数据容易出现"跳跃"(因为它将最年轻的两代视为活跃,世代老化时会导致数据剧烈波动)。

2. 社区的统一路线图

为了终结这种混乱,Shakeel Butt 提出了一个包含四个步骤的高层规划:

  1. 文件拆分: 将这两套代码库拆分到各自独立的文件中,而不是挤在一起。

  2. 定义工作负载: 收集并定义一套能够揭示算法优缺点的重要工作负载场景,用于评估回收补丁,防止性能退化。

  3. 寻找共同点: 识别两套实现之间的共同特性与可移植的启发式策略。

  4. 对比与抉择: 深入对比每个特性的具体实现,在两个选项之间做出取舍(例如尝试将页表扫描和反向映射在两个算法间对调,并使实现通用化)。

辩论火花:

拆分文件的计划在峰会上引发了激烈的讨论。Vlastimil Babka 担心分离文件会使统一任务复杂化;也有人担心搞乱 Git 提交历史(不过 Johannes Weiner 反驳称 MGLRU 反正也没多少 Git 历史)。Chris Li 担心大范围的代码重构会与宋楷瑞正在进行的许多优秀 MGLRU 改进补丁(如减少页面标志位、写回节流等)产生严重的冲突。

最终,Johannes Weiner 提出了折中方案:以宋楷瑞目前已经解决了很多指标和统一问题的补丁工作(如 MGLRU-FG 补丁集)作为起点,合并后再进行文件拆分。内核开发大牛 Lorenzo Stoakes 和 John Hubbard 达成一致,虽然短期内可能因为未知原因需要同时保留两者,但长远的终极目标是内核中只保留一个融合了两大架构优点的、唯一的回收实现。

五、 2026 峰会焦点之二:宋楷瑞的 MGLRU 进化蓝图

作为 MGLRU 领域的活跃核心,宋楷瑞主持了关于如何让 MGLRU"更好、更聪明"的专题。MGLRU 已经被许多主流发行版采用,证明了其通用价值,但它依然不是完美的。

1. 正在解决的缺陷

  • 写回节流(Writeback Throttling): 过去 MGLRU 缺失针对控制组(cgroup)的写回节流,目前正在修复,但全局写回节流依然缺失。

  • 页面缓存(Page Cache)退化: MGLRU 在处理拥有大量匿名页的工作负载时表现极其惊艳,但在极度依赖页面缓存的工作负载下可能会出现性能退化。宋楷瑞正在主导的 MGLRU-FG 补丁集旨在改进工作集检测,大幅提升页面缓存的保护力度。

  • 页面标志位(Page-flags)危机: 页面标志位在内核中极其稀缺。MGLRU 此前占用了 4 位,导致其很难在标志位更少的 32 位老旧系统上运行。MGLRU-FG 系列成功将使用量从 4 位减少到了 3 位,宋楷瑞表示可以进一步压榨到 2 位(虽然会有轻微性能影响),以确保它能运行在"所有我们不在乎的架构(老旧架构)"上。

  • 内存读取模型误判: MGLRU 过去错误地认为应用程序期望内存读取绝不发生停顿,从而对因缺页异常创建的页帧进行过快激活。但这并不符合通过 mmap() 映射的文件读取模型。目前 Barry Song 提出了补丁进行修复,而宋楷瑞倾向于直接移除这种行为。

2. 前沿实验与 BPF 扩展

在未来规划中,宋楷瑞展示了几个激进的想法:

  • 传统 LRU 兼容模式: 通过将 MGLRU 的世代和层级直接缩减到 2 个,让其直接模拟传统 LRU 的行为。

  • 64 代支持: 通过减少相关的页面标志位使用,未来让 MGLRU 支持高达 64 个世代(虽然 Matthew Wilcox 询问 64 代能好多少时,宋坦言并无确切答案,但实验成本极低)。

  • BPF 强力介入: 引入 BPF(Berkeley Packet Filter)挂钩。允许管理员编写 BPF 程序,动态决定发生缺页的页帧应该放入哪一代,或者在页面被访问时将其移至特定的世代。这将允许系统管理员完全自定义内核的内存回收策略(不过会场有听众暗示这可能有些大材小用)。

六、 2026 峰会焦点之三:移动端巨头的现实控诉------Android 上的 MGLRU

来自荣耀(HONOR)的王自成在峰会上分享了来自工业界一线、覆盖多达 7000 万台高负载智能手机设备的真实数据。移动端对内存回收有着近乎变态的残酷要求,这也彻底暴露了当前标准 MGLRU 的一些阿喀琉斯之踵。

1. 毫秒级时间预算与掉帧危机

Android 系统的特点是严重超发(Overcommits)内存 ,极度依赖激进的内存回收来维持流畅度。然而,在 120 帧/秒的现代屏幕上,回收操作必须死死卡在 8.3 毫秒 的时间预算内。任何超过该时间的阻塞都会导致 UI 渲染掉帧、卡顿。

2. 标准 MGLRU 在 Android 上的四大罪状与荣耀的"魔改"

  • 误杀前台文件页,相机启动变慢: Android 倾向于在应用启动早期预加载大量页面,然后靠激进回收清空垃圾。但 MGLRU 经常回收了过多的文件页,导致前台应用(如相机)在需要时必须重新发生缺页读入,拉高启动延迟。

    • 荣耀的解法: 引入主动老化(Active Aging)机制。当应用切到后台时,定向加速其页面老化,从而将匿名页合理分布到各个世代中;同时加入挂钩,在回收期间直接跳过前台应用
  • 打碎时间预算,超额回收: MGLRU 在达到目标水位线后依然会不管不顾地继续回收页面,这直接打破了 8.3ms 的时间限制。

    • 荣耀的解法: 强行添加了一个特定的挂钩,当时间或水位满足时,强行命令 MGLRU 退出
  • kswapd 内核线程无用功,引发线程停顿: 在直接回收(Direct Reclaim)中,进程经常被强制节流进入睡眠,等待 kswapd 释放内存。然而 kswapd 却把大量时间浪费在扫描那些根本榨不出油水的控制组(cgroup)上。

    • 现状: 荣耀目前对此也没有真正的解决方案
  • 预读机制反噬: 内核预读(Read-ahead)代码带入的页面会被自动激活,这经常激活了大量永远不会被使用的垃圾页面,反而排挤了应用程序真正需要的核心页面。

3. 来自业界的呼吁:从"魔改"走向"通用接口"

王自成在会议最后发出呼吁:荣耀不希望一直使用这些供应商特定的魔改(vendor-specific hacks)。内核社区应当为 MGLRU 引入更好的通用接口

  • 将控制老化的参数暴露出来。

  • 将目前锁在 debugfs 中的控制节点移到生产系统可用的 sysfs 中。

  • 最重要的一点: 让 MGLRU 必须具备感知系统运行任务优先级(Task Priority)的能力,不能对前台高优任务和后台冻结任务一视同仁。

由于时间耗尽,该专题小节在全场若有所思的沉默中结束。

七、 结语

从传统 LRU 的"非黑即白",到 MGLRU 的"多代同堂",Linux 内存管理的演进是现代硬件需求与极端业务场景(如移动端严重超发)倒逼软件架构创新的典型范例。

2026 年的这场峰会向我们清晰地展示了:新技术的落地从来不是一蹴而就的。MGLRU 虽然在多核和大内存上表现优异,但它带来了代码冗余、策略冲突以及在特定高响应工作负载(如 Android 8.3ms 预算)下的不适应。随着宋楷瑞等核心开发者对页面缓存保护、标志位优化的推进,以及整个内核社区对 mm/vmscan.c 大刀阔斧的解耦重构,未来的 Linux 内存回收机制终将走向大一统------变成一个既具备多代精准识别能力,又兼顾通用性与工业界微调灵活性的单一高效内核子系统。

相关推荐
赵渝强老师2 小时前
【赵渝强老师】Kubernetes(K8s)中的金丝雀升级
linux·docker·云原生·容器·kubernetes
Qt程序员2 小时前
Linux RCU 原理与应用
linux·c++·内核·linux内核·rcu
The Sheep 20232 小时前
Vue复习
linux·服务器·数据库
兄台の请冷静2 小时前
Linux 安装es
linux·elasticsearch·jenkins
fengyehongWorld2 小时前
Linux rg命令
linux
pride.li3 小时前
海思视觉Hi3516CV610--开机自动设置ip
linux·网络·网络协议·tcp/ip
我叫张小白。3 小时前
CentOS 7 安装 Docker并配置镜像加速(完整指南)
linux·docker·centos
源图客3 小时前
Minio配置HTTPS服务
服务器·网络协议·https
修炼室3 小时前
外网环境原生直连校内服务器:基于内网穿透 + SSH 密钥认证的完整实践指南
服务器·ssh·php