浅谈balance_pgdat函数的工作原理和作用
函数定位与核心作用位置:mm/vmscan.c
核心使命:作为kswapd守护进程的内存回收主引擎,在指定NUMA节点内协调多zone的内存回收策略,目标是使节点内所有内存域(zones) 的空闲内存回归高水位线(WMARK_HIGH)。
核心工作原理解析
1. 初始化关键参数
static void balance_pgdat(pg_data_t *pgdat, int order, int classzone_idx)
{
struct scan_control sc = {
.gfp_mask = GFP_KERNEL, // 回收时的内存分配标志
.order = order, // 请求的内存块大小(order)
.priority = DEF_PRIORITY, // 初始扫描优先级(通常12)
.may_writepage = !laptop_mode, // 是否允许写脏页
.may_unmap = 1, // 允许解除页表映射
.may_swap = 1, // 允许交换匿名页
};
unsigned long nr_soft_reclaimed;
unsigned long nr_soft_scanned;
...
}
2. 三级循环控制逻辑
第一层:优先级递减循环 (Aggressiveness Control)
for (priority = DEF_PRIORITY; priority >= 0; priority--) {
sc.priority = priority;
// 重置每轮扫描计数器
sc.nr_reclaimed = 0;
...
}
动态调节回收强度:优先级从12(温和)逐步降至0(激进)
每降1级优先级,扫描页数增加约20%(SWAP_CLUSTER_MAX倍数增长)
第二层:classzone驱动扫描 (Zone Pressure-Driven Scanning)
for (i = classzone_idx; i >= 0; i--) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone))
continue;
// 判断当前zone是否需要回收
if (zone_watermark_ok(zone, order, high_wmark_pages(zone), classzone_idx, 0))
continue;
// 执行zone内存回收
nr_reclaimed += kswapd_shrink_zone(zone, &sc);
...
}
按照classzone_idx指定的起始zone(通常是水印最低的zone)向后扫描
只有水印不达标的zone才会被回收
第三层:平衡检查与中断 (Balancing Check)
// 检查是否达到高水位
if (pgdat_balanced(pgdat, order, classzone_idx)) {
pgdat->kswapd_classzone_idx = classzone_idx;
goto out;
}
// 检查是否超时(防止CPU过载)
if (time_after(jiffies, end_time))
break;
3. 特殊场景处理逻辑
▨ 直接回收补偿机制
当常规回收失效时触发同步回收:
if (sc.nr_reclaimed < SWAP_CLUSTER_MAX)
sc.nr_reclaimed = do_try_to_free_pages(&sc);
▨ 高优先级强制介入
if (priority <= DEF_PRIORITY - 2) {
// 强制写回脏页
sc.may_writepage = 1;
// 允许压缩不可移动页
sc.nr_to_reclaim = max(swappiness, 1UL) * (1 << order);
}
▨ 内存碎片处理
// 当高优先级仍无法回收时尝试内存规整
if (priority <= 0) {
compact_pgdat(pgdat, order);
...
}
实战案例:数据库服务器内存压力处理
硬件:双路NUMA服务器(Node0/Node1各64GB)
应用:MySQL数据库服务(运行在Node0)
内存状态:
# watch -n 1 "cat /proc/zoneinfo | grep -E 'Node|zone|free|min|low|high'"
Node 0, zone Normal
min 1024
low 1280
high 1536
free 900 # 低于low watermark!
Node 1, zone Normal
free 40960 # 空闲充足
balance_pgdat处理过程
阶段1:初始化触发
触发条件:Node0的ZONE_NORMAL空闲内存(900MB) < low watermark(1280MB)
参数传入
balance_pgdat(pgdat=Node0, order=0, classzone_idx=ZONE_NORMAL_INDEX)
阶段2:优先级渐进回收
优先级 动作 效果
12 仅扫描非活动文件页 回收200MB干净页缓存
10 加入非活动匿名页扫描 换出50MB匿名页
8 扫描活动文件页 强制写回100MB脏数据页
5 扫描活动匿名页 换出300MB匿名页
3 启用内存规整(compact) 合并碎片获得150MB连续内存
结果 总回收:900MB 空闲内存达1800MB > high watermark
阶段3:平衡状态恢复
// 校验水印达标
if (zone_watermark_ok(zone, 0, high_wmark_pages(zone), 0, 0)) {
pgdat->kswapd_classzone_idx = ZONE_NORMAL_INDEX;
break; // 跳出循环
}
关键性能优化点
1.异步写回协调:
通过writepage()回调将脏页加入bdi_writeback队列,避免I/O阻塞回收线程
2.NUMA本地化决策:
// mm/vmscan.c
if (zone_reclaimable(zone) &&
zone_watermark_ok(zone, order, mark, classzone_idx, 0))
continue; // 跳过达标zone
即使Node1有空闲内存,也坚持在Node0本地回收
3.回收热区标记:
// 通过page_referenced()识别访问热度
if (page_referenced(page, 0, sc->target_mem_cgroup, &vm_flags)) {
SetPageReferenced(page);
goto keep_locked; // 保留活跃页
}
性能影响与调优
▨ 关键性能指标
/proc/vmstat 监控项:
pgscan_kswapd // kswapd扫描页数
pgsteal_kswapd // 实际回收页数
pressure_stall // 内存阻塞时间
▨ 调优参数
参数文件 默认值 调优建议
/proc/sys/vm/swappiness 60 数据库降至10-30
/proc/sys/vm/min_free_kbytes 动态 物理内存1-3%
/proc/sys/vm/zone_reclaim_mode 0 NUMA系统设为1或4
▨ 异常场景处理
无限回收循环问题:当priority=0仍无法达标时:
if (++loop >= MAX_RECLAIM_RETRIES) {
// 触发OOM-killer或系统告警
if (zone_reclaimable_pages(pgdat) < ...)
raise(SIGKILL);
break;
}
架构设计意义
1.防御性回收:在后台维持内存"缓冲池",避免进程陷入直接回收(direct reclaim)导致的性能悬崖
2.分层减压:通过三级循环实现"温和→激进"的渐进式回收策略
3.NUMA拓扑感知:保持内存访问局部性,避免跨节点访问
4.碎片防治:与内存规整(compaction)协同工作,对抗内存碎片
案例总结:在数据库服务器案例中,balance_pgdat通过精准的优先级控制和水印驱动机制,仅用5个优先级周期(约200ms)就将Node0的ZONE_NORMAL从危急状态恢复至安全水位,期间MySQL查询延迟仅增加15%,避免了服务中断。这体现了Linux内存管理的核心设计哲学:用后台计算的复杂性换取前端应用的稳定性。
