rmqueue_buddy()是页面分配路径中绕过 PCP 缓存、直接访问伙伴系统的函数。它通常在rmqueue()中作为 PCP 缓存分配失败后的后备路径被调用。
核心作用与定位
它的核心职责是在持有zone->lock保护的前提下,从伙伴系统的空闲链表中取出一个页块。与__rmqueue()相比,它多了一层封装:处理 HIGHATOMIC 保留区、循环重试坏页,并更新分配统计。
核心逻辑
根据 Mel Gorman 在 2022 年将其从rmqueue()中拆分出来的补丁,它的典型实现如下:
static __always_inline struct page *rmqueue_buddy(struct zone *preferred_zone, struct zone *zone, unsigned int order, unsigned int alloc_flags, int migratetype) { struct page *page; unsigned long flags; do { page = NULL; spin_lock_irqsave(&zone->lock, flags); /* * order-0 请求可能因非 CMA 分配上下文而跳过 pcplist。 * HIGHATOMIC 区保留给高阶原子分配,order-0 请求应跳过它。 */ if (order > 0 && alloc_flags & ALLOC_HARDER) { page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC); if (page) trace_mm_page_alloc_zone_locked(page, order, migratetype); } if (!page) { page = __rmqueue(zone, order, migratetype, alloc_flags); if (!page) { spin_unlock_irqrestore(&zone->lock, flags); return NULL; } } __mod_zone_freepage_state(zone, -(1 << order), get_pcppage_migratetype(page)); spin_unlock_irqrestore(&zone->lock, flags); } while (check_new_pages(page, order)); __count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order); zone_statistics(preferred_zone, zone, 1); return page; }逐步说明
1. 获取zone->lock
spin_lock_irqsave(&zone->lock, flags);
伙伴系统的空闲链表由zone->lock保护,所有操作必须在此锁下进行。
2. HIGHATOMIC 优先(仅限高阶ALLOC_HARDER)
if (order > 0 && alloc_flags & ALLOC_HARDER) { page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);MIGRATE_HIGHATOMIC是为高阶原子分配(如GFP_ATOMIC且 order > 0)预留的迁移类型。仅在
ALLOC_HARDER标志(高优先级分配)且order > 0时尝试,确保这些稀缺资源不被低优先级请求消耗。
3. 常规伙伴分配
page = __rmqueue(zone, order, migratetype, alloc_flags);
调用前面讲过的
__rmqueue(),它内部会依次尝试__rmqueue_smallest、__rmqueue_claim、__rmqueue_steal等策略。
4. 更新空闲页统计
__mod_zone_freepage_state(zone, -(1 << order), get_pcppage_migratetype(page));
从 zone 的空闲页计数中减去刚分配的页数。
迁移类型从页的
private字段(get_pcppage_migratetype)获取,可能因 fallback 而不同于请求类型。
5. 坏页检查与重试
} while (check_new_pages(page, order));
分配到的页需要经过
check_new_pages()校验。如果发现坏页(调试配置下),释放锁后重新尝试分配,避免把损坏的页交给调用方。
6. 更新 VM 事件与 NUMA 统计
__count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order); zone_statistics(preferred_zone, zone, 1);
PGALLOC:记录页分配事件。zone_statistics:更新 NUMA 命中/未命中统计。
在分配路径中的位置
rmqueue_buddy()被rmqueue()在 PCP 路径无法满足时调用:
rmqueue() ├── if (pcp_allowed_order(order)) { │ page = rmqueue_pcplist(...); // 尝试 PCP │ if (page) return page; │ } └── page = rmqueue_buddy(preferred_zone, zone, order, alloc_flags, migratetype); // ← 这里总结
rmqueue_buddy()是伙伴系统的直接分配入口:它在zone->lock保护下,优先处理高阶ALLOC_HARDER请求的MIGRATE_HIGHATOMIC保留区,然后通过__rmqueue()执行多级降级分配,更新空闲页统计,并在坏页时循环重试。它是rmqueue()在 PCP 缓存无法满足请求时的后备路径,确保了分配在绕过 CPU 缓存后仍能安全、准确地完成。