setup_per_cpu_pageset()是系统启动阶段初始化 PCP(Per-CPU Pages)缓存的总入口函数。它的核心职责是遍历所有 populated zone,为每个 zone 分配并初始化正式的 per-CPU pageset,从而让内核从启动早期的boot_pageset占位模式切换到真正的 PCP 缓存管理。
核心实现
void __init setup_per_cpu_pageset(void) { struct pglist_data *pgdat; struct zone *zone; int __maybe_unused cpu; for_each_populated_zone(zone) setup_zone_pageset(zone); #ifdef CONFIG_NUMA /* * Unpopulated zones continue using the boot pagesets. * The numa stats for these pagesets need to be reset. * Otherwise, they will end up skewing the stats of * the nodes these zones are associated with. */ for_each_possible_cpu(cpu) { struct per_cpu_zonestat *pzstats = &per_cpu(boot_zonestats, cpu); memset(pzstats->vm_numa_event, 0, sizeof(pzstats->vm_numa_event)); } #endif for_each_online_pgdat(pgdat) pgdat->per_cpu_nodestats = alloc_percpu(struct per_cpu_nodestat); }逐步解析
1. 为每个 populated zone 初始化正式 pageset
for_each_populated_zone(zone) setup_zone_pageset(zone);
遍历所有有内存的 zone,调用setup_zone_pageset()(前面讲过),为每个 zone 动态分配并初始化per_cpu_pages和per_cpu_zonestat数组。
2. NUMA 统计清理(CONFIG_NUMA)
for_each_possible_cpu(cpu) { struct per_cpu_zonestat *pzstats = &per_cpu(boot_zonestats, cpu); memset(pzstats->vm_numa_event, 0, sizeof(pzstats->vm_numa_event)); }注释解释得很清楚:未被 populated 的 zone 会继续使用boot_pageset(因为setup_zone_pageset()只对 populated zone 调用)。这些 zone 在启动早期可能已经往boot_zonestats中累积了一些 NUMA 事件统计。如果不清理,这些“残留数据”会歪曲所属节点的统计。这里把boot_zonestats的vm_numa_event清零,确保统计准确。
3. 分配节点的 per-CPU 统计
for_each_online_pgdat(pgdat) pgdat->per_cpu_nodestats = alloc_percpu(struct per_cpu_nodestat);
为每个在线 NUMA 节点分配 per-CPU 的节点统计结构,用于记录各 CPU 对应该节点的分配、回收等统计信息。
调用时机
setup_per_cpu_pageset()是__init函数,在启动流程中被调用,通常位于 per-CPU 分配器就绪之后、正式的内存管理子系统开始工作之前。它的调用标志着PCP 机制从“引导模式”正式切换到“运行模式”。
与相关函数的关系
| 函数 | 角色 |
|---|---|
setup_per_cpu_pageset() | 总入口,遍历 zone 并初始化 |
setup_zone_pageset(zone) | 单个 zone 的 pageset 分配与初始化 |
per_cpu_pages_init() | 单个 CPU 的 PCP 结构初始化 |
zone_set_pageset_high_and_batch() | 计算并设置 batch/high 配置 |
zone_pcp_init() | 启动早期将 zone 指向boot_pageset占位 |
总结
setup_per_cpu_pageset()是 PCP 缓存的启动初始化中枢:它遍历所有 populated zone 调用setup_zone_pageset()建立正式的 per-CPU pageset,清理未被 populated 的 zone 残留在boot_zonestats中的 NUMA 统计,并为每个在线节点分配 per-CPU 的节点统计结构。它标志着内核从使用静态的boot_pageset过渡到动态管理的 per-CPU 页缓存,是内存子系统启动流程中的关键步骤。