
调试/校验辅助函数检查某个page是否落在给定 zone 的边界之外。它主要服务于CONFIG_DEBUG_VM下的断言。一、函数签名static int page_outside_zone_boundaries(struct zone *zone, struct page *page)参数参数类型含义zonestruct zone *要检查的目标 zonepagestruct page *要检查的页返回值int非 0 表示 page 在 zone 边界之外即 page 不属于该 zone0 表示在边界内。返回int而非bool是历史风格语义上当作布尔用。static文件内私有只在page_alloc.c内部使用。通常被bad_page()、free_pages_prepare()、check_new_page()等调试检查调用。二、要解决的问题1. page 与 zone 的归属关系每个struct page都属于唯一一个 zone通过page_zone(page)可查。zone 由PFN 范围定义zone-zone_start_pfn起始页帧号zone-spanned_pages/zone-present_pages覆盖的页数。判断 page 是否在 zone 内就是看它的 pfn 是否落在[zone_start_pfn, zone_start_pfn spanned_pages)区间。2. 为什么需要这个检查在调试配置CONFIG_DEBUG_VM下内核会在关键路径上校验释放页时确认要释放的 page 确实属于它声称的 zone伙伴系统操作时确认 page 与 zone 匹配捕获内存损坏、page 结构错乱、zone 计算错误等 bug。如果 page 与 zone 不匹配继续操作会破坏伙伴系统、导致崩溃或数据损坏所以需要尽早发现。三、典型实现static int page_outside_zone_boundaries(struct zone *zone, struct page *page) { int ret 0; unsigned seq; unsigned long pfn page_to_pfn(page); unsigned long sp, start_pfn; do { seq zone_span_seqbegin(zone); start_pfn zone-zone_start_pfn; sp zone-spanned_pages; if (!zone_spans_pfn(zone, pfn)) ret 1; } while (zone_span_seqretry(zone, seq)); return ret; }逐步说明1.pfn page_to_pfn(page)把 page 转成页帧号。2. 用 seqlock 读取 zone 的 pfn 范围do { seq zone_span_seqbegin(zone); start_pfn zone-zone_start_pfn; sp zone-spanned_pages; if (!zone_spans_pfn(zone, pfn)) ret 1; } while (zone_span_seqretry(zone, seq));zone_span_seqbegin/seqretryzone 的 pfn 范围zone_start_pfn、spanned_pages可能被内存热插拔动态修改。用seqlock保证读到的是一致快照seqbegin读取序号读start_pfn、spanned_pagesseqretry检查序号是否变化若变化期间有写则重试。zone_spans_pfn(zone, pfn)判断 pfn 是否在 zone 覆盖范围内static inline bool zone_spans_pfn(const struct zone *zone, unsigned long pfn) { return zone-zone_start_pfn pfn pfn zone-zone_start_pfn zone-spanned_pages; }如果不在范围内ret 1表示在边界外。3. 返回结果ret 1page 在 zone 外不匹配。ret 0page 在 zone 内匹配。为什么用 seqlockzone 的zone_start_pfn/spanned_pages在内存热插拔时可能变化。调试检查可能在任意时刻发生若与热插拔并发读到的范围可能不一致。seqlock 提供无锁、一致的读快照适合读多写少的调试检查。简化版本如果不考虑热插拔并发实现可能简化为static int page_outside_zone_boundaries(struct zone *zone, struct page *page) { unsigned long pfn page_to_pfn(page); return !zone_spans_pfn(zone, pfn); }即直接检查 pfn 是否在 zone 范围内。但生产内核倾向用 seqlock 版本以保证热插拔安全。四、典型调用场景1.bad_page()或页检查static void check_new_page(struct page *page) { ... if (page_outside_zone_boundaries(page_zone(page), page)) bad_page(page, outside zone boundaries); ... }分配新页时校验 page 是否真的属于它所在的 zone。2. 释放页前的准备static bool free_pages_prepare(struct page *page, unsigned int order, ...) { ... if (page_outside_zone_boundaries(page_zone(page), page)) bad_page(page, freeing page outside zone); ... }释放页前确认 page 与 zone 匹配。3.free_pcppages_bulk()等 PCP 批量归还批量释放时校验页归属。4. 条件编译通常整个调用被CONFIG_DEBUG_VM包裹#ifdef CONFIG_DEBUG_VM if (page_outside_zone_boundaries(zone, page)) { ... } #endif未开CONFIG_DEBUG_VM时该函数可能不被调用甚至不编译零运行开销。五、与前面内容的呼应前面出现过的这里的关联page_zone(page)取 page 所属 zone常作为本函数第一个参数zone_spans_pfn()本函数的核心判断前面在get_pfnblock_bitmap_bitidx()的VM_BUG_ON_PAGE里见过VM_BUG_ON_PAGE调试断言本函数常与其配合bad_page()检测到 page 异常时的报错函数调用本函数CONFIG_DEBUG_VM控制调试检查是否启用内存热插拔zone_start_pfn/spanned_pages可能变化故用 seqlock调用链check_new_page() / free_pages_prepare() / bad_page() └── page_outside_zone_boundaries(zone, page) // CONFIG_DEBUG_VM 下 ├── pfn page_to_pfn(page) ├── seqlock 读 zone_start_pfn / spanned_pages └── !zone_spans_pfn(zone, pfn) → ret1在外六、小结表项说明函数名page_outside_zone_boundaries作用检查 page 是否落在 zone 边界之外参数zone目标 zone参数page目标 page返回值int非 0 在 zone 外0 在 zone 内可见性static文件内私有核心判断!zone_spans_pfn(zone, page_to_pfn(page))并发处理用zone_span_seqbegin/seqretryseqlock保证热插拔下读取一致使用场景CONFIG_DEBUG_VM下校验 page/zone 归属配合bad_page()/VM_BUG_ON_PAGE运行开销调试配置下才有生产内核通常不调用或零开销一句话总结page_outside_zone_boundaries()是一个调试校验函数把 page 转成 pfn检查它是否落在给定 zone 的[zone_start_pfn, zone_start_pfn spanned_pages)范围内用 seqlock 保证内存热插拔下读取一致返回非 0 表示 page 在 zone 外