pfn_range_valid_contig()是alloc_contig_pages()在扫描候选 PFN 范围时的“快速有效性检查”函数。它用于在真正尝试分配之前,快速排除那些绝对不可能成功的范围,避免无谓地调用开销巨大的alloc_contig_range()。
核心作用:快速筛掉“绝对不行”的范围
alloc_contig_pages()会遍历 zonelist 中的每个 zone,对每个对齐的候选起始 PFN 调用此函数。如果返回false,alloc_contig_pages()会跳过该范围,继续尝试下一个。
static bool pfn_range_valid_contig(struct zone *z, unsigned long start_pfn, unsigned long nr_pages) { unsigned long i, end_pfn = start_pfn + nr_pages; struct page *page; for (i = start_pfn; i < end_pfn; i++) { page = pfn_to_online_page(i); if (!page) return false; if (page_zone(page) != z) return false; if (PageReserved(page)) return false; } return true; }三项核心检查
1.pfn_to_online_page(i)— 页必须存在且在线
如果 PFN 对应的页根本不存在(内存空洞)或未上线,直接排除。
这是最基础的检查,确保后续操作不会访问无效的
struct page。
2.page_zone(page) != z— 页必须属于目标 zone
候选范围必须完全落在同一个 zone 内。
如果跨越了 zone 边界,排除该范围。
3.PageReserved(page)— 页不能是保留页
PageReserved的页(如内核镜像、设备内存、BIOS 区域)永远不能被迁移或分配。只要范围内有任何一页是保留页,整个范围就不可能成功,直接排除。
关键演进:2021 年移除的检查
在 2021 年的补丁(Oscar Salvador)之前,该函数还检查两项:
if (page_count(page) > 0) // 页正在被使用 return false; if (PageHuge(page)) // 页是 hugetlb 页 return false;
移除原因:
page_count(page) > 0:正在使用的页可以被__alloc_contig_pages()迁移走,所以“正在使用”并不代表“不能分配”。预先排除反而会显著降低分配成功率,错过那些本可通过迁移腾出的范围。PageHuge(page):isolate_migratepages_range()现在能够处理 hugetlb 页,因此也不需要预先排除。
更深层的原因:这两项检查本身是有竞态的——检查时页可能空闲,但alloc_contig_range()真正运行时可能已被占用;反之亦然。既然终究要在alloc_contig_range()中处理,不如信任底层函数的能力,让pfn_range_valid_contig()只做那些绝对必要且无竞态的检查。
在调用链中的位置
alloc_contig_pages(nr_pages, gfp_mask, nid, nodemask) └── for_each_zone_zonelist_nodemask(zone, ...): ├── spin_lock_irqsave(&zone->lock, flags) ├── pfn = ALIGN(zone->zone_start_pfn, nr_pages) └── while (zone_spans_last_pfn(zone, pfn, nr_pages)): ├── if (pfn_range_valid_contig(zone, pfn, nr_pages)) // ← 这里 │ ├── spin_unlock_irqrestore(&zone->lock, flags) │ └── __alloc_contig_pages(pfn, nr_pages, gfp_mask) └── pfn += nr_pages
注意:pfn_range_valid_contig()是在持有zone->lock的情况下调用的,用于快速筛选。一旦通过检查,锁会被释放,再调用__alloc_contig_pages()(它内部会重新获取锁)。注释解释了原因:alloc_contig_range()稍后也会获取zone->lock,如果一直持有,可能导致锁竞争或死锁。
总结
pfn_range_valid_contig()是alloc_contig_pages()的快速预筛器:它在持锁状态下遍历候选 PFN 范围,只做三项绝对必要且无竞态的检查——页是否在线、是否属于目标 zone、是否为保留页。它不再检查页是否正在使用或是否为 hugetlb 页,因为这些页可以被迁移,预先排除会降低分配成功率,且检查本身具有竞态。它的存在是为了低成本地过滤掉明显不可能的范围,避免对它们执行昂贵的隔离和迁移操作。