内存管理-75-lru_gen-1-理论

基于 Linux-6.1.115

 

一、LRU_GEN 简介

1. 一句话概括

CONFIG_LRU_GEN 启用 Multi-Gen LRU(多代 LRU),它用一套基于"代"(generation)的页面老化和淘汰机制, 完全替代传统的 active/inactive 双链表 LRU,目标是更精确地识别冷热页面,在内存过量使用(overcommit)场景下显著提升回收效率和性能。


2. 传统 LRU 的问题

传统 LRU 只有两个链表:active 和 inactive。这带来三个固有缺陷:

------------------------------------------------------------------------------------------------------------------------
问题            表现
------------------------------------------------------------------------------------------------------------------------
精度粗糙        仅区分"活跃/非活跃"两个状态,无法区分"刚刚访问""很久以前访问过一次"
时钟精度差      依赖 PG_referenced + PG_active 两位标志做近似 LRU,但这只是"最近是否被访问"的布尔值,不是真正的时间序列
大扫描开销      在内存压力下需要扫描整个 inactive 链表来找冷页,扫描量与链表长度线性相关
工作集保护弱    面对突发大文件读取(streaming IO),整个 active 链表可能被冲刷,工作集保护不足
------------------------------------------------------------------------------------------------------------------------


3. MGLRU 的核心设计

3.1 代(Generation)替代双链表

传统 LRU:       Active <--> Inactive(2 个状态)

MGLRU:          Gen 0 (最老) --> Gen 1 --> Gen 2 --> Gen 3 (最年轻)
                   ↑ 淘汰方向                           ↑ 新页进入方向
                 min_seq                               max_seq

页面被分配/访问时加入最年轻的代(max_seq),老化过程将页面从年轻代"滑"向年老代,淘汰从最老的代(min_seq)开始选取。

// mmzone.h
#define MIN_NR_GENS   2U   //最少保持 2 代(对应 active/inactive 兼容性)
#define MAX_NR_GENS   4U   //最多 4 代(支持更精细的冷热区分)


3.2 核心数据结构

struct lru_gen_folio {
    unsigned long max_seq;                    //最年轻代的序号(全局递增)
    unsigned long min_seq[ANON_AND_FILE];     //最老代的序号(匿名/文件分别维护)
    unsigned long timestamps[MAX_NR_GENS];    //每代的"出生时间"

    //核心链表:folios[代][匿名/文件][zone]
    struct list_head folios[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];

    //每代的页数统计
    long nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES];

    //refault 统计(用于工作集保护决策)
    unsigned long avg_refaulted[ANON_AND_FILE][MAX_NR_TIERS];
    unsigned long avg_total[ANON_AND_FILE][MAX_NR_TIERS];
};


3.3 层(Tier)— 更精细的热度区分

在"代"内部,页面还根据被文件描述符访问的次数分为多个层:

#define MAX_NR_TIERS   4U
// Tier = order_base_2(访问次数 N)
// Tier 0:N=0,1(冷,用 PG_referenced 标记)
// Tier 1:N=2,3(温,PG_referenced + PG_workingset)
// Tier 2:N=4..7
// Tier 3:N=8+

代(generation):基于时间的老化,通过页表扫描(walk_pte_range)驱动;
层(tier):基于访问频率,通过 folio->flags 中的 reference 位免锁递增;


3. 两大核心操作

3.1 老化(Aging)— inc_max_seq / try_to_inc_max_seq

目的:推进 max_seq,创建新的年轻代
时机:当最老代中的页不够淘汰时,需要老化来产生更多候选页

流程:
(1) 遍历进程的 mm_struct 列表;
(2) 通过 walk_pte_range() 扫描页表;
(3) 检查每个 PTE 的 accessed bit (硬件自动置位);
(4) accessed=1 的页:清除 accessed bit,提升到年轻代 <-- "续命";
(5) accessed=0 的页:不动,自然留在当前代 <-- "老化";
(6) max_seq++ 创建新一代;

这比传统 LRU 的优势:
(1) 不扫描页本身,而是扫描页表 —— 页表是紧凑的,cache 友好;
(2) 批量操作 —— 一次页表遍历可以处理整个 PMD 范围(512 个页);
(3) 精确时间 —— 利用硬件 accessed bit,而非软件模拟的 referenced;


3.2 淘汰(Eviction)— 从 min_seq 的最老代选取

流程:
(1) 从 min_seq 对应的代中选取页面(最老的页面);
(2) 按 tier 判断是否保护(高 tier 的热页可能被提升回年轻代);
(3) 不受保护的冷页被淘汰(回写/swap/释放);
(4) min_seq++ 当该代为空时推进;


lru_gen_look_around — 空间局部性优化, 这是 MGLRU 的另一个精妙设计:

