
alloc_pages_bulk_noprof()是内核提供的批量页面分配接口。它的核心设计目标是减少锁竞争和函数调用开销通过一次持有 PCPPer-CPU Pages锁连续分配多个页面填充到调用方提供的数组中而不是反复调用单页分配接口。函数签名与参数unsigned long alloc_pages_bulk_noprof(gfp_t gfp, int preferred_nid, nodemask_t *nodemask, int nr_pages, struct page **page_array)参数含义gfp分配标志preferred_nid首选 NUMA 节点nodemask允许的节点掩码nr_pages请求分配的页数数组长度page_array输出数组用于存放分配的页面指针返回值实际成功分配的页数。核心特性与约束1. 只填充 NULL 槽位该接口的一个关键语义是它只填充数组中为 NULL 的槽位已经非 NULL 的槽位会被跳过。代码中通过nr_populated来跟踪已填充的槽位数量并在水位检查中使用(nr_pages - nr_populated)来计算剩余需求。2. 仅支持 order-0alloc_pages_bulk_noprof()是专门为单页order-0分配设计的不支持高阶分配。如果需要分配高阶页必须使用__alloc_pages_noprof()等单次分配接口。3. 不支持 memcg 记账代码中有一条明确的限制检查/* Bulk allocator does not support memcg accounting. */ if (memcg_kmem_online() (gfp __GFP_ACCOUNT)) goto failed;如果内存 cgroup 在线且分配请求带有__GFP_ACCOUNT批量分配会直接失败回退到单页分配路径。4. 单页请求回退如果nr_pages为 1或者数组中只剩 1 个槽位需要填充函数会回退到标准的单页分配接口因为批量路径的开销不值得为单页付出。分配策略与水位检查批量分配的核心优化在于一次性判断 zone 是否有足够的内存满足整批需求而不是为每一页单独做水位检查。水位检查的标记值计算方式为mark wmark_pages(zone, alloc_flags ALLOC_WMARK_MASK) nr_pages - nr_populated;这里nr_pages - nr_populated是本次调用还需要分配的页数上界避免重复计算已存在的槽位。在 PCP 锁下的批量分配循环水位检查通过后函数会获取 PCP 锁然后在一个循环中连续从 PCP 链表取页直到达到请求数量或 PCP 链表耗尽pcp_list pcp-lists[order_to_pindex(ac.migratetype, 0)]; while (nr_populated nr_pages) { page __rmqueue_pcplist(zone, 0, ac.migratetype, alloc_flags, pcp, pcp_list); if (unlikely(!page)) { /* 如果已经分配了至少一页可以提前结束 */ if (!nr_populated) { pcp_spin_unlock(pcp); goto failed_irq; } break; } prep_new_page(page, 0, gfp, 0); set_page_refcounted(page); page_array[nr_populated] page; }这个循环的关键点是只在开始时获取一次 PCP 锁循环内部不再重复获取锁大幅减少了锁操作开销。缺失的追踪钩子已修复在 2026 年 9 月的补丁中开发者发现批量分配路径遗漏了trace_mm_page_alloc()和kmsan_alloc_page()调用导致批量分配的页面在 ftrace/BPF/perf 中不可见KMSAN 的影子内存也未更新。补丁在prep_new_page()和set_page_refcounted()之后添加了这两个钩子与标准单页分配路径保持一致。总结alloc_pages_bulk_noprof()是内核为高性能批量单页分配设计的专用接口。它通过“一次锁、多次分配”的模式减少 PCP 锁竞争通过“只填充 NULL 槽位”的语义支持增量填充通过“整批水位检查”避免逐页判断的开销。它的约束也很明确仅 order-0、不支持 memcg 记账、单页请求回退。