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 可优先迁移低代际活跃页,减少服务中断。
相关推荐
强风7942 小时前
Linux-线程的同步与互斥
linux·服务器
提伯斯6462 小时前
Orangepi R1内置了哪些网卡驱动?(全志H3的板子)
linux·网络·wifi·全志h3
技术摆渡人2 小时前
专题二:【驱动进阶】打破 Linux 驱动开发的黑盒:从 GPIO 模拟到 DMA 陷阱全书
android·linux·驱动开发
wishchin3 小时前
Jetson Orin Trt: No CMAKE_CUDA_COMPILER could be found
linux·运维·深度学习
ArrebolJiuZhou3 小时前
03 rtp,rtcp,sdp的包结构
linux·运维·服务器·网络·arm开发
403240733 小时前
Ubuntu/Jetson 通用:NVMe 硬盘分区、挂载及开机自动挂载完整教程
linux·运维·ubuntu
田地和代码3 小时前
linux应用用户安装jdk以后 如果root安装hbase客户端需要jdk还需要再次安装吗
java·linux·hbase
乔碧萝成都分萝3 小时前
二十四、Linux如何处理中断
linux·驱动开发·嵌入式
真的想上岸啊3 小时前
2、刷机+mobaxterm登录
linux