ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

page_alloc zone_set_pageset_high_and_batch

page_alloc zone_set_pageset_high_and_batch

zone_set_pageset_high_and_batch()是 PCP(Per-CPU Pages)缓存配置更新的决策与协调函数。它负责计算 zone 的新 PCP 参数,判断是否需要更新,并在需要时通过__zone_set_pageset_high_and_batch()分发到所有 CPU。

核心实现

static void zone_set_pageset_high_and_batch(struct zone *zone, int cpu_online) { unsigned long new_high_min, new_high_max, new_batch; int old_batch, old_high_min, old_high_max; new_batch = max(1, zone_batchsize(zone)); new_high_min = zone_highsize(zone, new_batch, cpu_online, 0); new_high_max = zone_highsize(zone, new_batch, cpu_online, MIN_PERCPU_PAGELIST_HIGH_FRACTION); old_batch = zone->pageset_batch; old_high_min = zone->pageset_high_min; old_high_max = zone->pageset_high_max; if (new_batch == old_batch && new_high_min == old_high_min && new_high_max == old_high_max) return; zone->pageset_batch = new_batch; zone->pageset_high_min = new_high_min; zone->pageset_high_max = new_high_max; pr_info("Node %d, zone %8s: " "pCPUs=%d, batch=%d, high_min=%lu, high_max=%lu\n", zone_to_nid(zone), zone->name, cpu_online, new_batch, new_high_min, new_high_max); /* * The zone->lock is acquired here to avoid a race with * zone_pcp_update() which relies on the batch and high * values being visible. */ spin_lock_irq(&zone->lock); __zone_set_pageset_high_and_batch(zone, new_high_min, new_high_max, new_batch); spin_unlock_irq(&zone->lock); }

逐步解析

1. 计算新参数

new_batch = max(1, zone_batchsize(zone)); new_high_min = zone_highsize(zone, new_batch, cpu_online, 0); new_high_max = zone_highsize(zone, new_batch, cpu_online, MIN_PERCPU_PAGELIST_HIGH_FRACTION);
  • zone_batchsize():根据 zone 大小计算批量大小;

  • zone_highsize(..., 0):用默认策略计算high_min(基于低水位线);

  • zone_highsize(..., MIN_PERCPU_PAGELIST_HIGH_FRACTION):用用户配置上限计算high_max。

2. 检查是否需要更新

if (new_batch == old_batch && new_high_min == old_high_min && new_high_max == old_high_max) return;

如果三个值都没变,直接返回,避免无谓的分发和锁操作。这是热插拔路径上的重要优化——CPU 上线/下线时并不总是需要重新计算。

3. 更新 zone 级缓存值

zone->pageset_batch = new_batch; zone->pageset_high_min = new_high_min; zone->pageset_high_max = new_high_max;

把新值存入 zone 的pageset_*字段,供后续查询。

4. 持锁分发到所有 CPU

spin_lock_irq(&zone->lock); __zone_set_pageset_high_and_batch(zone, new_high_min, new_high_max, new_batch); spin_unlock_irq(&zone->lock);

前面讲过的__zone_set_pageset_high_and_batch():遍历所有可能 CPU,通过pageset_update()写入新值。这里持有zone->lock是为了避免与zone_pcp_update()的竞态——后者依赖batch和high值可见。

调用场景

  • zone 初始化:setup_zone_pageset()为每个 populated zone 设置初始 PCP 配置;

  • CPU 热插拔:CPU 上线/下线时,PCP 的 CPU 数量变化,需要重新分配份额;

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

  • sysctl 修改:用户调整vm.percpu_pagelist_high_fraction时。

与pcp_batch_high_lock的关系

虽然这个函数内部用zone->lock保护分发,但它的调用方必须持有pcp_batch_high_lock(全局互斥锁):

mutex_lock(&pcp_batch_high_lock); zone_set_pageset_high_and_batch(zone, cpu_online); mutex_unlock(&pcp_batch_high_lock);

pcp_batch_high_lock保证同一时间只有一个更新者修改 zone 的 pageset 配置,而zone->lock则保证分发过程中与zone_pcp_update()等路径的同步。

历史演进

2023 年,zone_set_pageset_high_and_batch()的签名从(struct zone *zone)变为(struct zone *zone, int cpu_online)。新增的cpu_online参数用于处理 CPU 热插拔时的竞态:当新 CPU 上线时,cpumask_of_node()可能尚未更新,导致zone_highsize()计算出的份额偏大。通过显式传入额外 CPU 数量,确保计算准确。

总结

zone_set_pageset_high_and_batch()是 PCP 配置的决策中枢:它计算新的batch/high_min/high_max,与旧值比较以决定是否需要更新,需要时更新 zone 级字段并在zone->lock保护下通过__zone_set_pageset_high_and_batch()分发到所有 CPU。它被 zone 初始化、CPU/内存热插拔和 sysctl 修改路径调用,是 PCP 机制自适应系统拓扑和配置变化的关键环节。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进