传统 LRU: 逐页扫描 rmap,一次只看一个页

MGLRU: 当回收过程中遇到一个被访问的热页时,"顺便"扫描同一 PMD 内相邻的 PTE(空间局部性),把附近的热页一起提升,冷页留在原地。

效果: 利用 cache line 已经加载了相邻 PTE 的事实,几乎零额外开销地完成了批量老化判断。


4. 与传统 LRU 的完整对比

----------------------------------------------------------------------------------------------------
维度                传统 Active/Inactive LRU       Multi-Gen LRU (MGLRU)
----------------------------------------------------------------------------------------------------
状态数              2(active/inactive)             2~4 代 × 4 层 = 最多 16 级精度
老化驱动            软件 kswapd 扫描 LRU 链表        扫描页表 accessed bit(硬件辅助)
扫描粒度            单页遍历(逐 page struct)         页表级批量(一次扫描整个 PMD 范围)
cache 效率          差(链表指针跳跃)                 好(页表是连续内存,cache line 友好)
工作集保护          依赖 refault 统计反馈            多层 tier + refault 统计联合决策
匿名/文件平衡       粗粒度 swappiness                min_seq 独立演进,文件可以先淘汰
锁竞争              LRU lock 是全局热点              代间移动只需原子操作(folio->flags)
适合场景            内存充裕、负载稳定                内存过量使用、工作集动态变化
----------------------------------------------------------------------------------------------------


5. 性能收益

根据 MGLRU 作者(Yu Zhao,Google)的基准测试和社区反馈:

----------------------------------------------------------------------------------------------------
场景                                          传统 LRU    MGLRU       提升
----------------------------------------------------------------------------------------------------
kswapd CPU 占用(内存压力下)                     高          显著降低     减少 40-80% CPU 开销
数据库(MySQL/PostgreSQL)在内存压力下的吞吐       下降明显      基本稳定     吞吐量提升 ~40%
Android 应用启动延迟                           高           低          减少 ~18% 冷启动时间
Chrome 多标签页(overcommit)                   频繁 OOM     稳定         tab discard 减少 ~55%
----------------------------------------------------------------------------------------------------

核心原因:MGLRU 能更准确地识别真正的冷页,减少了"误杀热页再 refault"的恶性循环。


6. 启用方式

编译时默认启用: CONFIG_LRU_GEN=y + CONFIG_LRU_GEN_ENABLED=y
编译了但运行时启用: echo 1 > /sys/kernel/mm/lru_gen/enabled

运行时查看状态: cat /sys/kernel/mm/lru_gen/enabled(位掩码, cat出来是 0x0003)

enabled 是一个 bitmask:
bit 0:core功能(MGLRU 本身)
bit 1:mm_walk(页表遍历老化)
bit 2:nonleaf_young(非叶页表项 young 优化)

典型值 0x7(全部启用)或 0x0(回退到传统 LRU)。


7. 整体架构图

页面生命周期(MGLRU 视角)

    [新分配/fault] --> Gen max_seq (最年轻)
                              │
                       ┌───────────────────┐
                       │   老化循环         │
                       │  (walk_pte_range) │
                       │                   │
                       │  accessed=1?      │
                       │   是 → 留在年轻代   │
                       │   否 → 自然老化     │
                       └───────────────────┘
                              │ max_seq++
                              ▼
                        Gen max_seq-1
                              │
                              ▼
                        Gen max_seq-2
                              │
                              ▼
                  Gen min_seq (最老) --> [淘汰]
                       │                    │
                       │  tier 高(热)?       │
                       │   是 → 保护/提升     │
                       │   否 → 回收         │
                       └────────────────────┘

辅助机制:
lru_gen_look_around(): 淘汰时发现热页 --> 批量扫描相邻 PTE --> 热页提升;
Bloom filter: 记录哪些 PMD 有热页 --> 下次老化优先扫描这些区域;
refault 统计: evicted/refaulted 比率 --> 动态调整 tier 保护阈值;


8. 在 vmscan.c 中的位置

shrink_node()
│
├─ lru_gen_enabled()?
│     是 → lru_gen_age_node()       //MGLRU 老化路径
│           try_to_inc_max_seq()    //  遍历页表,推进 max_seq
│
│           evict_folios()          //MGLRU 淘汰路径
│           isolate_folios()        //  从 min_seq 代选取冷页
│           shrink_folio_list()     //  实际回收(writeback/swap/free)
│
│     否 → shrink_active_list()     //传统 active → inactive 降级
│           shrink_inactive_list()  //传统 inactive 扫描回收

MGLRU 完全替代了传统 shrink_active_list + shrink_inactive_list 的双链表扫描逻辑。

 

posted on 2026-08-05 15:44  Hello-World3  阅读(12)  评论(0)    收藏  举报

导航