4. 内存水线:watermark 机制、min/low/high 水线含义、zone watermarks
内存水线(watermark)是 Linux 内核 页面回收触发与抑制 的核心阈值机制。每个内存管理区(zone)都维护三根水线:min、low、high,它们决定了 kswapd 何时唤醒、何时睡眠,以及直接回收(direct reclaim)是否介入。
4.1 三根水线的物理含义
水线以页面数(page)为单位,基于 zone 的 managed_pages(可管理页面数)计算得出。三根水线将 zone 的空闲页面划分为四个区域:
| 水线名称 | 位置关系 | 触发行为 | 回收压力等级 |
|---|---|---|---|
| min | 最低阈值 | 低于 min → 触发直接回收(direct reclaim),分配者自身阻塞回收 | 🔴 紧急 |
| low | 中间阈值 | 低于 low → 唤醒 kswapd 异步回收,分配者仍可尝试快速分配 | 🟡 预警 |
| high | 最高阈值 | 高于 high → kswapd 睡眠,回收停止 | 🟢 正常 |
关键理解: min 是系统最后的防线;low 是 kswapd 的唤醒线;high 是 kswapd 的睡眠线。三者之间的差值称为 水线间隙(watermark gap),决定了 kswapd 一次回收的工作量。
4.2 水线计算与 min_free_kbytes
水线的计算起点是内核参数 vm.min_free_kbytes,它直接决定了 min 水线 的全局基数。计算公式如下:
// 简化后的水线计算逻辑(mm/page_alloc.c)
// 1. 根据 min_free_kbytes 计算每个 zone 的 min 水线
zone->_watermark[WMARK_MIN] = min_free_kbytes * zone->managed_pages / total_managed_pages;
// 2. low 水线 = min + (min / 2) (默认比例)
zone->_watermark[WMARK_LOW] = zone->_watermark[WMARK_MIN] + (zone->_watermark[WMARK_MIN] >> 1);
// 3. high 水线 = min + min (默认比例)
zone->_watermark[WMARK_HIGH] = zone->_watermark[WMARK_MIN] + zone->_watermark[WMARK_MIN];
调优经验: 增大
vm.min_free_kbytes 会整体抬高所有 zone 的三根水线,使 kswapd 更早唤醒、更晚睡眠。适合延迟敏感型业务(如数据库、实时计算),但会减少可用内存总量。
4.3 水线与 kswapd 状态机
kswapd 的行为完全由水线驱动,其状态转换如下图所示:
4.4 水线对分配路径的影响
内存分配器(allocator)在分配页面时,会根据当前 zone 的空闲页面数选择不同的分配路径:
- free > high:快速路径(fast path),直接从
per-CPU 页框缓存(pcp)分配,无锁或低锁开销。 - low < free < high:慢速路径(slow path),尝试从伙伴系统分配,同时检查是否需要唤醒 kswapd。
- min < free < low:kswapd 已唤醒,分配者可能等待 kswapd 回收,或尝试
ALLOC_HARDER标志。 - free < min:直接回收(direct reclaim),分配者自身调用
try_to_free_pages(),这是最昂贵的路径。
性能陷阱: 频繁进入 direct reclaim 是内存压力的明确信号。如果
/proc/vmstat 中的 pgsteal_direct 持续增长,说明水线设置过低或 kswapd 回收速度跟不上分配速度,应适当提高 min_free_kbytes。
4.5 查看当前水线
通过 /proc/zoneinfo 可以查看每个 zone 的实时水线值:
# 查看所有 zone 的水线信息
cat /proc/zoneinfo | grep -E "Node|zone|watermark"
# 输出示例(DMA32 zone):
Node 0, zone DMA32
pages free 12345
min 5678
low 8517
high 11356
spanned 1048576
present 1048576
managed 1012345
各字段含义:
- free:当前空闲页面数。
- min / low / high:三根水线值。
- spanned:zone 覆盖的总页面数(含空洞)。
- present:zone 实际存在的页面数。
- managed:内核可管理的页面数(present 减去预留页),水线基于此值计算。
4.6 水线调优参数
| 参数 | 默认值 | 调优方向 | 适用场景 |
|---|---|---|---|
vm.min_free_kbytes |
通常为 1%~2% 物理内存 | 增大 → 提前回收,减少 direct reclaim | 数据库、高频交易、实时系统 |
vm.watermark_scale_factor |
10(0.1%) | 增大 → 扩大 low-high 间隙,kswapd 一次回收更多 | 大内存机器、批量回收场景 |
vm.watermark_boost_factor |
15000(150%) | 增大 → 回收后临时抬高水线,抑制突发分配 | 内存抖动频繁的业务 |
调优公式: low = min + (min × watermark_scale_factor / 10000)
high = min + 2 × (min × watermark_scale_factor / 10000)
默认 watermark_scale_factor = 10,即 low = min + 0.1% × min,high = min + 0.2% × min。
high = min + 2 × (min × watermark_scale_factor / 10000)
默认 watermark_scale_factor = 10,即 low = min + 0.1% × min,high = min + 0.2% × min。
4.7 水线架构总览
下图展示了水线在内存分配与回收路径中的核心位置:
4.8 实战:水线异常排查
当系统出现内存压力时,可以通过以下命令快速定位水线问题:
# 1. 查看全局水线配置
sysctl vm.min_free_kbytes vm.watermark_scale_factor vm.watermark_boost_factor
# 2. 查看各 zone 水线与实际 free 的差距
cat /proc/zoneinfo | awk '
/Node/ {node=$2}
/zone/ {zone=$2}
/free/ {free=$2}
/min/ {min=$2; printf "%-10s %-10s free=%-8d min=%-8d diff=%-8d\n", node, zone, free, min, free-min}
'
# 3. 观察 direct reclaim 频率
grep -E "pgsteal_direct|pgscan_direct" /proc/vmstat
快速判断: 如果
free - min 长期小于 low - min(即 free 在 low 以下徘徊),说明 kswapd 回收能力不足,应增大 min_free_kbytes 或优化回收效率。