linux内核 Multi-Gen LRU 算法

Multi-Gen LRU(Multi-Generation Least Recently Used)是 Linux 内核为解决传统 LRU 算法在大内存场景下的低效问题 而引入的内存回收优化机制,核心目标是提升内存页活跃度判断的精准度和回收效率。该算法首次在 5.18 内核 中正式合入主线,后续在 5.19+ 版本中持续优化,4.4/5.4 等老内核无原生支持(需通过 backport 补丁移植)。

传统 LRU 算法的痛点

传统 Linux 内核的内存回收依赖单一代际的 LRU 双向链表 ,将物理页分为 active_list(活跃页)和 inactive_list(非活跃页),核心逻辑是:

  1. 页被访问时,从 inactive_list 提升至 active_list
  2. 内存不足时,kswapdinactive_list 尾部回收页;
  3. 通过 page_referenced() 函数扫描页的访问位,判断是否需要重新激活。

大内存场景下的核心痛点

在大内存**(如 128GB+)** 场景中,物理页数量可达 数千万甚至数亿级(按 4KB 页计算,128GB 对应 3200 万页),传统 LRU 的设计缺陷会被急剧放大:

全局链表扫描效率极低

这是传统 LRU 最核心的瓶颈。

  • 线性扫描的开销kswapd 回收内存时,需要线性遍历 inactive_list,判断每个页是否可回收(如是否为脏页、是否被锁定)。大内存下链表长度可达百万级,单次扫描会消耗大量 CPU 周期,导致回收延迟高达数百毫秒甚至秒级。
  • 访问位检测的额外开销 :为了判断页的活跃度,内核需要调用 page_referenced() 扫描页对应的所有页表项 (支持多进程共享的页),检查硬件的访问位(PG_referenced)。大内存下,这个操作的时间复杂度会随页数量线性增长,进一步加剧 CPU 消耗。
  • 极端情况 :当系统内存紧张时,kswapd 会长期占用 CPU(出现 kswapd 高负载),导致用户态进程被抢占,业务响应延迟增加。

全局锁竞争激烈

传统 LRU 依赖 全局 lru_lock 保护 active_listinactive_list 的操作(如添加、删除、迁移页)。

  • 多核场景的冲突 :在多 CPU 服务器中,多个进程并发申请 / 释放内存、触发页访问时,都会竞争 lru_lock。大内存下页操作的频率更高,锁冲突会导致严重的上下文切换,降低系统整体吞吐量。
  • 锁粒度问题:全局锁的粒度太大,无法并行处理不同区域的页,完全无法发挥多核 CPU 的优势。

活跃度判断精准度不足

传统 LRU 的双链表模型无法区分页的 "短期活跃" 和 "长期活跃",导致两种误判:

  • 误回收 "低频重要页" :例如,某些后台服务的页可能几小时才访问一次,会被降级到 inactive_list 并被回收。当服务再次访问时,会触发大量缺页异常(Page Fault),需要从磁盘 / 交换分区读取数据,导致服务卡顿。
  • 误保留 "短期活跃页" :某些一次性任务的页(如临时文件缓存)会被短暂访问并晋升到 active_list,长期占用内存,导致真正需要内存的进程无法获取资源。

内存回收的 "饥饿" 问题

大内存场景下,kswapd 的回收速度可能跟不上内存消耗速度 ,导致系统触发 OOM Killer

  • kswapd 扫描速度过慢,内存水位持续低于 min,内核会认为 "内存回收无效",直接启动 OOM Killer 杀死占用内存大的进程。
  • 这种情况在高并发业务(如数据库、大数据计算)中尤为致命,可能导致核心服务被意外终止。

对系统性能的具体影响

