一、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%

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