[PATCH v3 7/8] mm/page_alloc: retry alloc_contig_pages() when the zonelist is rebuilt

From: Gregory Price (Meta)

Date: Tue Oct 20 2026 - 22:41:42 EST


Onlining the first block of a zone rebuilds every zonelist in place,
shifting the slots after the new zone. alloc_contig_pages() can then
take a range from a zone or node its filters rejected.

Writing 1 to a node's 1G nr_hugepages while that node's first
ZONE_MOVABLE block comes online puts the gigantic page in ZONE_MOVABLE,
which commit 79b63f12abcb ("mm, hugetlb: do not allocate non-migrateable
gigantic pages from movable zones") excludes.

alloc_contig_frozen_pages_noprof() walks the zonelist with the caller's
gfp and nodemask, and the iterator approves a slot before the walk loads
the zone from it:

CPU 0 CPU 1
alloc_contig_frozen_pages_noprof(GFP_HIGHUSER | __GFP_THISNODE)
slot 0 (NORMAL1) approved
online_pages()
build_all_zonelists()
[NORMAL1]
-> [MOVABLE1, NORMAL1]
zone = zonelist_zone(slot 0) /* MOVABLE1 */
alloc_contig_frozen_range(MOVABLE1 pfns)

Check zonelist_update_seq before using each zone the walk returns, and
on the way out with nothing found, and restart the walk if it changed,
as the hugetlb pool walk does.

With kprobes holding the walk across the online, the base kernel put the
1G page in ZONE_MOVABLE in 3 of 3 runs, and this change put it in
ZONE_NORMAL in 3 of 3.

Calling alloc_contig_pages() directly, GFP_KERNEL with nodemask {1} got
a ZONE_MOVABLE range and nodemask {2} a node 1 range in 3 of 3 runs
each, and neither did with this change.

Fixes: 5e27a2df03b8 ("mm/page_alloc: add alloc_contig_pages()")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
---
mm/page_alloc.c | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 5ca8e57c5fcbf..96ae09c8099be 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7592,6 +7592,7 @@ struct page *alloc_contig_frozen_pages_noprof(unsigned long nr_pages,
gfp_t gfp_mask, int nid, nodemask_t *nodemask)
{
unsigned long ret, pfn, flags;
+ unsigned int zonelist_cookie;
struct zonelist *zonelist;
struct zone *zone;
struct zoneref *z;
@@ -7600,8 +7601,13 @@ struct page *alloc_contig_frozen_pages_noprof(unsigned long nr_pages,

retry:
zonelist = node_zonelist(nid, gfp_mask);
+ zonelist_cookie = zonelist_iter_begin();
for_each_zone_zonelist_nodemask(zone, z, zonelist,
gfp_zone(gfp_mask), nodemask) {
+ /* A rebuild may have shifted an ineligible zone into this slot. */
+ if (unlikely(check_retry_zonelist(zonelist_cookie)))
+ goto retry;
+
spin_lock_irqsave(&zone->lock, flags);

pfn = ALIGN(zone->zone_start_pfn, nr_pages);
@@ -7640,6 +7646,8 @@ struct page *alloc_contig_frozen_pages_noprof(unsigned long nr_pages,
skip_hugetlb = false;
goto retry;
}
+ if (unlikely(check_retry_zonelist(zonelist_cookie)))
+ goto retry;
return NULL;
}
EXPORT_SYMBOL(alloc_contig_frozen_pages_noprof);
--
2.53.0-Meta