在大内存服务器(如 256GB 内存的数据库主机)上,传统 LRU 的缺陷会直接体现在以下指标上:

  1. CPU 利用率异常kswapd 线程的 CPU 占比可达 20%~50%,挤占业务进程的资源。
  2. 页缺失率升高 :误回收导致大量主缺页异常(Major Page Fault),磁盘 IO 飙升(iostat 显示 rMB/s 突增)。
  3. 响应延迟增加:业务进程因内存不足被阻塞,或因缺页异常等待磁盘 IO,导致接口响应时间从毫秒级升至秒级。
  4. 系统稳定性下降:频繁触发 OOM Killer,或因锁竞争导致进程死锁、内核软锁(Soft Lockup)。

Multi-Gen LRU 的核心设计思想

Multi-Gen LRU 的核心是将物理页按 "访问代际" 分层管理,通过代际差异快速区分页的活跃度,从而精准、高效地回收 "冷页"。

核心概念:代际(Generation)

Multi-Gen LRU 将所有物理页划分为多个代际,每个代际用一个独立的链表管理,代际编号越大,代表页的 "闲置时间越长"(活跃度越低)。

  • 代际划分规则
    1. 新分配的页加入第 0 代(最活跃代);
    2. 当页在一段时间内未被访问,会被晋升到更高代际
    3. 若页在高代际被重新访问,则降级回低代际(如第 0 代);
    4. 内存回收时,优先从最高代际的链表尾部回收页
  • 代际数量 :可通过内核参数 vm.mg_lru_max_gen 配置,默认一般为 16~32 代。

核心机制:分层管理与快速老化

页的代际迁移

Multi-Gen LRU 为每个页添加了代际标记 (扩展 struct page 字段),并通过两个核心操作实现代际迁移:

  • 老化(Ageing):内核定期扫描各代际的页,若页未被访问,则将其移入更高代际;若被访问,则重置为第 0 代。
  • 回收(Reclaim)kswapd 优先回收最高代际的页,只有当高代际无页可回收时,才会扫描低代际。

这种分层机制避免了传统 LRU 的全局链表扫描,大幅降低了 CPU 开销。

访问位优化:避免全局扫描

传统 LRU 依赖 page_referenced() 遍历页表项的访问位(PG_referenced),而 Multi-Gen LRU 做了两点优化:

  1. 批量更新访问位 :通过内存管理单元(MMU)的硬件特性(如 Intel 的 PCID、ARM 的 ASID)批量刷新页的访问状态,无需逐页扫描;
  2. 代际内局部扫描:仅在当前代际内判断页的活跃度,而非全局链表。

锁粒度优化:从全局锁到代际锁

传统 LRU 使用全局 lru_lock 保护链表操作,Multi-Gen LRU 则为每个代际配置独立的锁,多核场景下不同 CPU 可并行处理不同代际的页,锁竞争大幅减少。

与传统 LRU 的兼容性

Multi-Gen LRU 并未完全替换传统 LRU,而是作为增强功能叠加

  • 保留了 active_listinactive_list 的逻辑,代际管理在 inactive_list 内部实现;
  • 支持通过内核参数 vm.mg_lru_enabled 动态开关(1 开启,0 关闭)。

Multi-Gen LRU 的关键优势

  • 回收效率提升:优先回收高代际冷页,减少无效扫描,降低内存回收的 CPU 占用(实测大内存场景下可降低 30%~50% 回收耗时);
  • 活跃度判断更精准:代际分层能区分短期活跃和长期活跃页,减少 "误回收" 现象,提升系统稳定性;
  • 多核友好:代际锁机制降低了锁竞争,适合高并发服务器场景;
  • 可配置性强:通过内核参数调整代际数量、老化周期等,适配不同硬件和业务场景。

与其他内存回收特性的协同

  • 透明巨页(THP):Multi-Gen LRU 支持巨页的代际管理,5.19+ 内核优化了巨页的拆分与回收逻辑;
  • Zswap/Zram:与压缩交换分区协同,优先回收高代际页并压缩存储,提升交换效率;
  • Page Migration:内存热插拔场景下,Multi-Gen LRU 可优先迁移低代际活跃页,减少服务中断。
相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室11 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy13 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕14 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫15 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1117 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯19 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983419 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器