zone_spans_last_pfn()是alloc_contig_pages()在扫描候选 PFN 范围时用于边界检查的辅助函数。它判断一个从start_pfn开始、长度为nr_pages的范围,其最后一页是否仍然落在指定的 zone 内。
核心实现
static bool zone_spans_last_pfn(const struct zone *zone, unsigned long start_pfn, unsigned long nr_pages) { unsigned long last_pfn = start_pfn + nr_pages - 1; return zone->zone_start_pfn <= last_pfn && last_pfn < zone_end_pfn(zone); }逻辑解析
1. 计算最后一页的 PFN
unsigned long last_pfn = start_pfn + nr_pages - 1;
范围是
[start_pfn, start_pfn + nr_pages),所以最后一页的 PFN 是start_pfn + nr_pages - 1。减 1 是关键——
nr_pages是页数,不是结束 PFN。
2. 判断最后一页是否在 zone 内
return zone->zone_start_pfn <= last_pfn && last_pfn < zone_end_pfn(zone);
zone->zone_start_pfn:zone 的起始 PFN(包含)。zone_end_pfn(zone):zone 的结束 PFN(不包含),等于zone_start_pfn + spanned_pages。两个条件都满足,说明最后一页在 zone 内。
为什么只检查最后一页?
因为alloc_contig_pages()的扫描是从 zone 起始位置按nr_pages对齐向上推进的:
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)) { ... } pfn += nr_pages; }pfn从对齐后的起始位置开始,每次递增nr_pages。只要第一页在 zone 内(由循环起点保证),且最后一页也在 zone 内,那么中间的页必然也在 zone 内(因为 zone 是连续 PFN 区间)。
所以只检查最后一页就够了,无需遍历整个范围。这是一个性能优化。
与zone_spans_pfn()的关系
前面在get_pfnblock_bitmap_bitidx()的VM_BUG_ON_PAGE中见过zone_spans_pfn():
static inline bool zone_spans_pfn(const struct zone *zone, unsigned long pfn) { return zone->zone_start_pfn <= pfn && pfn < zone_end_pfn(zone); }zone_spans_last_pfn()就是对范围最后一页调用zone_spans_pfn()的逻辑,只是没有直接调用它,而是内联了相同的判断,并多做了一步last_pfn的计算。
在调用链中的位置
alloc_contig_pages(nr_pages, gfp_mask, nid, nodemask) └── for_each_zone_zonelist_nodemask(zone, ...): ├── 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)) │ └── __alloc_contig_pages(pfn, nr_pages, gfp_mask) └── pfn += nr_pages
为什么需要它?
alloc_contig_pages()需要确保候选范围不跨越 zone 边界——因为alloc_contig_range()要求所有页属于同一个 zone(pfn_range_valid_contig()也会检查page_zone(page) != z)。
zone_spans_last_pfn()作为循环条件,当范围超出 zone 边界时自动终止扫描,避免对无效范围调用pfn_range_valid_contig()。
边界情况
nr_pages = 0:last_pfn = start_pfn - 1,可能下溢或落在 zone 外,但alloc_contig_pages()不会用 0 调用它。范围恰好到 zone 末尾:
last_pfn = zone_end_pfn(zone) - 1,满足条件,返回true。范围恰好超出 zone 末尾:
last_pfn = zone_end_pfn(zone),不满足< zone_end_pfn(zone),返回false,循环终止。
总结
zone_spans_last_pfn()是alloc_contig_pages()扫描循环的边界守门人:它计算候选范围的最后一页 PFN,判断它是否仍在 zone 的[zone_start_pfn, zone_end_pfn)区间内。由于扫描是按nr_pages对齐递增的,只需检查最后一页即可保证整个范围都在 zone 内。当范围超出 zone 边界时返回false,循环自动终止,避免对无效范围做后续检查。