alloc_contig_pages_noprof()是内核中用于分配大块物理连续内存的接口。当伙伴系统(buddy allocator)因内存碎片化无法满足需求时,它通过扫描 zonelist、迁移现有页面的方式,主动“腾出”一段连续物理内存。
核心作用与设计动机
伙伴系统分配连续内存的最大阶数受限于MAX_ORDER(通常为 4MB)。但某些设备(如摄像头、GPU、DMA 控制器)需要更大的、物理地址完全连续的内存块(例如 1GB)。
alloc_contig_pages_noprof()解决这个问题的思路是:不依赖伙伴系统“恰好”有空闲的连续块,而是主动扫描物理地址空间,找到候选范围后,把范围内的“住户”迁走,再把腾出的空地交给调用方。
核心流程:扫描、验证、执行
该函数是一个外层搜索封装,内层通过__alloc_contig_pages()调用alloc_contig_range()完成实际分配。
1. 遍历 zonelist 寻找候选 zone
zonelist = node_zonelist(nid, gfp_mask); for_each_zone_zonelist_nodemask(zone, z, zonelist, gfp_zone(gfp_mask), nodemask) { spin_lock_irqsave(&zone->lock, flags); pfn = ALIGN(zone->zone_start_pfn, nr_pages); while (zone_spans_last_pfn(zone, pfn, nr_pages)) { ... } }它从指定节点(nid)的 zonelist 中,按 zone 类型限制(gfp_zone(gfp_mask))和节点掩码(nodemask)遍历所有候选 zone。
2. 按nr_pages对齐扫描
pfn = ALIGN(zone->zone_start_pfn, nr_pages); while (zone_spans_last_pfn(zone, pfn, nr_pages)) { pfn += nr_pages; }在 zone 内,从对齐后的起始 PFN 开始,以nr_pages为步长向上扫描。这意味着它只检查按请求大小对齐的候选范围。
3. 快速预筛(pfn_range_valid_contig)
对每个候选范围,调用pfn_range_valid_contig()做低成本检查:页是否在线、是否属于目标 zone、是否为保留页。如果通过,释放 zone 锁后再调用内层分配函数。
释放锁的原因:注释明确解释——alloc_contig_range()稍后也会获取zone->lock,如果一直持有,可能与其他自旋等待该锁的分配者形成竞争,导致alloc_contig_range()失败。
4. 内层执行与重试
spin_unlock_irqrestore(&zone->lock, flags); ret = __alloc_contig_pages(pfn, nr_pages, gfp_mask); if (!ret) return pfn_to_page(pfn); spin_lock_irqsave(&zone->lock, flags);
如果__alloc_contig_pages()成功(返回 0),立即返回pfn_to_page(pfn)。失败则重新获取锁,继续扫描下一个候选范围。
关键演进:HugeTLB 页迁移
2025 年底的 RFC 补丁page_alloc: allow migration of smaller hugepages during contig_alloc改变了该函数的行为:
旧行为:pfn_range_valid_contig()遇到PageHuge(page)直接返回false,即完全跳过包含 HugeTLB 页的范围,认为它们不可迁移。
新行为(RFC 补丁):引入两轮扫描机制。
第一轮:仍然跳过 HugeTLB 页(
skip_hugetlb = true),尝试快速分配。第二轮:如果第一轮失败且确实因为 HugeTLB 页被跳过(
skipped_hugetlb = true),则设置skip_hugetlb = false并goto retry,允许迁移比请求范围更小的 HugeTLB 页(例如,为腾出 1GB 连续内存而迁移 2MB 大页)。
补丁的动机是:在多尺寸大页共存(2MB 和 1GB)的系统中,迁移小尺寸大页来满足大尺寸请求是合理的,能提高连续分配的成功率,同时避免无谓地迁移更大的页。
关键约束
释放方式:分配出的页必须用
free_contig_range()释放,或对每页手动调用__free_page()。不能分配复合页:该接口分配的页是独立的单页,每页引用计数为 1,不支持
__GFP_COMP(在alloc_contig_range层会检查并拒绝)。对齐特性:如果
nr_pages是 2 的幂,分配出的范围也会对齐到相同的nr_pages(例如 1GB 请求会对齐到 1GB 边界)。
总结
alloc_contig_pages_noprof()是内核在物理内存碎片化时获取大块连续内存的搜索与调度层:它遍历 zonelist,按请求大小对齐扫描候选 PFN 范围,用pfn_range_valid_contig()快速预筛,然后释放锁并委托__alloc_contig_pages()→alloc_contig_range()执行隔离和迁移。2025 年的 RFC 补丁引入两轮扫描,允许在必要时迁移比请求范围更小的 HugeTLB 页,进一步提升连续分配的成功率。