Linux 内核采用的LRU(Least Recently Used,最近最少使用) 页面置换算法,是内存管理子系统中实现页面回收的核心机制,其目标是优先淘汰最久未被访问的页面,最大化保留活跃页面,减少磁盘 I/O 开销。
内核的 LRU 实现并非简单的单链表结构,而是基于双链表 + 多级分类的优化设计,以适配不同类型页面(文件页 / 匿名页)的回收策略。
局部性原理
程序运行时存在时间局部性 (近期访问的页面大概率会再次访问)和空间局部性(访问某页面时大概率访问相邻页面)。LRU 算法正是利用这一原理,淘汰最久未访问的页面,降低页面缺失率。
活跃与非活跃链表分离
Linux 内核为每个内存域(zone)维护 4 条核心 LRU 链表,将页面划分为「活跃」和「非活跃」两类,避免频繁移动页面:
active_file:活跃的文件页(如文件缓存、内存映射文件)inactive_file:非活跃的文件页active_anon:活跃的匿名页(如进程堆、栈,无磁盘映射)inactive_anon:非活跃的匿名页
页面的核心状态流转逻辑:
新分配页面 → inactive_{file/anon} → 被访问 → active_{file/anon} → 长时间未访问 → inactive_{file/anon} → 回收
页面访问标记与链表迁移
内核通过两次访问标记机制判断页面是否活跃,避免短时间内的偶然访问误判:
- 第一次访问 :页面被访问时,触发
page_referenced(),设置PG_referenced标志位。 - 第二次访问 :若页面在下次扫描前再次被访问,清除
PG_referenced,并将页面从inactive链表迁移到active链表。 - 无二次访问 :扫描时发现
PG_referenced未被清除,判定为非活跃,放入回收候选队列。
内核对 LRU 的优化
标准 LRU 存在开销大 (每次访问需移动链表节点)和无法处理突发访问的问题,Linux 内核做了以下优化:
-
近似 LRU(Clock 算法变种) 内核并未严格维护页面的访问时间戳,而是通过
PG_referenced标志位模拟「时钟指针」扫描,降低链表操作的开销,称为 Clock-Pro 算法,是标准 LRU 的高效近似实现。 -
页面回收优先级 通过
scan_control结构体的priority参数控制扫描深度:优先级越高(数值越小),扫描的页面越多,回收越激进。 -
区分文件页与匿名页 通过
swappiness参数(/proc/sys/vm/swappiness)控制两类页面的回收比例:swappiness = 0:优先回收文件页,仅在内存极度紧张时回收匿名页。swappiness = 100:同等优先级回收文件页和匿名页。
-
透明大页(THP)的 LRU 处理
对于透明大页(THP),内核将其作为单个页面对象挂载到 LRU 链表,而非拆分为小页:
- 扫描时一次性判断大页的活跃状态,避免拆分扫描的开销;
- 回收时通过
split_huge_page()拆分为小页后释放,或直接释放连续大页(若未被引用)。
Clock-Pro 算法
Clock-Pro 是 Linux 内核实现的近似 LRU 页面置换算法,是标准 Clock 算法的改进版本,它在保证低开销的同时,更精准地模拟了 LRU 的核心逻辑(优先淘汰最久未访问页面),解决了传统 Clock 算法对页面活跃程度区分不足的问题。
linux 内核并未实现严格意义上的 LRU(需维护页面访问时间戳并频繁调整链表顺序,开销过高),而是通过 PG_referenced 等标志位和多轮扫描机制,用 Clock-Pro 达成近似 LRU 的效果 ,并成为内存回收(kswapd/ 直接回收)的核心页面选择策略。
传统 Clock 算法的缺陷
传统 Clock 算法(也叫 NRU 算法)为每个页面设置一个访问位 (referenced),内核通过一个「时钟指针」循环扫描页面:
- 若页面访问位为
0→ 淘汰该页面; - 若页面访问位为
1→ 置为0,指针后移。其问题在于:无法区分「被访问过一次」和「被频繁访问」的页面,容易误淘汰短期活跃的页面。
Clock-Pro 的改进点
内核 2.6 及之后版本采用 Clock-Pro (也叫 Enhanced Clock ),通过 双访问逻辑 + 两轮扫描 + 活跃 / 非活跃链表分离,实现低开销、高精度的近似 LRU:
- 标志位优化 :引入
PG_active(标记活跃链表归属)和PG_referenced(标记访问状态)两个核心标志位,划分「冷页 / 温页 / 热页」三种状态。 - 两轮扫描机制 :第一次扫描将
PG_referenced=1的页面置 0 并保留,第二次扫描再检查 ------ 若仍为 0 则淘汰,若重新置 1 则升级为活跃页。 - 链表分离优化 :将页面分为
active和inactive双链表,避免频繁移动节点,仅在页面状态发生质变时(温页→热页)才迁移链表。
该优化的核心收益是:用「标志位 + 两轮扫描」替代「时间戳 + 链表排序」,将页面状态维护的时间复杂度从 O (n) 降到 O (1)。
Clock-Pro 引入了 2 个访问标志位 和 3 种页面状态,可以更精细地识别页面的活跃程度,核心目标是:
- 降低链表操作的开销(无需像严格 LRU 那样频繁移动页面节点);
- 减少「活跃页面被误淘汰」的概率;
- 适配 Linux 内核的活跃 / 非活跃 LRU 链表分离架构。
Clock-Pro 将页面划分为 3 种状态,对应不同的回收优先级:
| 状态 | 链表位置 | 标志位特征 | 含义 | 回收优先级 |
|---|---|---|---|---|
| 状态 1:未被访问的非活跃页 | inactive 链表 |
PG_referenced=0 |
长期未被访问,属于「冷页」 | 最高(优先淘汰) |
| 状态 2:被访问过的非活跃页 | inactive 链表 |
PG_referenced=1 |
近期被访问过一次,属于「温页」 | 中等(需二次扫描确认) |
| 状态 3:活跃页 | active 链表 |
PG_active=1 |
被多次访问,属于「热页」 |
核心扫描与状态流转逻辑
Clock-Pro 的核心是两轮扫描机制,配合页面访问事件的标记,实现页面状态的动态流转,整体流程如下:
(1) 页面访问的标记逻辑
当进程访问一个页面时,内核调用 mark_page_accessed() 函数,根据页面当前状态更新标志位:
- 若页面在
inactive链表且PG_referenced=0→ 置PG_referenced=1(状态 1 → 状态 2); - 若页面在
inactive链表且PG_referenced=1→ 清除PG_referenced,并迁移到active链表(状态 2 → 状态 3); - 若页面在
active链表 → 不修改标志位(保持状态 3,确认活跃性)。
(2) 回收时的扫描逻辑
当 kswapd 或直接回收触发时,内核调用 shrink_inactive_list() 扫描 inactive 链表,执行两轮扫描:
- 第一轮扫描 :遍历
inactive链表页面- 若
PG_referenced=0(状态 1)→ 加入回收候选队列; - 若
PG_referenced=1(状态 2)→ 置PG_referenced=0,指针后移(给页面一次「复活」机会)。
- 若
- 第二轮扫描 :再次遍历第一轮中被置为
0的页面- 若
PG_referenced=0(期间无访问,状态 2 → 状态 1)→ 淘汰该页面; - 若
PG_referenced=1(期间被访问,状态 2 → 状态 3)→ 迁移到active链表。
- 若
核心优势:避免误淘汰活跃页面
传统 Clock 算法仅一轮扫描,可能误淘汰「刚被访问一次的页面」;而 Clock-Pro 的两轮扫描 + 状态流转机制,确保:
- 只有「两轮扫描期间都未被访问」的页面才会被淘汰;
- 被频繁访问的页面会快速进入
active链表,避免被回收。
与其他近似 LRU 算法的对比
| 算法 | 核心机制 | 优点 | 缺点 | Linux 内核应用 |
|---|---|---|---|---|
| 传统 Clock | 单访问位 + 一轮扫描 | 实现简单、开销极低 | 易误淘汰活跃页面 | 早期内核版本(2.4 及之前) |
| Clock-Pro | 双访问逻辑 + 两轮扫描 + 活跃 / 非活跃链表 | 低开销、近似 LRU 精度高 | 实现略复杂 | 主流内核版本(2.6 及之后,默认算法) |
| ARC | 自适应调整文件页 / 匿名页缓存比例 | 动态适配负载 | 仅适用于文件系统缓存 | 未进入主线内核,部分存储驱动使用 |
分层回收与优先级控制
内核通过多级扫描优先级、水位联动、场景适配,让 LRU 回收策略更贴合系统负载。
-
回收优先级分层:
scan_control优先级机制 :内存回收时,内核通过struct scan_control的priority参数控制扫描深度(优先级取值 0~20,数值越小优先级越高):- 高优先级(
priority=0):内存极度紧张(接近WMARK_MIN),扫描所有非活跃页,甚至尝试回收部分活跃页; - 低优先级(
priority=20):内存轻度紧张(接近WMARK_LOW),仅扫描少量非活跃页,避免「内存轻度紧张时过度回收」,减少不必要的磁盘 I/O 开销。
- 高优先级(
-
与 kswapd 水位联动:后台回收 vs 直接回收
- 当内存低于
WMARK_LOW时,唤醒kswapd进行后台异步回收,此时扫描优先级低,不阻塞进程; - 当内存低于
WMARK_MIN时,触发直接同步回收 ,此时扫描优先级高,进程会阻塞直到回收足够页面。优化价值:优先通过后台回收维持内存水位,避免直接回收导致的进程卡顿。
- 当内存低于
-
**NUMA 架构适配:节点本地 LRU 优先:**在 NUMA 架构下,内核为每个 NUMA 节点维护独立的 LRU 链表:
- 回收时优先扫描远程节点的非活跃页,将内存迁移到进程运行的本地节点(配合页面迁移机制);
- 避免「本地节点内存紧张、远程节点内存空闲」的资源浪费,提升访存性能。
与其他页面置换算法的对比
| 算法 | 核心特点 | Linux 内核应用场景 |
|---|---|---|
| LRU(近似实现) | 优先淘汰最久未访问页面,基于局部性原理 | 默认算法,适配绝大多数场景 |
| LFU(Least Frequently Used) | 优先淘汰访问频率最低的页面 | 未直接实现,可通过内核补丁扩展 |
| FIFO | 先进先出,无访问时间考量 | 仅用于部分特殊场景(如 DMA 缓冲区) |