wake_all_kswapds()是内存分配慢速路径中用于唤醒所有相关 kswapd 内核线程的函数。它的核心目的是在分配陷入困境时,让后台回收线程提前开始工作,从而可能在后续重试中满足分配请求。
核心作用与设计动机
kswapd 是每个 NUMA 节点一个的后台回收线程,正常情况下由水位线触发(当 zone 空闲页低于WMARK_LOW时自动唤醒)。但在慢速路径中,分配已经失败,说明情况更紧急——如果只是被动等待 kswapd 自动醒来可能太慢,因此需要主动唤醒所有可能相关的 kswapd,让它们并行回收。
核心逻辑
static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, const struct alloc_context *ac) { struct zoneref *z; struct zone *zone; pg_data_t *last_pgdat = NULL; enum zone_type highest_zoneidx = ac->highest_zoneidx; for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, ac->nodemask) { if (!populated_zone(zone)) continue; if (cpusets_enabled() && (alloc_flags & ALLOC_CPUSET) && !__cpuset_zone_allowed(zone, gfp_mask)) continue; /* 每个 pgdat 只唤醒一次 */ if (last_pgdat == zone->zone_pgdat) continue; last_pgdat = zone->zone_pgdat; wakeup_kswapd(zone, gfp_mask, order, highest_zoneidx); } }逐步说明
1. 遍历 zonelist
for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, ac->nodemask)
遍历分配上下文中所有可能的 zone(受
highest_zoneidx和nodemask限制)。
2. 跳过空 zone 和 cpuset 不允许的 zone
if (!populated_zone(zone)) continue; if (cpusets_enabled() && (alloc_flags & ALLOC_CPUSET) && !__cpuset_zone_allowed(zone, gfp_mask)) continue;
populated_zone():只处理有内存的 zone。cpuset 检查:遵守 cpuset 的内存节点限制。
3. 每个 pgdat 只唤醒一次
if (last_pgdat == zone->zone_pgdat) continue; last_pgdat = zone->zone_pgdat; wakeup_kswapd(zone, gfp_mask, order, highest_zoneidx);
因为每个 NUMA 节点只有一个 kswapd,而一个 pgdat 包含多个 zone。
用
last_pgdat去重,避免对同一个 kswapd 重复唤醒。wakeup_kswapd():实际唤醒该节点的 kswapd,传入order、highest_zoneidx等信息,让 kswapd 知道回收的目标。
历史演进:highest_zoneidx的引入与修复
早期版本(2010 年代初):wake_all_kswapds()只接收order和gfp_mask,遍历所有 zone 并唤醒 kswapd。这导致两个问题:
唤醒无关的 kswapd:可能唤醒了
highest_zoneidx之外的节点,造成不必要的 CPU 消耗;gfp_mask传递不当:wakeup_kswapd()的签名在不同版本中变化,早期传order,后来传gfp_mask。
2017 年 Vlastimil Babka 的修复:引入ac->highest_zoneidx参数,让函数只唤醒可能对本次分配有用的节点。这避免了在 zonelist 很长(大 NUMA 系统)时唤醒大量无关 kswapd 的开销。
后续清理:wake_all_kswapds()的实现被简化为当前形式,wakeup_kswapd()的签名统一为(zone, gfp_mask, order, highest_zoneidx)。
在分配路径中的位置
它被__alloc_pages_slowpath()在进入慢速路径时调用:
__alloc_pages_slowpath() ├── wake_all_kswapds(order, gfp_mask, ac) // 唤醒 kswapd 并行回收 ├── retry: │ ├── get_page_from_freelist(...) // 再试一次分配 │ ├── __alloc_pages_direct_reclaim(...) // 直接回收 │ └── ... └── ...
总结
wake_all_kswapds()是慢速路径的kswapd 并行唤醒器:它遍历 zonelist 中受highest_zoneidx和nodemask限制的 zone,对每个 pgdat 只唤醒一次对应的 kswapd,让后台回收线程提前工作,为后续的分配重试创造机会。highest_zoneidx参数的引入避免了在大 NUMA 系统上唤醒大量无关 kswapd 的开销。