[PATCH 6/9] mm/compaction: trust zone->contiguous only in the span

From: Vlastimil Babka (SUSE)

Date: Tue Oct 27 2026 - 11:57:52 EST


From: Josef Bacik <josef@xxxxxxxxxxxxxx>

compact_zone() keeps the positions of its scanners in cc->migrate_pfn and
cc->free_pfn for the whole run. Memory offlining can shrink the zone in
the meantime. At the end of remove_pfn_range_from_zone(),
set_zone_contiguous() only looks at what is left of the zone. If that has
no hole, or if nothing is left, zone->contiguous is set again, and
pageblock_pfn_to_page() returns the memmap of any pfn unchecked, also of
one that the zone does not span any more.

The memmap of the range that left the zone is poisoned if CONFIG_DEBUG_VM
is set. Once the memory has been removed, the memmap and the pageblock
flags are freed. Ejecting DIMMs that were online in ZONE_MOVABLE while
other tasks write to /proc/sys/vm/compact_memory, no debug options set:

BUG: unable to handle page fault for address: ffffea0004000008
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 3fee9067 P4D 3fee9067 PUD 3fee8067 PMD 0
RIP: 0010:isolate_migratepages_block+0x32f/0x1280
Call Trace:
compact_zone+0x673/0x1140
compact_node+0xb6/0x130
sysctl_compaction_handler+0x77/0xd0

kcompactd and direct compaction walk into such ranges in the same way.

Only trust the flag for a pfn that the zone spans. Everything else goes
to __pageblock_pfn_to_page(), which finds the section offline or gone.
The span is written before the flag is set, so read them in the opposite
order.

Fixes: 7cf91a98e607 ("mm/compaction: speed up pageblock_pfn_to_page() when zone is contiguous")
Cc: <stable@xxxxxxxxxxxxxxx>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@xxxxxxxxxx>
---
mm/mm_init.c | 4 ++--
mm/page_alloc.h | 7 ++++++-
2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index df1f888e59b8..262e4e5e97be 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -2270,8 +2270,8 @@ void set_zone_contiguous(struct zone *zone)
cond_resched();
}

- /* We confirm that there is no hole */
- zone->contiguous = true;
+ /* We confirm that there is no hole, see pageblock_pfn_to_page() */
+ smp_store_release(&zone->contiguous, true);
}

/*
diff --git a/mm/page_alloc.h b/mm/page_alloc.h
index 2b1017a91928..e24d1203c514 100644
--- a/mm/page_alloc.h
+++ b/mm/page_alloc.h
@@ -216,7 +216,12 @@ extern struct page *__pageblock_pfn_to_page(unsigned long start_pfn,
static inline struct page *pageblock_pfn_to_page(unsigned long start_pfn,
unsigned long end_pfn, struct zone *zone)
{
- if (zone->contiguous)
+ /*
+ * A scanner may still be at a pfn that has left the zone: the zone is
+ * shrunk before set_zone_contiguous() says that it has no holes.
+ */
+ if (smp_load_acquire(&zone->contiguous) &&
+ zone_spans_pfn(zone, start_pfn))
return pfn_to_page(start_pfn);

return __pageblock_pfn_to_page(start_pfn, end_pfn, zone);

--
2.56.0