From: "Gregory Price (Meta)" <gourry@gourry.net>
To: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org, linux-cxl@vger.kernel.org,
kernel-team@meta.com, akpm@linux-foundation.org,
vbabka@kernel.org, surenb@google.com, mhocko@suse.com,
brendan.jackman@linux.dev, hannes@cmpxchg.org, ziy@nvidia.com,
linmiaohe@huawei.com, nao.horiguchi@gmail.com, david@kernel.org,
ljs@kernel.org, liam@infradead.org, rppt@kernel.org,
osalvador@suse.de, muchun.song@linux.dev, iamjoonsoo.kim@lge.com,
anshuman.khandual@arm.com, stable@vger.kernel.org
Subject: [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap
Date: Tue, 27 Oct 2026 00:21:59 -0400 [thread overview]
Message-ID: <20261027042204.3741307-2-gourry@gourry.net> (raw)
In-Reply-To: <20261027042204.3741307-1-gourry@gourry.net>
Compaction checks that a pageblock is online, then uses its pages across
lock drops and sleeps. If offline poisons the memmap meanwhile, a
DEBUG_VM kernel faults:
CPU 1 (compaction) CPU 2 (offline_pages)
pageblock_pfn_to_page()
block is online
isolate_migratepages_block()
cond_resched()
offline_mem_sections()
remove_pfn_range_from_zone()
page_init_poison()
set_pageblock_skip(valid_page)
reads the zone of a poisoned page
Oops: general protection fault, probably for non-canonical address 0xdffffc00000006a2
RIP: 0010:set_pfnblock_bit+0xa6/0x2b0
isolate_migratepages_block+0x2456/0x3c80
compact_zone+0x1638/0x3900
compact_node+0x12a/0x1f0
sysctl_compaction_handler+0xec/0x160
The free scanner faults the same way. A contiguous zone skips the
online check, since offline clears zone->contiguous only after
poisoning.
Hold an SRCU-fast read section (pfn_walk) from the block check to the
last use of its pages, and make remove_pfn_range_from_zone() clear
zone->contiguous and wait for it before poisoning. Offline pays one
grace period per range.
Scanner positions outlive the section, and offline can shrink the zone
meanwhile, so take the contiguous fast path only inside the current
span, publish zone->contiguous with release, and keep the migrate
scanner inside the zone.
With a kprobe parking the migrate scanner across the offline, base
faults in 3 of 3 runs and this patch in none. Unforced offline/online
cycling under compaction faults in 8 of 24 runs on base and 0 of 12 with
this patch.
Fixes: d33695b16a9f ("mm/memory_hotplug: poison memmap in remove_pfn_range_from_zone()")
Cc: stable@vger.kernel.org
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
mm/compaction.c | 8 ++++++++
mm/internal.h | 41 +++++++++++++++++++++++++++++++++++++++++
mm/memory_hotplug.c | 14 ++++++++++++--
mm/mm_init.c | 4 ++--
mm/page_alloc.h | 7 ++++++-
5 files changed, 69 insertions(+), 5 deletions(-)
diff --git a/mm/compaction.c b/mm/compaction.c
index f1b2060eb2016..2aeb18b6d54dd 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -360,6 +360,7 @@ static void __reset_isolation_suitable(struct zone *zone)
if (!zone->compact_blockskip_flush)
return;
+ guard(pfn_walk)();
zone->compact_blockskip_flush = false;
/*
@@ -716,6 +717,7 @@ isolate_freepages_range(struct compact_control *cc,
unsigned long isolated, pfn, block_start_pfn, block_end_pfn;
int order;
+ guard(pfn_walk)();
for (order = 0; order < NR_PAGE_ORDERS; order++)
INIT_LIST_HEAD(&cc->freepages[order]);
@@ -1340,6 +1342,7 @@ isolate_migratepages_range(struct compact_control *cc, unsigned long start_pfn,
unsigned long pfn, block_start_pfn, block_end_pfn;
int ret = 0;
+ guard(pfn_walk)();
/* Scan block by block. First and last block may be incomplete */
pfn = start_pfn;
block_start_pfn = pageblock_start_pfn(pfn);
@@ -1732,6 +1735,7 @@ static void isolate_freepages(struct compact_control *cc)
unsigned long low_pfn; /* lowest pfn scanner is able to scan */
unsigned int stride;
+ guard(pfn_walk)();
/* Try a small search of the free lists for a candidate */
fast_isolate_freepages(cc);
if (cc->nr_freepages)
@@ -2099,6 +2103,7 @@ static isolate_migrate_t isolate_migratepages(struct compact_control *cc)
(cc->mode != MIGRATE_SYNC ? ISOLATE_ASYNC_MIGRATE : 0);
bool fast_find_block;
+ guard(pfn_walk)();
/*
* Start at where we last stopped, or beginning of the zone as
* initialized by compact_zone(). The first failure will use
@@ -2116,6 +2121,9 @@ static isolate_migrate_t isolate_migratepages(struct compact_control *cc)
*/
fast_find_block = low_pfn != cc->migrate_pfn && !cc->fast_search_fail;
+ /* Offline may have shrunk the zone start past an old migrate_pfn */
+ low_pfn = max(low_pfn, block_start_pfn);
+
/* Only scan within a pageblock boundary */
block_end_pfn = pageblock_end_pfn(low_pfn);
diff --git a/mm/internal.h b/mm/internal.h
index d497c35ee7833..8a2faa74403b5 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -18,6 +18,7 @@
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/leafops.h>
+#include <linux/srcu.h>
#include <linux/tracepoint-defs.h>
/* Internal core VMA manipulation functions. */
@@ -1206,6 +1207,46 @@ struct tlbflush_unmap_batch;
*/
extern struct workqueue_struct *mm_percpu_wq;
+/*
+ * Compaction checks that a pageblock is online and then uses its pages,
+ * possibly across sleeps. remove_pfn_range_from_zone() waits for these
+ * readers before it poisons the memmap of a range taken offline.
+ */
+#ifdef CONFIG_MEMORY_HOTREMOVE
+extern struct srcu_struct pfn_walk_srcu;
+
+static inline struct srcu_ctr __percpu *pfn_walk_lock(void)
+{
+ return srcu_read_lock_fast(&pfn_walk_srcu);
+}
+
+static inline void pfn_walk_unlock(struct srcu_ctr __percpu *scp)
+{
+ srcu_read_unlock_fast(&pfn_walk_srcu, scp);
+}
+
+static inline void pfn_walk_synchronize(void)
+{
+ synchronize_srcu(&pfn_walk_srcu);
+}
+#else
+static inline struct srcu_ctr __percpu *pfn_walk_lock(void)
+{
+ return NULL;
+}
+
+static inline void pfn_walk_unlock(struct srcu_ctr __percpu *scp)
+{
+}
+
+static inline void pfn_walk_synchronize(void)
+{
+}
+#endif
+
+DEFINE_LOCK_GUARD_0(pfn_walk, _T->scp = pfn_walk_lock(),
+ pfn_walk_unlock(_T->scp), struct srcu_ctr __percpu *scp)
+
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
void try_to_unmap_flush(void);
void try_to_unmap_flush_dirty(void);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 796af1028ee23..b5d9e367b893d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -543,6 +543,16 @@ void remove_pfn_range_from_zone(struct zone *zone,
struct pglist_data *pgdat = zone->zone_pgdat;
unsigned long pfn, cur_nr_pages;
+ /*
+ * The range is offline, but compaction may still use pages it found
+ * online. Make new scans check the online state instead of trusting
+ * zone->contiguous, and wait for the old ones before poisoning.
+ */
+ if (!zone_is_zone_device(zone)) {
+ clear_zone_contiguous(zone);
+ pfn_walk_synchronize();
+ }
+
/* Poison struct pages because they are now uninitialized again. */
for (pfn = start_pfn; pfn < end_pfn; pfn += cur_nr_pages) {
cond_resched();
@@ -563,8 +573,6 @@ void remove_pfn_range_from_zone(struct zone *zone,
if (zone_is_zone_device(zone))
return;
- clear_zone_contiguous(zone);
-
shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
update_pgdat_span(pgdat);
@@ -1783,6 +1791,8 @@ bool mhp_range_allowed(u64 start, u64 size, bool need_mapping)
#ifdef CONFIG_MEMORY_HOTREMOVE
+DEFINE_SRCU_FAST(pfn_walk_srcu);
+
static int check_no_memblock_for_node_cb(struct memory_block *mem, void *arg)
{
int nid = *(int *)arg;
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 01ced0553ce9b..db610ddc37398 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; pairs with pageblock_pfn_to_page() */
+ smp_store_release(&zone->contiguous, true);
}
/*
diff --git a/mm/page_alloc.h b/mm/page_alloc.h
index 53df6b8a0c00a..7e185326b80ab 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)
+ /*
+ * Offline can shrink the zone under a caller's old pfns and then set
+ * zone->contiguous again, so only trust it inside the current span.
+ */
+ if (smp_load_acquire(&zone->contiguous) &&
+ zone_spans_pfn(zone, start_pfn) && zone_spans_pfn(zone, end_pfn - 1))
return pfn_to_page(start_pfn);
return __pageblock_pfn_to_page(start_pfn, end_pfn, zone);
--
2.53.0-Meta
next prev parent reply other threads:[~2026-10-27 4:22 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 4:21 [PATCH 0/6] mm: fix pfn walker races with memory offline Gregory Price (Meta)
2026-10-27 4:21 ` Gregory Price (Meta) [this message]
2026-10-27 13:38 ` [PATCH 1/6] mm/compaction: wait for pfn walkers before poisoning offlined memmap David Hildenbrand (Arm)
2026-10-28 4:22 ` sashiko-bot
2026-10-27 4:22 ` [PATCH 2/6] mm/page_owner: hold off memory offline while a page is read Gregory Price (Meta)
2026-10-28 4:22 ` sashiko-bot
2026-10-27 4:22 ` [PATCH 3/6] mm/memory-failure: keep the page online while handling it Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 4/6] mm/page_alloc: don't undo another isolation in alloc_contig_range Gregory Price (Meta)
2026-10-27 13:37 ` David Hildenbrand (Arm)
2026-10-27 14:06 ` Gregory Price (Meta)
2026-10-27 14:13 ` David Hildenbrand (Arm)
2026-10-27 18:53 ` Zi Yan
2026-10-27 18:58 ` David Hildenbrand (Arm)
2026-10-27 19:38 ` Gregory Price
2026-10-27 4:22 ` [PATCH 5/6] mm/page_alloc: keep the range online while alloc_contig isolates it Gregory Price (Meta)
2026-10-27 4:22 ` [PATCH 6/6] mm/hwpoison-inject: keep the page online while filtering it Gregory Price (Meta)
2026-10-27 8:57 ` [PATCH 0/6] mm: fix pfn walker races with memory offline David Hildenbrand (Arm)
2026-10-27 11:31 ` Gregory Price (Meta)
2026-10-27 12:51 ` David Hildenbrand (Arm)
2026-10-27 13:49 ` Gregory Price (Meta)
2026-10-27 14:25 ` David Hildenbrand (Arm)
2026-10-27 15:49 ` Gregory Price (Meta)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027042204.3741307-2-gourry@gourry.net \
--to=gourry@gourry.net \
--cc=akpm@linux-foundation.org \
--cc=anshuman.khandual@arm.com \
--cc=brendan.jackman@linux.dev \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=iamjoonsoo.kim@lge.com \
--cc=kernel-team@meta.com \
--cc=liam@infradead.org \
--cc=linmiaohe@huawei.com \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=nao.horiguchi@gmail.com \
--cc=osalvador@suse.de \
--cc=rppt@kernel.org \
--cc=stable@vger.kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox