浅谈Linux内核kswapd的内存域(zone)扫描机制
内存域(zone)的基本概念
在Linux内核中,物理内存被划分为多个内存域(zone),每个zone管理特定类型的内存区域:
ZONE_DMA:用于直接内存访问(DMA)设备的内存区域(0~16MB)
ZONE_DMA32:32位系统DMA可访问内存(0~4GB)
ZONE_NORMAL:常规内存映射区域(x86_64上通常16MB~896MB)
ZONE_HIGHMEM:高端内存区域(仅32位系统,x86_64上不存在)
ZONE_MOVABLE:可移动内存区域(避免内存碎片)
kswapd的zone扫描方向原则
kswapd按照以下优先级顺序扫描内存域:
1.从高水印最低的zone开始扫描
内核计算每个zone的low_wmark_pages(zone)
优先处理内存压力最大的zone
2.NUMA节点的本地化优先原则
每个NUMA节点有自己的zone集合
kswapd实例在所属NUMA节点内扫描
通过pgdat->kswapd_classzone_idx跟踪扫描起始点
3.zone类型优先级顺序
扫描顺序遵循:
ZONE_HIGHMEM → ZONE_NORMAL → ZONE_DMA32 → ZONE_DMA
高端内存优先回收(因其易失效),常规内存次之
4.水位线驱动(watermark-driven)扫描
当zone->watermark[WMARK_LOW]未被满足时触发回收
回收直到达到zone->watermark[WMARK_HIGH]
5.zone_reclaim_mode调节
通过/proc/sys/vm/zone_reclaim_mode控制:
0 (默认):仅回收本地zone
1:启用本地zone强制回收
2:本地回收时写脏页
4:可交换本地内存
底层机制实现(源码级)
关键函数调用链:
kswapd()
→ kswapd_try_to_sleep()
→ balance_pgdat(pgdat, order, classzone_idx)
→ for (priority = DEF_PRIORITY; priority >= 0; priority--)
→ sc.priority = priority;
→ for (i = pgdat->kswapd_classzone_idx; i >= 0; i--) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone)) continue;
// 扫描当前zone及更低类型zone
sc.nr_reclaimed += kswapd_shrink_zone(zone, &sc);
}
处理逻辑:
设置初始优先级:DEF_PRIORITY(通常为12)
定位需扫描的classzone_idx:当前NUMA节点中最低水印的zone索引
分层扫描:
外层循环:优先级从高到低(12→0)
内层循环:从classzone_idx向低地址类型zone扫描
案例说明:NUMA服务器内存回收
环境配置:
双路NUMA服务器:Node0(32GB), Node1(32GB)
每个节点包含:
ZONE_DMA: 16MB
ZONE_DMA32: 4GB
ZONE_NORMAL: 剩余内存
运行内存密集型应用
内存压力场景:
1.Node0的应用占用30GB内存,逼近物理极限
2./proc/zoneinfo显示:
Node 0, zone Normal
low 5% # watermark_low = 1.6GB
min 3% # watermark_min = 960MB
high 8% # watermark_high = 2.56GB
free: 800MB < low_wmark!
3.kswapd唤醒与扫描过程:
步骤1:定位压力zone
扫描pg_data_t结构发现Node0的ZONE_NORMAL水印最低
classzone_idx设为ZONE_NORMAL的索引
步骤2:层级扫描启动
for (i = classzone_idx; i >= 0; i--) { // i=ZONE_NORMAL→ZONE_DMA32→ZONE_DMA
zone = pgdat->node_zones + i;
kswapd_shrink_zone(zone, &sc);
}
先扫描ZONE_NORMAL:回收匿名页和文件页
再扫描ZONE_DMA32:回收可用页面
跳过ZONE_DMA(水印正常)
步骤3:跨zone回收协作
当ZONE_NORMAL回收后空闲内存仍不足时:
if (buffer_heads_over_limit)
sc.gfp_mask |= __GFP_HIGHMEM; // 尝试回收HIGHMEM
步骤4:NUMA本地性维持
始终在Node0内部回收,即使Node1有大量空闲内存
4.结果:
成功回收2GB内存后
ZONE_NORMAL空闲达3.6GB > watermark_high
kswapd返回睡眠状态
特殊场景:ZONE_HIGHMEM回收
环境:32位ARM设备(1GB内存)
ZONE_NORMAL: 0-896MB
ZONE_HIGHMEM: 896MB-1024MB
kswapd行为:
1.当ZONE_NORMAL水印告警时:
优先扫描ZONE_HIGHMEM
因HIGHMEM映射开销大,回收可减少TLB压力
2.回收逻辑:
// mm/vmscan.c
if (buffer_heads_over_limit) {
sc.gfp_mask |= __GFP_HIGHMEM;
reclaim_state->reclaimed_slab = 0;
shrink_slab(...);
}
监控与调优工具
1.实时观察zone状态:
watch -n 1 "cat /proc/zoneinfo | grep -E 'zone|free|min|low|high'"
2.NUMA内存分布:
numastat -m # 显示各节点内存状态
3.调整zone回收策略:
echo 1 > /proc/sys/vm/zone_reclaim_mode # 启用本地强制回收
总结
kswapd的zone扫描机制是Linux内存管理的核心调度器:
1.压力驱动:优先处理低水印zone
2.层级递进:从HIGHMEM→NORMAL→DMA32→DMA
3.NUMA本地化:限制在所属节点内回收
4.水位闭环:回收至达高水印后停止
5.动态调节:通过zone_reclaim_mode改变策略
这种设计确保在复杂内存拓扑下,系统能智能平衡回收效率与性能损耗,是Linux健壮性的关键支撑。
