根据Linux内核原理 详细介绍kswapd内核线程何时会被唤醒?其底层原理是什么?
min_free 不可回收的应急内存,由 vm.min_free_kbytes 和系统内存大小决定(默认 ≈ 0.3% 总内存)当 __alloc_pages() 尝试分配内存失败时,若空闲内存 ≤ low_mem,调用 wakeup_kswapd()。waitqueue_active(&pgdat->kswapd_wait)) // 避免重复唤醒。调整 /proc/sys/vm/watermar
一、kswapd 何时被唤醒?
kswapd 的唤醒由 内存水位检测 触发,基于以下三种水位的动态计算:
水位 计算规则
min_free 不可回收的应急内存,由 vm.min_free_kbytes 和系统内存大小决定(默认 ≈ 0.3% 总内存)
low_mem min_free + (total_pages - min_free) * vm.watermark_scale_factor / 10000
high_mem min_free + (total_pages - min_free) * (vm.watermark_scale_factor * 2) / 10000
唤醒条件:
1.分配器触发:
当 __alloc_pages() 尝试分配内存失败时,若空闲内存 ≤ low_mem,调用 wakeup_kswapd()。
2.周期性检查:
通过 memory_pressure 定时器(默认 10 秒)检查水位,但非主要唤醒途径。
3.直接回收失败:
当进程执行直接内存回收(direct reclaim)失败且内存低于 min_free,强制唤醒 kswapd。
二、底层原理详解
核心函数调用链
__alloc_pages() → wake_all_kswapds() → wakeup_kswapd()
↓
kswapd() → balance_pgdat() → kswapd_shrink_node()
关键步骤解析:
1.水位检测:
// mm/page_alloc.c
bool __zone_watermark_ok(..., int mark, int classzone_idx) {
free_pages -= min_free; // 扣除保留内存
if (free_pages <= mark) // 低于水位线
return false;
}
2.唤醒 kswapd:
// mm/vmscan.c
void wakeup_kswapd(struct zone *zone, int order, int classzone_idx) {
if (!waitqueue_active(&pgdat->kswapd_wait)) // 避免重复唤醒
return;
wake_up_interruptible(&pgdat->kswapd_wait); // 唤醒线程
}
3.内存回收逻辑(kswapd_shrink_node()):
// mm/vmscan.c
static void kswapd_shrink_node(pg_data_t *pgdat) {
sc.priority = DEF_PRIORITY; // 优先级控制
do {
shrink_node(pgdat, &sc); // 回收匿名页和文件页
} while (sc.priority-- && sc.nr_reclaimed < sc.nr_to_reclaim);
}
回收优先级策略:
扫描压力:从 DEF_PRIORITY=12 开始递减,数值越低扫描越激进。
四种回收模式
优先级区间 回收行为
12~7 仅回收文件页缓存(Clean Page)
6~3 回收文件页 + 非活跃匿名页
2~1 强制回收活跃页(触发 Swap)
0 OOM 前最后一搏
三、案例演示:数据库突发负载导致回收
场景:
系统内存:64 GB,min_free=196 MB, low_mem=1.5 GB, high_mem=3 GB
运行 MySQL 突发大查询,需分配 10 GB 新内存。
kswapd 行为:
1.初始状态:
空闲内存 5 GB(高于 high_mem),kswapd 休眠。
2.内存分配触发回收:
当 MySQL 分配内存导致空闲内存 ≤ low_mem(1.5 GB) 时:
分配失败 → __alloc_pages() → wakeup_kswapd() → 唤醒 kswapd0 线程
3.后台回收过程:
kswapd 扫描 LRU 链表,按顺序回收:
阶段1:优先释放文件页缓存(如未写入磁盘的日志缓冲区)
阶段2:若文件页不足,回收非活跃匿名页 → 触发 Swap
$ sar -B 1 # 观察页面回收统计
pgpgin/s pgpgout/s pswpin/s pswpout/s
0.00 10240.00 0.00 15.20 # kswapd 写出 10 MB/s
目标:让空闲内存回升至 high_mem(3 GB)。
查看zone水位值 :内存区域类型(如DMA、DMA32、Normal),不同区域的水位独立管理
awk '/Node|min|low|high/ {
if (/Node/) {print}
else {printf "%s: %.2f MB\n", $1, $2*4/1024}
}' /proc/zoneinfo
4.恢复休眠:
当空闲内存 > high_mem,kswapd 主动休眠:
// kswapd() 主循环
if (pgdat->kswapd_max_order == 0 &&
zone_balanced(zone, order, high_mem)) {
schedule(); // 休眠等待下一次唤醒
}
四、关键优化机制
1. 避免过度回收(Lumpy Recycling)
在内存碎片化时跳过孤立的活跃页,优先回收大块连续页。
2. 内存压缩(Compaction)
kswapd 触发 kcompactd 线程对高阶内存进行压缩:
// mm/compaction.c
wakeup_kcompactd(pgdat, order, classzone_idx);
3. 冷热页识别(Refault Distance)
基于 页面历史访问记录 预测回收代价,避免频繁换入换出:
// mm/workingset.c
if (refault_distance > inactive_pages)
activate_page(page); // 提升页面活性
4. 自适应水位调整
根据系统负载动态计算 watermark_scale_factor:
高负载系统 → 增大比例 → 更早唤醒 kswapd
嵌入式设备 → 减小比例 → 减少后台回收开销
五、kswapd 与直接回收的对比
特性 kswapd 直接回收(Direct Reclaim)
触发时机 异步(内存≤ low_mem) 同步(分配失败时立即执行)
执行上下文 内核线程 用户进程的内核路径
优先级 低(不阻塞进程) 高(阻塞进程直至完成)
回收量目标 直至空闲内存 ≥ high_mem 仅满足当前分配请求
性能影响 后台执行,对业务延迟影响小 直接阻塞进程,导致请求延迟突增
总结
唤醒条件:空闲内存 ≤ low_mem,或直接回收失败时强制唤醒。
设计目标:在内存压力升级前提前回收,避免进程陷入同步阻塞。
实践意义:
调整 /proc/sys/vm/watermark_scale_factor(默认 10)优化敏感型系统。
监控 kswapd CPU 使用率(top -H)识别潜在内存瓶颈。
避免将 vm.min_free_kbytes 设的过大(超总内存 5%)以防浪费资源。
通过理解 kswapd 的唤醒机制和回收策略,可有效优化系统在内存压力下的响应能力,保障关键应用的性能稳定。
附件:
watermark_scale_factor 参数定义
位置:/proc/sys/vm/watermark_scale_factor
默认值:10 (单位:千分比,即 10/10000 = 0.1%)
取值范围:1-1000
作用:控制内核内存回收的激进程度
参数调整效果:
参数值 效果 适用场景
增大 扩大高低水位距离延迟内存回收减少回收频率 内存充足系统追求高性能应用
减小 缩小水位线距离提前触发内存回收回收更频繁 内存紧张系统避免OOM风险
指标 调整前 (scale=10) 调整后 (scale=95)
回收触发点 3.5GB 5.5GB
kswapd 唤醒频率 60次/小时 12次/小时
平均回收时间 400ms 850ms
数据库查询延迟 15%波动 <5%波动
OOM事件 0 0
内存利用率峰值 94% 96%
更多推荐
所有评论(0)