setup_per_zone_wmarks()是__setup_per_zone_wmarks()的外层封装与串行化函数。它的职责很简单:获取保护水位配置的全局互斥锁,然后调用__setup_per_zone_wmarks()完成实际计算。
核心实现
void setup_per_zone_wmarks(void) { mutex_lock(&pcp_batch_high_lock); __setup_per_zone_wmarks(); mutex_unlock(&pcp_batch_high_lock); }关键点解析
1. 使用pcp_batch_high_lock保护
pcp_batch_high_lock:
/* prevent >1 _updater_ of zone percpu pageset ->high and ->batch fields */ static DEFINE_MUTEX(pcp_batch_high_lock);
这个锁的注释是针对 PCP 的high/batch字段更新的,但它同样被setup_per_zone_wmarks()复用来保护水位配置。原因是水位和 PCP 的high值紧密相关:
zone_highsize()在默认策略下基于low_wmark_pages(zone)计算 PCP 的high_min;如果水位在计算过程中被并发修改,PCP 的
high也会基于不一致的水位计算,导致配置错乱。
所以两者共用同一把锁,确保水位和 PCP 配置的更新是串行且一致的。
2. 为什么需要互斥?
__setup_per_zone_wmarks()会遍历所有 zone,对每个 zone 持zone->lock修改_watermark[]。但遍历本身和全局参数(如min_free_kbytes、watermark_scale_factor)的读取需要保护,避免:
多个更新者(如两个 CPU 同时写 sysctl)并发执行;
一个更新者在修改
watermark_scale_factor时,另一个在执行__setup_per_zone_wmarks()读到半更新的值。
pcp_batch_high_lock作为全局互斥锁,保证同一时间只有一个更新者。
调用场景
它被以下路径调用:
| 调用者 | 场景 |
|---|---|
init_per_zone_wmark_min() | 启动初始化 |
min_free_kbytes_sysctl_handler() | 用户修改vm.min_free_kbytes |
watermark_scale_factor_sysctl_handler() | 用户修改vm.watermark_scale_factor |
watermark_boost_factor_sysctl_handler() | 用户修改vm.watermark_boost_factor |
| 内存热插拔 | zone 大小变化后 |
| CPU 热插拔 | 在线 CPU 数变化影响水位计算时 |
与__setup_per_zone_wmarks()的分工
| 函数 | 职责 |
|---|---|
setup_per_zone_wmarks() | 加锁/解锁,串行化 |
__setup_per_zone_wmarks() | 实际计算和设置各 zone 的_watermark[] |
这种“外层加锁 + 内层执行”的模式在内核中很常见,便于在已持锁的上下文中直接调用内层函数(避免重复加锁)。
与calculate_totalreserve_pages()的关系
__setup_per_zone_wmarks()末尾会调用calculate_totalreserve_pages(),因为水位变化会影响totalreserve_pages的值。这个调用在持锁状态下完成,确保汇总值的一致性。
总结
setup_per_zone_wmarks()是水位配置的串行化入口:它获取pcp_batch_high_lock,调用__setup_per_zone_wmarks()完成各 zone 水位的计算和设置,然后释放锁。使用同一把锁保护水位和 PCP 配置,是因为两者在默认策略下相互依赖——水位决定了 PCP 的high_min。它被启动初始化、sysctl 修改、内存/CPU 热插拔等路径调用,确保水位更新在并发环境下的一致性和串行化。