☰
page_alloc wake_all_kswapds
2026/10/13 16:40:00 网站建设 项目流程

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。这导致两个问题:

  1. 唤醒无关的 kswapd:可能唤醒了highest_zoneidx之外的节点,造成不必要的 CPU 消耗;

  2. 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 的开销。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询