☰
page_alloc __setup_per_zone_wmarks
2026/10/19 2:10:45 网站建设 项目流程

__setup_per_zone_wmarks()是内存管理中用于计算并设置每个 zone 水位线(watermark)的核心函数。水位线决定了 kswapd 何时启动回收、直接回收何时触发,以及分配器在什么条件下允许分配。

核心作用与背景

每个 zone 有三个水位线:

  • WMARK_MIN:最低水位。低于此值,分配器会触发直接回收,甚至 OOM。

  • WMARK_LOW:低水位。低于此值,唤醒 kswapd 后台回收。

  • WMARK_HIGH:高水位。kswapd 回收到此值以上才停止。

__setup_per_zone_wmarks()根据全局的min_free_kbytes和watermark_scale_factor,为每个 zone 计算这三个值。

典型实现

static void __setup_per_zone_wmarks(void) { unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10); unsigned long lowmem_pages = 0; struct zone *zone; unsigned long flags;
/* Calculate total number of !ZONE_HIGHMEM pages */ for_each_zone(zone) { if (!is_highmem(zone)) lowmem_pages += zone->managed_pages; } for_each_zone(zone) { u64 tmp; spin_lock_irqsave(&zone->lock, flags); /* * 按 zone 在低端内存中的占比分配 min_free_kbytes */ tmp = (u64)pages_min * zone->managed_pages; do_div(tmp, lowmem_pages); if (is_highmem(zone)) { /* * HIGHMEM 没有自己独立的水位,使用低端内存水位的 * 四分之一作为近似值。 */ unsigned long min_pages; min_pages = zone->managed_pages / 1024; min_pages = clamp_val(min_pages, SWAP_CLUSTER_MAX, 128UL * 1024 * 1024 / PAGE_SIZE); min_pages >>= 2; zone->_watermark[WMARK_MIN] = min_pages; } else { zone->_watermark[WMARK_MIN] = tmp; } /* * 设置 LOW 和 HIGH 水位 * 间距由 watermark_scale_factor 控制 */ tmp = max_t(u64, tmp >> 2, mult_frac(zone->managed_pages, watermark_scale_factor, 10000)); zone->watermark_boost = 0; zone->_watermark[WMARK_LOW] = min_wmark_pages(zone) + tmp; zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + tmp * 2; zone->watermark_boost = 0; spin_unlock_irqrestore(&zone->lock, flags); } /* 更新 totalreserve_pages */ calculate_totalreserve_pages();

}

逐步解析

1. 计算pages_min和lowmem_pages

unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10); unsigned long lowmem_pages = 0; for_each_zone(zone) { if (!is_highmem(zone)) lowmem_pages += zone->managed_pages; }
  • min_free_kbytes从 KB 转成页数。

  • lowmem_pages:所有非 HIGHMEMzone 的 managed_pages 总和。HIGHMEM 不参与min_free_kbytes的分配。

2. 按比例分配WMARK_MIN

tmp = (u64)pages_min * zone->managed_pages; do_div(tmp, lowmem_pages);
  • 每个非 HIGHMEM zone 按自己在低端内存中的占比,分到min_free_kbytes的一部分。

  • 用do_div做 64 位除法,避免溢出。

3. HIGHMEM 的特殊处理

if (is_highmem(zone)) { min_pages = zone->managed_pages / 1024; min_pages = clamp_val(min_pages, SWAP_CLUSTER_MAX, 128UL * 1024 * 1024 / PAGE_SIZE); min_pages >>= 2; zone->_watermark[WMARK_MIN] = min_pages; }
  • HIGHMEM 没有独立的 min_free_kbytes 分配,用managed_pages / 1024作为近似值。

  • clamp_val限制在[SWAP_CLUSTER_MAX, 128MB]之间。

  • >> 2即除以 4,进一步降低 HIGHMEM 的 min 水位。

4. 计算WMARK_LOW和WMARK_HIGH

tmp = max_t(u64, tmp >> 2, mult_frac(zone->managed_pages, watermark_scale_factor, 10000)); zone->_watermark[WMARK_LOW] = min_wmark_pages(zone) + tmp; zone->_watermark[WMARK_HIGH] = min_wmark_pages(zone) + tmp * 2;
  • tmp是 LOW 和 HIGH 之间的间距:

    • 至少是WMARK_MIN / 4(tmp >> 2);

    • 或者由watermark_scale_factor决定(默认 10,即 0.1% 的 managed_pages)。

    • 取两者较大值。

  • WMARK_LOW = WMARK_MIN + tmp;

  • WMARK_HIGH = WMARK_MIN + 2 * tmp。

5. 重置watermark_boost

zone->watermark_boost = 0;

boost_watermark()会临时抬高水位。重新计算水位时,先清零 boost。

6. 更新totalreserve_pages

calculate_totalreserve_pages();

calculate_totalreserve_pages():水位变化后,重新汇总系统保留页数。

调用场景

它被setup_per_zone_wmarks()封装调用,后者在以下时机执行:

  • 启动初始化:init_per_zone_wmark_min();

  • 内存热插拔:zone 大小变化后;

  • CPU 热插拔:在线 CPU 数变化影响水位计算时;

  • sysctl 修改:调整min_free_kbytes、watermark_scale_factor后。

历史演进

2021 年:Mel Gorman 引入watermark_scale_factor和mult_frac计算,替代了之前按managed_pages / 100的粗略方式,使水位间距更可配置。

2023 年:boost_watermark()和watermark_boost_factor机制被引入,用于在碎片化事件时临时抬高水位。

watermark_boost的重置:每次重新计算水位时清零,避免过期的 boost 影响新水位。

总结

__setup_per_zone_wmarks()是水位线的计算器:它把全局的min_free_kbytes按各非 HIGHMEM zone 的managed_pages占比分配为WMARK_MIN,HIGHMEM 用managed_pages / 1024 / 4近似;然后用max(WMARK_MIN/4, watermark_scale_factor * managed_pages / 10000)作为间距,计算出WMARK_LOW和WMARK_HIGH;最后清零watermark_boost并更新totalreserve_pages。它是内存回收触发时机和分配器水位判断的基础。

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

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

立即咨询