Linux 内核 LRU 页面置换算法

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} → 回收

页面访问标记与链表迁移

内核通过两次访问标记机制判断页面是否活跃,避免短时间内的偶然访问误判:

  1. 第一次访问 :页面被访问时,触发 page_referenced(),设置 PG_referenced 标志位。
  2. 第二次访问 :若页面在下次扫描前再次被访问,清除 PG_referenced,并将页面从 inactive 链表迁移到 active 链表。
  3. 无二次访问 :扫描时发现 PG_referenced 未被清除,判定为非活跃,放入回收候选队列。

内核对 LRU 的优化

标准 LRU 存在开销大 (每次访问需移动链表节点)和无法处理突发访问的问题,Linux 内核做了以下优化:

  1. 近似 LRU(Clock 算法变种) 内核并未严格维护页面的访问时间戳,而是通过 PG_referenced 标志位模拟「时钟指针」扫描,降低链表操作的开销,称为 Clock-Pro 算法,是标准 LRU 的高效近似实现。

  2. 页面回收优先级 通过 scan_control 结构体的 priority 参数控制扫描深度:优先级越高(数值越小),扫描的页面越多,回收越激进。

  3. 区分文件页与匿名页 通过 swappiness 参数(/proc/sys/vm/swappiness)控制两类页面的回收比例:

    • swappiness = 0:优先回收文件页,仅在内存极度紧张时回收匿名页。
    • swappiness = 100:同等优先级回收文件页和匿名页。
  4. 透明大页(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 则升级为活跃页。
  • 链表分离优化 :将页面分为 activeinactive 双链表,避免频繁移动节点,仅在页面状态发生质变时(温页→热页)才迁移链表。

该优化的核心收益是:用「标志位 + 两轮扫描」替代「时间戳 + 链表排序」,将页面状态维护的时间复杂度从 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() 函数,根据页面当前状态更新标志位:

  1. 若页面在 inactive 链表且 PG_referenced=0 → 置 PG_referenced=1(状态 1 → 状态 2);
  2. 若页面在 inactive 链表且 PG_referenced=1 → 清除 PG_referenced,并迁移到 active 链表(状态 2 → 状态 3);
  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_controlpriority 参数控制扫描深度(优先级取值 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 缓冲区)
相关推荐
小白同学_C3 小时前
Lab1-Xv6 and Unix utilities 配置环境的搭建以及前言 && MIT6.1810操作系统工程【持续更新】
linux·c/c++·操作系统os
haluhalu.3 小时前
深入理解Linux线程机制:线程概念,内存管理
java·linux·运维
乙酸氧铍3 小时前
【imx6ul 学习笔记】Docker 运行百问网 imx6ul_qemu
linux·docker·arm·qemu·imx6ul
不会C++的雾3 小时前
Linux操作系统(2)
linux·数据库·mysql
Code-world-14 小时前
NVIDIA Isaac Sim 安装教程
linux·人工智能·ubuntu·强化学习·isaac sim
cui__OaO4 小时前
Linux驱动--驱动编译
linux·运维·服务器
SunnyRivers4 小时前
深入理解Linux后台命令
linux·后台运行·重定向·nohub
刘叨叨趣味运维4 小时前
快速掌握Linux启动过程:像看接力赛一样简单
linux
Q16849645154 小时前
红帽Linux-进程、ssh、网络、软件包、文件系统
linux·运维·网络