Linux 内存回收机制:水位线、kswapd 与 Direct Reclaim

Linux 内存回收机制:水位线、kswapd 与 Direct Reclaim

源码基线:Linux v6.18.9 (mm/page_alloc.c / mm/vmscan.c)。

现象 :内存明明还有(free 数 GB),进程却偶发卡顿几百毫秒,P99 翻倍。

根因 :kswapd 回收跟不上分配速度,水位跌破 min 后,业务线程被迫在分配路径上亲自回收(Direct Reclaim),并触发内核节流。

一、三条水位线:谁来收债

text 复制代码
─────────────────── high   ← kswapd 回收到这里就睡
─────────────────── low    ← 跌破这里唤醒 kswapd(后台收债,业务无感)
─────────────────── min    ← 跌破这里,分配线程自己收债(Direct Reclaim,卡顿)
水位区间 行为 业务感知
高于 low 快路径直接返回页框 无
low ~ min 唤醒 kswapd 后台回收 基本无
低于 min 当前线程进入慢路径亲自回收 卡顿
bash 复制代码
grep -A5 "Node 0" /proc/zoneinfo | grep -E "min|low|high"   # 查看水位(单位:页)

二、Direct Reclaim 调用链(v6.18.9)

text 复制代码
alloc_pages()
  └─ 快路径失败 → __alloc_pages_slowpath()          # 先 wake_all_kswapds(),
                                                    # 再按 ALLOC_WMARK_MIN 重试
  └─ __alloc_pages_direct_reclaim()                  # psi_memstall_enter()
  └─ __perform_reclaim()
  └─ try_to_free_pages()                             # ← 业务线程开始收债
  └─ do_try_to_free_pages() → shrink_zones() → shrink_node()
       (MGLRU 启用时走 lru_gen_shrink_node())
  └─ 收到页返回(几十~几百 ms);收不到 → OOM

7.0 中 shrink_zones() 更名为 shrink_one(),其余不变。

为什么慢------回收线程扫到不同页的代价:

页类型 代价 量级
干净 Page Cache 直接丢 微秒
脏页 回写 I/O + 节流 毫秒起
匿名页 换出 swap/zram 毫秒

三级节流(卡顿的真正来源):

  1. 撞见大量回写页 → reclaim_throttle(VMSCAN_THROTTLE_WRITEBACK),睡最长 100ms
  2. 回收连续无进展 → VMSCAN_THROTTLE_NOPROGRESS
  3. min 以下保护区耗尽 → throttle_direct_reclaim(),睡等 kswapd 救回水位

多线程同时分配会集体进 Direct Reclaim,形成"集体抖动"。

MGLRU(6.12 起默认)改变扫描方式,但水位线、触发条件、计数器全部不变,排查方法照用。

三、观测与实锤

bash 复制代码
grep -E "allocstall|pgscan_direct|pgscan_kswapd|pgsteal|throttle" /proc/vmstat
计数器 含义 信号
pgscan_kswapd 后台扫描量 稳定 = 正常
pgscan_direct 前台扫描量 持续增长 = 业务在收债
allocstall_* 进 Direct Reclaim 次数 增长 = 卡顿元凶
pgscan_direct_throttle 睡等 kswapd 救援次数 非零 = 最危险阶段
bash 复制代码
drsnoop-bpfcc                                  # 专抓 Direct Reclaim 延迟
cat /proc/pressure/memory                      # full avg10 高 = 撞见回写
perf stat -e 'vmscan:mm_vmscan_direct_reclaim_begin' -a sleep 10

典型卡顿栈:__alloc_pages_direct_reclaim → __perform_reclaim → try_to_free_pages → shrink_node → reclaim_throttle

四、缓解手段

手段 作用点 代价
调大 vm.min_free_kbytes 抬水位,给 kswapd 提前量 常驻内存变少
调大 vm.watermark_scale_factor 更早唤醒 kswapd 同上
降低 vm.swappiness 倾向回收文件页 匿名页压力大时 OOM 更早
业务削峰 / 内存池 避免集体进慢路径 改业务
cgroup 限额 压力隔离到容器 限额内仍会发生
查磁盘与 vm.dirty_* 降低回写节流命中 基础设施成本

排查顺序 :pgscan_direct 涨 → 确认 Direct Reclaim → pgscan_direct_throttle 涨 → 抬水位+查 kswapd → 否则查 Dirty/Writeback 与磁盘 → 再查 swap/zram。

能做 :隔一分钟跑一次 grep -E "allocstall|pgscan_direct" /proc/vmstat,数字在涨,说明你的业务线程正在替内核收债。

相关推荐
SKH.1 小时前
Linux驱动-key、中断
linux·运维·服务器
小龙报1 小时前
【零到全栈】从扩展名到终端实操,打通 Linux 基础
linux·ubuntu·云原生·职场和发展·创业创新·学习方法·程序员创富
Logic1011 小时前
C语言/数据结构动态规划题解:字符串变回文的最少操作次数——最长回文子序列(LPS)
c语言·数据结构·算法·动态规划·时间复杂度·回文串·最长回文子序列
星恒随风1 小时前
Linux进程间通信(一):从匿名管道、命名管道到进程池,深入理解Pipe、FIFO与文件描述符
linux·运维·笔记·学习·状态模式
UIU1142 小时前
P1115 最大子段和
c++·算法
垆边人似月.2 小时前
华为题:零钱兑换
算法·华为
wuminyu2 小时前
ZGC染色指针在虚拟线程stackChunkOop上的并发屏障全景分析
java·linux·c语言·jvm·c++
Logic1012 小时前
C语言/数据结构组合数学题解:数字好排列计数——质数倍数分类与阶乘取模
c语言·数据结构·算法·组合数学·时间复杂度·排列·取模运算
硅基手札2 小时前
【I2C 技术系列 11】I2C Linux 驱动框架
linux·驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设