get_page_from_freelist()是伙伴系统快速路径的核心分配函数,负责遍历 zonelist,找到第一个满足水位条件的 zone 并尝试分配。
核心作用与定位
它是__alloc_frozen_pages_noprof()(旧版__alloc_pages_nodemask())在首次尝试分配时调用的函数。如果快速路径失败,才会进入__alloc_pages_slowpath()慢速路径。
核心逻辑
static struct page * get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags, const struct alloc_context *ac) { struct zoneref *z = ac->preferred_zoneref; struct zone *zone; struct pglist_data *last_pgdat_dirty_limit = NULL; bool no_fallback; retry: no_fallback = alloc_flags & ALLOC_NOFRAGMENT; z = ac->preferred_zoneref; for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx, ac->nodemask) { struct page *page; unsigned long mark; /* cpuset 限制检查 */ if (cpusets_enabled() && (alloc_flags & ALLOC_CPUSET) && !__cpuset_zone_allowed(zone, gfp_mask)) continue; /* 脏页限制检查(文件页缓存写分配) */ if (ac->spread_dirty_pages) { ... } /* ALLOC_NOFRAGMENT + NUMA 远程节点:丢弃 NOFRAGMENT 重试 */ if (no_fallback && nr_online_nodes > 1 && zone != ac->preferred_zoneref->zone) { int local_nid = zone_to_nid(ac->preferred_zoneref->zone); if (zone_to_nid(zone) != local_nid) { alloc_flags &= ~ALLOC_NOFRAGMENT; goto retry; } } /* 水位检查 */ mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK); if (!zone_watermark_fast(zone, order, mark, ac_classzone_idx(ac), alloc_flags)) { /* deferred pages 补充 */ if (static_branch_unlikely(&deferred_pages)) { if (_deferred_grow_zone(zone, order)) goto try_this_zone; } /* ALLOC_NO_WATERMARKS:跳过水位限制 */ if (alloc_flags & ALLOC_NO_WATERMARKS) goto try_this_zone; /* node reclaim 判断 */ if (node_reclaim_mode == 0 || !zone_allows_reclaim(ac->preferred_zoneref->zone, zone)) continue; ret = node_reclaim(zone->zone_pgdat, gfp_mask, order); switch (ret) { case NODE_RECLAIM_NOSCAN: continue; case NODE_RECLAIM_FULL: continue; default: if (zone_watermark_ok(zone, order, mark, ac_classzone_idx(ac), alloc_flags)) goto try_this_zone; continue; } } try_this_zone: /* 实际分配 */ page = rmqueue(ac->preferred_zoneref->zone, zone, order, gfp_mask, alloc_flags, ac->migratetype); if (page) { prep_new_page(page, order, gfp_mask, alloc_flags); /* 高阶原子分配:保留 highatomic pageblock */ if (unlikely(order && (alloc_flags & ALLOC_HARDER))) reserve_highatomic_pageblock(page, zone, order); return page; } } return NULL; }逐步说明
1. 遍历 zonelist
for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx, ac->nodemask)
从preferred_zoneref开始,遍历所有符合条件的 zone(zone 类型 ≤ 首选类型,且节点在 nodemask 中)。
2. cpuset 检查
if (cpusets_enabled() && (alloc_flags & ALLOC_CPUSET) && !__cpuset_zone_allowed(zone, gfp_mask)) continue;
如果启用了 cpuset 且调用方要求检查,跳过不允许的 zone。
3. 脏页限制检查
if (ac->spread_dirty_pages) { if (last_pgdat_dirty_limit == zone->zone_pgdat) continue; if (!node_dirty_ok(zone->zone_pgdat)) { last_pgdat_dirty_limit = zone->zone_pgdat; continue; } }当调用方设置__GFP_WRITE(文件页缓存写分配)时,检查节点的脏页数是否超限,避免单节点脏页过多。
4. ALLOC_NOFRAGMENT 与 NUMA 远程节点
if (no_fallback && nr_online_nodes > 1 && zone != ac->preferred_zoneref->zone) { int local_nid = zone_to_nid(ac->preferred_zoneref->zone); if (zone_to_nid(zone) != local_nid) { alloc_flags &= ~ALLOC_NOFRAGMENT; goto retry; } }如果启用了ALLOC_NOFRAGMENT(避免碎片)但当前 zone 是远程节点,则丢弃 NOFRAGMENT 标志并重新扫描。因为跨节点分配时防碎片的意义不大。
5. 水位检查
mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK); if (!zone_watermark_fast(zone, order, mark, ac_classzone_idx(ac), alloc_flags))
zone_watermark_fast()检查水位。
6. 水位不足时的处理
deferred pages 补充:启动早期内存未完全初始化时,尝试补充后重试。
ALLOC_NO_WATERMARKS:高优先级分配(如 PF_MEMALLOC)跳过水位检查,直接分配。
node reclaim:如果开启了节点回收且回收距离合法,尝试回收该节点的内存,成功后重新检查水位。
7. 实际分配
try_this_zone: page = rmqueue(ac->preferred_zoneref->zone, zone, order, gfp_mask, alloc_flags, ac->migratetype); if (page) { prep_new_page(page, order, gfp_mask, alloc_flags); if (unlikely(order && (alloc_flags & ALLOC_HARDER))) reserve_highatomic_pageblock(page, zone, order); return page; }水位满足后,调用rmqueue()从 PCP 或伙伴系统取页,再通过prep_new_page()校验和初始化。高阶原子分配还会保留 highatomic pageblock。
在分配路径中的位置
__alloc_frozen_pages_noprof() ├── get_page_from_freelist(...) // 快速路径 │ ├── 遍历 zonelist │ ├── zone_watermark_fast() // 水位检查 │ ├── node_reclaim() // 条件性回收 │ └── rmqueue() + prep_new_page() // 取页并准备 └── if (!page) __alloc_pages_slowpath(...) // 慢速路径
总结
get_page_from_freelist()是伙伴系统快速路径的核心调度器:它遍历 zonelist,对每个 zone 执行 cpuset/脏页限制检查、ALLOC_NOFRAGMENT远程节点处理、zone_watermark_fast()水位判断;水位不足时尝试 deferred pages 补充或 node reclaim,满足条件后调用rmqueue()取页并通过prep_new_page()完成初始化。它是__alloc_frozen_pages_noprof()首次分配尝试的执行者,失败后才进入慢速路径。