* [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range()
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 15:48 ` Zi Yan
2026-10-27 18:31 ` Gregory Price (Meta)
2026-10-27 15:46 ` [PATCH 2/9] mm/page_alloc: clear ->private of deferred free pages Vlastimil Babka (SUSE)
` (7 subsequent siblings)
8 siblings, 2 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
start_isolate_page_range() fails with -EBUSY if one of the pageblocks is
already isolated, i.e. if somebody else is working on it. It restores
the pageblocks which it has isolated itself before it returns, as its
kernel-doc says:
We then clean up by restoring the migration type on pageblocks we may
have modified and return -EBUSY to caller. This prevents two threads
from simultaneously working on overlapping ranges.
alloc_contig_frozen_range() nevertheless calls undo_isolate_page_range()
for the whole range in this case, which clears every isolated pageblock
it finds. The only ones left at this point belong to the other user of
the range:
CPU0 offline_pages() CPU1 alloc_contig_frozen_range()
start_isolate_page_range(A)
pageblock X: isolated
migrate ...
start_isolate_page_range(B)
X is isolated: -EBUSY
(own pageblocks restored)
undo_isolate_page_range(B)
X: not isolated any more
test_pages_isolated(A): -EBUSY
(again and again)
offline_pages() repeats the migration and test_pages_isolated() until the
latter succeeds, which is now impossible and the task spins until it gets
a signal, while the free pages of X are handed out by the allocator again.
If X is released after the last test_pages_isolated(),
__offline_isolated_pages() takes pages which are accounted as free off
the free lists as MIGRATE_ISOLATE. Thus NR_FREE_PAGES stays too high, or
hits BUG_ON(!PageBuddy(page)) if one of them has been allocated.
For another alloc_contig_frozen_range() or cma_alloc() the result is a
spurious -EBUSY.
Return the error, like the function did until it was reworked.
Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_alloc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 36079e092875..ee013db45ed1 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7329,7 +7329,7 @@ int alloc_contig_frozen_range_noprof(unsigned long start, unsigned long end,
ret = start_isolate_page_range(start, end, mode);
if (ret)
- goto done;
+ return ret;
drain_all_pages(cc.zone);
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range()
2026-10-27 15:46 ` [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range() Vlastimil Babka (SUSE)
@ 2026-10-27 15:48 ` Zi Yan
2026-10-27 18:31 ` Gregory Price (Meta)
1 sibling, 0 replies; 16+ messages in thread
From: Zi Yan @ 2026-10-27 15:48 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Shakeel Butt, Mike Rapoport, David Hildenbrand,
Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik, stable
On 27 Oct 2026, at 11:46, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> start_isolate_page_range() fails with -EBUSY if one of the pageblocks is
> already isolated, i.e. if somebody else is working on it. It restores
> the pageblocks which it has isolated itself before it returns, as its
> kernel-doc says:
>
> We then clean up by restoring the migration type on pageblocks we may
> have modified and return -EBUSY to caller. This prevents two threads
> from simultaneously working on overlapping ranges.
>
> alloc_contig_frozen_range() nevertheless calls undo_isolate_page_range()
> for the whole range in this case, which clears every isolated pageblock
> it finds. The only ones left at this point belong to the other user of
> the range:
>
> CPU0 offline_pages() CPU1 alloc_contig_frozen_range()
> start_isolate_page_range(A)
> pageblock X: isolated
> migrate ...
> start_isolate_page_range(B)
> X is isolated: -EBUSY
> (own pageblocks restored)
> undo_isolate_page_range(B)
> X: not isolated any more
> test_pages_isolated(A): -EBUSY
> (again and again)
>
> offline_pages() repeats the migration and test_pages_isolated() until the
> latter succeeds, which is now impossible and the task spins until it gets
> a signal, while the free pages of X are handed out by the allocator again.
>
> If X is released after the last test_pages_isolated(),
> __offline_isolated_pages() takes pages which are accounted as free off
> the free lists as MIGRATE_ISOLATE. Thus NR_FREE_PAGES stays too high, or
> hits BUG_ON(!PageBuddy(page)) if one of them has been allocated.
>
> For another alloc_contig_frozen_range() or cma_alloc() the result is a
> spurious -EBUSY.
>
> Return the error, like the function did until it was reworked.
>
> Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
> Cc: <stable@vger.kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/page_alloc.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range()
2026-10-27 15:46 ` [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range() Vlastimil Babka (SUSE)
2026-10-27 15:48 ` Zi Yan
@ 2026-10-27 18:31 ` Gregory Price (Meta)
1 sibling, 0 replies; 16+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 18:31 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik, stable
On 2026-10-27 16:46 +0100, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> start_isolate_page_range() fails with -EBUSY if one of the pageblocks is
> already isolated, i.e. if somebody else is working on it. It restores
> the pageblocks which it has isolated itself before it returns, as its
> kernel-doc says:
>
> We then clean up by restoring the migration type on pageblocks we may
> have modified and return -EBUSY to caller. This prevents two threads
> from simultaneously working on overlapping ranges.
>
> alloc_contig_frozen_range() nevertheless calls undo_isolate_page_range()
> for the whole range in this case, which clears every isolated pageblock
> it finds. The only ones left at this point belong to the other user of
> the range:
>
> CPU0 offline_pages() CPU1 alloc_contig_frozen_range()
> start_isolate_page_range(A)
> pageblock X: isolated
> migrate ...
> start_isolate_page_range(B)
> X is isolated: -EBUSY
> (own pageblocks restored)
> undo_isolate_page_range(B)
> X: not isolated any more
> test_pages_isolated(A): -EBUSY
> (again and again)
>
> offline_pages() repeats the migration and test_pages_isolated() until the
> latter succeeds, which is now impossible and the task spins until it gets
> a signal, while the free pages of X are handed out by the allocator again.
>
> If X is released after the last test_pages_isolated(),
> __offline_isolated_pages() takes pages which are accounted as free off
> the free lists as MIGRATE_ISOLATE. Thus NR_FREE_PAGES stays too high, or
> hits BUG_ON(!PageBuddy(page)) if one of them has been allocated.
>
> For another alloc_contig_frozen_range() or cma_alloc() the result is a
> spurious -EBUSY.
>
> Return the error, like the function did until it was reworked.
>
> Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
> Cc: <stable@vger.kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Given i separately found this exact patch and tested it
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Tested-by: Gregory Price (Meta) <gourry@gourry.net>
--
~Gregory
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 2/9] mm/page_alloc: clear ->private of deferred free pages
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range() Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 18:38 ` Zi Yan
2026-10-27 15:46 ` [PATCH 3/9] mm: page_frag: refill the cache for large fragments Vlastimil Babka (SUSE)
` (6 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
free_pages_nolock() and free_frozen_pages_nolock() put the page on
zone->trylock_free_pages if they get neither the pcp lock nor zone->lock.
add_page_to_zone_llist() keeps the order of the page in page->private.
The next free_one_page() which does get zone->lock passes these pages to
split_large_buddy() with ->private still set.
__free_one_page() writes ->private of the head of the resulting free page
only. If the deferred page is merged with its lower buddy, it becomes a
tail of that free page and keeps the order in ->private.
All other free pages which are not the head of a buddy have a ->private
of zero, and since commit 819bfbd47e49 ("mm/page_alloc: remove
set_page_private() in prep_compound_tail()") the allocator relies on it.
When the stale page is allocated again as a tail of a larger page:
- prep_compound_tail() hits VM_WARN_ON_ONCE(tail->private);
- with the page checks enabled, __free_pages_prepare() reports
"BUG: Bad page state", "nonzero private", and leaks the whole page;
- without them the value is seen by whoever uses that page next, e.g. as
folio->private of a folio which is split off a large folio.
Only pages of an order above zero are affected. Those can be freed
through _nolock() by the stack depot (order 2) and by SLUB (a new slab
which cannot be added to the partial list by kmalloc_nolock()).
Clear ->private when the page is taken off the list.
Fixes: 8c57b687e833 ("mm, bpf: Introduce free_pages_nolock()")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_alloc.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index ee013db45ed1..9a4d3352c037 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -1562,6 +1562,8 @@ static void free_one_page(struct zone *zone, struct page *page,
llist_for_each_entry_safe(p, tmp, llnode, pcp_llist) {
unsigned int p_order = p->private;
+ /* It may end up as a tail of the merged free page. */
+ p->private = 0;
split_large_buddy(zone, p, page_to_pfn(p), p_order, fpi_flags);
__count_vm_events(PGFREE, 1 << p_order);
}
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 2/9] mm/page_alloc: clear ->private of deferred free pages
2026-10-27 15:46 ` [PATCH 2/9] mm/page_alloc: clear ->private of deferred free pages Vlastimil Babka (SUSE)
@ 2026-10-27 18:38 ` Zi Yan
0 siblings, 0 replies; 16+ messages in thread
From: Zi Yan @ 2026-10-27 18:38 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Shakeel Butt, Mike Rapoport, David Hildenbrand,
Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik, stable
On 27 Oct 2026, at 11:46, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> free_pages_nolock() and free_frozen_pages_nolock() put the page on
> zone->trylock_free_pages if they get neither the pcp lock nor zone->lock.
> add_page_to_zone_llist() keeps the order of the page in page->private.
> The next free_one_page() which does get zone->lock passes these pages to
> split_large_buddy() with ->private still set.
>
> __free_one_page() writes ->private of the head of the resulting free page
> only. If the deferred page is merged with its lower buddy, it becomes a
> tail of that free page and keeps the order in ->private.
>
> All other free pages which are not the head of a buddy have a ->private
> of zero, and since commit 819bfbd47e49 ("mm/page_alloc: remove
> set_page_private() in prep_compound_tail()") the allocator relies on it.
> When the stale page is allocated again as a tail of a larger page:
>
> - prep_compound_tail() hits VM_WARN_ON_ONCE(tail->private);
> - with the page checks enabled, __free_pages_prepare() reports
> "BUG: Bad page state", "nonzero private", and leaks the whole page;
> - without them the value is seen by whoever uses that page next, e.g. as
> folio->private of a folio which is split off a large folio.
>
> Only pages of an order above zero are affected. Those can be freed
> through _nolock() by the stack depot (order 2) and by SLUB (a new slab
> which cannot be added to the partial list by kmalloc_nolock()).
>
> Clear ->private when the page is taken off the list.
>
> Fixes: 8c57b687e833 ("mm, bpf: Introduce free_pages_nolock()")
> Cc: <stable@vger.kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/page_alloc.c | 2 ++
> 1 file changed, 2 insertions(+)
>
Makes sense.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 3/9] mm: page_frag: refill the cache for large fragments
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 1/9] mm/page_alloc: don't undo a racing start_isolate_page_range() Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 2/9] mm/page_alloc: clear ->private of deferred free pages Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 4/9] mm/page_alloc: keep the pagesets of an empty zone Vlastimil Babka (SUSE)
` (5 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
A fragment which is larger than PAGE_SIZE can only be served from the
32K page of a page_frag_cache. If memory is low and the cache had to fall
back to a single page, __page_frag_alloc_align() returns NULL and keeps
that page.
Since the offset counts up, this check comes first and is made whenever
the fragment does not fit behind nc->offset, no matter how big the page
of the cache is. So the cache is never refilled on behalf of a large
fragment: with fragsz = 10000 the first three allocations succeed, the
fourth one and all the following ones fail although memory is plentiful.
nc->offset does not move either, so this lasts until smaller fragments
have used up the rest of the page, which never happens if the cache has
no other user.
The nfp driver allocates its receive buffers with napi_alloc_frag() and
netdev_alloc_frag(). They are larger than a page with a jumbo MTU.
Only give up if the fragment does not fit into the page at all, which is
what the comment describes. The fourth allocation in the scenario above
with a 32k page will thus now proceed to refill the cache.
Fixes: 8218f62c9c9b ("mm: page_frag: use initial zero offset for page_frag_alloc_align()")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
[vbabka@kernel.org: make the comment more obvious]
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_frag_cache.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/mm/page_frag_cache.c b/mm/page_frag_cache.c
index e63efe78b7d4..8914437ddc51 100644
--- a/mm/page_frag_cache.c
+++ b/mm/page_frag_cache.c
@@ -119,15 +119,20 @@ void *__page_frag_alloc_align(struct page_frag_cache *nc,
size = PAGE_SIZE << encoded_page_decode_order(encoded_page);
offset = __ALIGN_KERNEL_MASK(nc->offset, ~align_mask);
if (unlikely(offset + fragsz > size)) {
- if (unlikely(fragsz > PAGE_SIZE)) {
+ if (unlikely(fragsz > size)) {
/*
- * The caller is trying to allocate a fragment
- * with fragsz > PAGE_SIZE but the cache isn't big
- * enough to satisfy the request, this may
- * happen in low memory conditions.
- * We don't release the cache page because
- * it could make memory pressure worse
- * so we simply return NULL here.
+ * The caller is trying to allocate a fragment that's
+ * larger than the whole cache page.
+ *
+ * Either fragsz is larger than PAGE_FRAG_CACHE_MAX_SIZE
+ * so this cannot ever succeed.
+ *
+ * Or the refill has fallen back to order-0 due to a low
+ * memory condition. Then we don't want to release the
+ * cache page to attempt a new large-size refill because
+ * it could make memory pressure worse.
+ *
+ * So in both cases we simply return NULL here.
*/
return NULL;
}
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH 4/9] mm/page_alloc: keep the pagesets of an empty zone
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (2 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 3/9] mm: page_frag: refill the cache for large fragments Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone Vlastimil Babka (SUSE)
` (4 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
When the last memory of a zone goes offline, zone_pcp_reset() frees
zone->per_cpu_pageset and zone->per_cpu_zonestats. Nothing waits for those
who found the zone populated and still use the two pointers. They take
neither the memory hotplug lock nor any other lock which offline_pages()
holds, and some of them can sleep:
CPU0 vmstat_update() CPU1 offline_pages()
refresh_cpu_vm_stats()
for_each_populated_zone(zone)
pcp = zone->per_cpu_pageset
cond_resched()
adjust_present_page_count()
zone_pcp_reset(zone)
free_percpu(zone->per_cpu_pageset)
decay_pcp_high(zone, pcp)
__this_cpu_write(pcp->expire, 0)
The same goes for drain_all_pages(NULL), which takes pcp->lock, for
zone_pcp_update(), show_free_areas() and the readers and writers of the
zonestats. If the per-cpu memory has got a new owner in the meantime, it
is corrupted.
The pagesets are only freed because setup_zone_pageset() would otherwise
leak them when the zone gets memory again. Keep them instead and let
setup_zone_pageset() and zone_pcp_init() use them again, like it is done
with pgdat->per_cpu_nodestats. Their lists are empty, and their limits
have been calculated for a zone without memory by the
init_per_zone_wmark_min() of offline_pages(), which is what
setup_zone_pageset() would come up with as well.
Fixes: 340175b7d14d ("mm/hotplug: free zone->pageset when a zone becomes empty")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_alloc.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 9a4d3352c037..13211b8caada 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -6312,6 +6312,10 @@ void __meminit setup_zone_pageset(struct zone *zone)
{
int cpu;
+ /* The zone has been populated before, see zone_pcp_reset(). */
+ if (zone->per_cpu_pageset != &boot_pageset)
+ return;
+
/* Size may be 0 on !SMP && !NUMA */
if (sizeof(struct per_cpu_zonestat) > 0)
zone->per_cpu_zonestats = alloc_percpu(struct per_cpu_zonestat);
@@ -6404,6 +6408,10 @@ void __init setup_per_cpu_pageset(void)
__meminit void zone_pcp_init(struct zone *zone)
{
+ /* A node which is added again, see zone_pcp_reset(). */
+ if (zone->per_cpu_pageset && zone->per_cpu_pageset != &boot_pageset)
+ return;
+
/*
* per cpu subsystem is not up at this point. The following code
* relies on the ability of the linker to provide the
@@ -7684,6 +7692,12 @@ void zone_pcp_enable(struct zone *zone)
mutex_unlock(&pcp_batch_high_lock);
}
+/*
+ * The zone has become empty. Its pagesets and zonestats are not freed: nothing
+ * keeps somebody who found the zone populated, such as refresh_cpu_vm_stats()
+ * or drain_all_pages(), from still using them. They are used again when the
+ * zone gets memory again.
+ */
void zone_pcp_reset(struct zone *zone)
{
int cpu;
@@ -7694,12 +7708,6 @@ void zone_pcp_reset(struct zone *zone)
pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu);
drain_zonestat(zone, pzstats);
}
- free_percpu(zone->per_cpu_pageset);
- zone->per_cpu_pageset = &boot_pageset;
- if (zone->per_cpu_zonestats != &boot_zonestats) {
- free_percpu(zone->per_cpu_zonestats);
- zone->per_cpu_zonestats = &boot_zonestats;
- }
}
}
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (3 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 4/9] mm/page_alloc: keep the pagesets of an empty zone Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 18:35 ` Gregory Price (Meta)
2026-10-27 18:39 ` Zi Yan
2026-10-27 15:46 ` [PATCH 6/9] mm/compaction: trust zone->contiguous only in the span Vlastimil Babka (SUSE)
` (3 subsequent siblings)
8 siblings, 2 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
should_reclaim_retry() walks a zonelist and passes the zoneref pointer
to the reclaim_retry_zone tracepoint, which loads the zone from the
entry again to find the node.
Memory hotplug rebuilds the zonelists in place while allocations are
under way. When a zone in front of the zoneref entry has become empty,
the entries move up, and this one can be the end marker by the time the
tracepoint looks at it. zonelist_node_idx() then dereferences a NULL
zone. Short of that, the event can name another zone than the one its
numbers belong to.
Pass the zone pointer instead of the zoneref pointer.
Fixes: d379f01de095 ("oom, trace: add oom detection tracepoints")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
include/trace/events/oom.h | 8 ++++----
mm/page_alloc.c | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/include/trace/events/oom.h b/include/trace/events/oom.h
index 9f0a5d1482c4..cbcf6fe4cc61 100644
--- a/include/trace/events/oom.h
+++ b/include/trace/events/oom.h
@@ -33,7 +33,7 @@ TRACE_EVENT(oom_score_adj_update,
TRACE_EVENT(reclaim_retry_zone,
- TP_PROTO(struct zoneref *zoneref,
+ TP_PROTO(struct zone *zone,
int order,
unsigned long reclaimable,
unsigned long available,
@@ -41,7 +41,7 @@ TRACE_EVENT(reclaim_retry_zone,
int no_progress_loops,
bool wmark_check),
- TP_ARGS(zoneref, order, reclaimable, available, min_wmark, no_progress_loops, wmark_check),
+ TP_ARGS(zone, order, reclaimable, available, min_wmark, no_progress_loops, wmark_check),
TP_STRUCT__entry(
__field( int, node)
@@ -55,8 +55,8 @@ TRACE_EVENT(reclaim_retry_zone,
),
TP_fast_assign(
- __entry->node = zonelist_node_idx(zoneref);
- __entry->zone_idx = zonelist_zone_idx(zoneref);
+ __entry->node = zone_to_nid(zone);
+ __entry->zone_idx = zone_idx(zone);
__entry->order = order;
__entry->reclaimable = reclaimable;
__entry->available = available;
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 13211b8caada..8f8885165045 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -4675,7 +4675,7 @@ should_reclaim_retry(gfp_t gfp_mask, unsigned order,
*/
wmark = __zone_watermark_ok(zone, order, min_wmark,
ac->highest_zoneidx, alloc_flags, available);
- trace_reclaim_retry_zone(z, order, reclaimable,
+ trace_reclaim_retry_zone(zone, order, reclaimable,
available, min_wmark, *no_progress_loops, wmark);
if (wmark) {
ret = true;
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone
2026-10-27 15:46 ` [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone Vlastimil Babka (SUSE)
@ 2026-10-27 18:35 ` Gregory Price (Meta)
2026-10-27 18:39 ` Zi Yan
1 sibling, 0 replies; 16+ messages in thread
From: Gregory Price (Meta) @ 2026-10-27 18:35 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik, stable
On 2026-10-27 16:46 +0100, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> should_reclaim_retry() walks a zonelist and passes the zoneref pointer
> to the reclaim_retry_zone tracepoint, which loads the zone from the
> entry again to find the node.
>
> Memory hotplug rebuilds the zonelists in place while allocations are
> under way. When a zone in front of the zoneref entry has become empty,
> the entries move up, and this one can be the end marker by the time the
> tracepoint looks at it. zonelist_node_idx() then dereferences a NULL
> zone. Short of that, the event can name another zone than the one its
> numbers belong to.
>
> Pass the zone pointer instead of the zoneref pointer.
>
> Fixes: d379f01de095 ("oom, trace: add oom detection tracepoints")
> Cc: <stable@vger.kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Similarly found separately and tested the fix.
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Tested-by: Gregory Price (Meta) <gourry@gourry.net>
--
~Gregory
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone
2026-10-27 15:46 ` [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone Vlastimil Babka (SUSE)
2026-10-27 18:35 ` Gregory Price (Meta)
@ 2026-10-27 18:39 ` Zi Yan
1 sibling, 0 replies; 16+ messages in thread
From: Zi Yan @ 2026-10-27 18:39 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Shakeel Butt, Mike Rapoport, David Hildenbrand,
Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik, stable
On 27 Oct 2026, at 11:46, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> should_reclaim_retry() walks a zonelist and passes the zoneref pointer
> to the reclaim_retry_zone tracepoint, which loads the zone from the
> entry again to find the node.
>
> Memory hotplug rebuilds the zonelists in place while allocations are
> under way. When a zone in front of the zoneref entry has become empty,
> the entries move up, and this one can be the end marker by the time the
> tracepoint looks at it. zonelist_node_idx() then dereferences a NULL
> zone. Short of that, the event can name another zone than the one its
> numbers belong to.
>
> Pass the zone pointer instead of the zoneref pointer.
>
> Fixes: d379f01de095 ("oom, trace: add oom detection tracepoints")
> Cc: <stable@vger.kernel.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> include/trace/events/oom.h | 8 ++++----
> mm/page_alloc.c | 2 +-
> 2 files changed, 5 insertions(+), 5 deletions(-)
>
Makes sense to me.
Acked-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 6/9] mm/compaction: trust zone->contiguous only in the span
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (4 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 5/9] mm, page_alloc: pass zone pointer to trace_reclaim_retry_zone Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 7/9] mm: page_isolation: do not take slab pages for free Vlastimil Babka (SUSE)
` (2 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
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@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
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
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH 7/9] mm: page_isolation: do not take slab pages for free
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (5 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 6/9] mm/compaction: trust zone->contiguous only in the span Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 21:04 ` Zi Yan
2026-10-27 15:46 ` [PATCH 8/9] mm/memory_hotplug: resize zones under their seqlock Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 9/9] mm/page_alloc: check pageblock pfns under span seqlock Vlastimil Babka (SUSE)
8 siblings, 1 reply; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik
From: Josef Bacik <josef@toxicpanda.com>
page_is_unmovable() regards a page with a reference count of zero as free
or about to be freed. Since slab allocates frozen pages, they will
be wrongly considered to be freed if order 0 (higher-order compound
pages are caught earlier).
If such slabs are the only unmovable pages of a memory block, memory
offlining now isolates the block, this can lead to offline_pages()
looping without progress.
Thus detect the slab pages as removable to return -EBUSY, as would
happen before commit 9aec2fb0fd5e ("slab: allocate frozen pages").
Fixes: 9aec2fb0fd5e ("slab: allocate frozen pages")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/memory_hotplug.c | 7 +++++++
mm/page_isolation.c | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 796af1028ee2..e773a7d54165 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1847,6 +1847,13 @@ static int scan_movable_pages(unsigned long start, unsigned long end,
if (PageOffline(page) && page_count(page))
return -EBUSY;
+ /*
+ * A page that was allocated, but was not a slab page yet, when
+ * its pageblock was isolated.
+ */
+ if (PageSlab(page))
+ return -EBUSY;
+
folio = page_folio(page);
if (!folio_test_hugetlb(folio))
continue;
diff --git a/mm/page_isolation.c b/mm/page_isolation.c
index 5aaad037384e..f4f0571607ab 100644
--- a/mm/page_isolation.c
+++ b/mm/page_isolation.c
@@ -74,6 +74,13 @@ bool page_is_unmovable(struct zone *zone, struct page *page,
return false;
}
+ /*
+ * Slab pages are frozen: do not take their reference count of zero
+ * for that of a free page.
+ */
+ if (PageSlab(page))
+ return true;
+
/*
* We can't use page_count without pin a page
* because another CPU can free compound page.
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 7/9] mm: page_isolation: do not take slab pages for free
2026-10-27 15:46 ` [PATCH 7/9] mm: page_isolation: do not take slab pages for free Vlastimil Babka (SUSE)
@ 2026-10-27 21:04 ` Zi Yan
0 siblings, 0 replies; 16+ messages in thread
From: Zi Yan @ 2026-10-27 21:04 UTC (permalink / raw)
To: Vlastimil Babka (SUSE)
Cc: Andrew Morton, Shakeel Butt, Mike Rapoport, David Hildenbrand,
Oscar Salvador, Matthew Wilcox (Oracle),
Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Josef Bacik
On 27 Oct 2026, at 11:46, Vlastimil Babka (SUSE) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> page_is_unmovable() regards a page with a reference count of zero as free
> or about to be freed. Since slab allocates frozen pages, they will
> be wrongly considered to be freed if order 0 (higher-order compound
> pages are caught earlier).
>
> If such slabs are the only unmovable pages of a memory block, memory
> offlining now isolates the block, this can lead to offline_pages()
> looping without progress.
>
> Thus detect the slab pages as removable to return -EBUSY, as would
> happen before commit 9aec2fb0fd5e ("slab: allocate frozen pages").
>
> Fixes: 9aec2fb0fd5e ("slab: allocate frozen pages")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/memory_hotplug.c | 7 +++++++
> mm/page_isolation.c | 7 +++++++
> 2 files changed, 14 insertions(+)
>
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 8/9] mm/memory_hotplug: resize zones under their seqlock
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (6 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 7/9] mm: page_isolation: do not take slab pages for free Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
2026-10-27 15:46 ` [PATCH 9/9] mm/page_alloc: check pageblock pfns under span seqlock Vlastimil Babka (SUSE)
8 siblings, 0 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
With CONFIG_DEBUG_VM the page allocator checks that a page which is
freed, split or allocated lies within its zone. The check reads
zone->zone_start_pfn and zone->spanned_pages under zone->span_seqlock, as
memory hotplug can change the two at any time.
Commit 27cacaad16c5 ("mm,memory_hotplug: drop unneeded locking") removed
the write side, since all resizing is serialized by the hotplug lock. That
leaves the reader without protection. When memory below the start of a
zone goes online, the start is lowered first and the size is raised
afterwards. When the lowest memory of a zone goes offline, the size is
reduced first. In between, the zone seems to end before its last pages. If
one of them goes through the allocator at that moment, it is reported as
being outside of the zone and VM_BUG_ON_PAGE() kills the kernel.
Write the two fields in a write section of the seqlock. Interrupts are
disabled as pages are freed from interrupts, too, and the reader would
wait for the writer which it has interrupted.
Fixes: 27cacaad16c5 ("mm,memory_hotplug: drop unneeded locking")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/memory_hotplug.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index e773a7d54165..66f181d98ebd 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -467,6 +467,22 @@ static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
return 0;
}
+/*
+ * The page allocator checks pages against the span of their zone while the
+ * zone is resized, see page_outside_zone_boundaries(). It must not see the
+ * new value of one field together with the old value of the other one.
+ */
+static void zone_set_span(struct zone *zone, unsigned long start_pfn,
+ unsigned long spanned_pages)
+{
+ unsigned long flags;
+
+ write_seqlock_irqsave(&zone->span_seqlock, flags);
+ zone->zone_start_pfn = start_pfn;
+ zone->spanned_pages = spanned_pages;
+ write_sequnlock_irqrestore(&zone->span_seqlock, flags);
+}
+
static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
unsigned long end_pfn)
{
@@ -482,13 +498,10 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
*/
pfn = find_smallest_section_pfn(nid, zone, end_pfn,
zone_end_pfn(zone));
- if (pfn) {
- zone->spanned_pages = zone_end_pfn(zone) - pfn;
- zone->zone_start_pfn = pfn;
- } else {
- zone->zone_start_pfn = 0;
- zone->spanned_pages = 0;
- }
+ if (pfn)
+ zone_set_span(zone, pfn, zone_end_pfn(zone) - pfn);
+ else
+ zone_set_span(zone, 0, 0);
} else if (zone_end_pfn(zone) == end_pfn) {
/*
* If the section is biggest section in the zone, it need
@@ -499,11 +512,10 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
pfn = find_biggest_section_pfn(nid, zone, zone->zone_start_pfn,
start_pfn);
if (pfn)
- zone->spanned_pages = pfn - zone->zone_start_pfn + 1;
- else {
- zone->zone_start_pfn = 0;
- zone->spanned_pages = 0;
- }
+ zone_set_span(zone, zone->zone_start_pfn,
+ pfn - zone->zone_start_pfn + 1);
+ else
+ zone_set_span(zone, 0, 0);
}
}
@@ -696,12 +708,12 @@ static void online_pages_range(unsigned long start_pfn, unsigned long nr_pages)
static void __meminit resize_zone_range(struct zone *zone, unsigned long start_pfn,
unsigned long nr_pages)
{
- unsigned long old_end_pfn = zone_end_pfn(zone);
+ unsigned long end_pfn = max(start_pfn + nr_pages, zone_end_pfn(zone));
- if (zone_is_empty(zone) || start_pfn < zone->zone_start_pfn)
- zone->zone_start_pfn = start_pfn;
+ if (!zone_is_empty(zone) && start_pfn > zone->zone_start_pfn)
+ start_pfn = zone->zone_start_pfn;
- zone->spanned_pages = max(start_pfn + nr_pages, old_end_pfn) - zone->zone_start_pfn;
+ zone_set_span(zone, start_pfn, end_pfn - start_pfn);
}
static void __meminit resize_pgdat_range(struct pglist_data *pgdat, unsigned long start_pfn,
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH 9/9] mm/page_alloc: check pageblock pfns under span seqlock
2026-10-27 15:46 [PATCH 0/9] mm, page_alloc: several stable fixes Vlastimil Babka (SUSE)
` (7 preceding siblings ...)
2026-10-27 15:46 ` [PATCH 8/9] mm/memory_hotplug: resize zones under their seqlock Vlastimil Babka (SUSE)
@ 2026-10-27 15:46 ` Vlastimil Babka (SUSE)
8 siblings, 0 replies; 16+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-10-27 15:46 UTC (permalink / raw)
To: Andrew Morton, Zi Yan, Shakeel Butt, Mike Rapoport,
David Hildenbrand, Oscar Salvador, Matthew Wilcox (Oracle)
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel, Vlastimil Babka (SUSE),
Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
Whenever the flags of a pageblock are read or written, a kernel with
CONFIG_DEBUG_VM makes sure that the pfn lies within the zone of the page.
Unlike bad_range(), that check reads zone->zone_start_pfn and
zone->spanned_pages without zone->span_seqlock.
Memory hotplug changes the two fields one after the other. If the lower
end of a zone moves, the zone seems to end before its last pages in
between. A page of that part which is freed at that moment, on another
CPU, ends in
page dumped because: VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn))
kernel BUG at mm/page_alloc.c:372!
free_unref_folios+0x55e/0xed0
folios_put_refs+0x2b5/0x530
Read the span in a read section of the seqlock.
Fixes: 86051ca5eaf5 ("mm: fix usemap initialization")
Cc: <stable@vger.kernel.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_alloc.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 8f8885165045..f7bb0d59efc8 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -357,6 +357,21 @@ static __always_inline bool is_standalone_pb_bit(enum pageblock_bits pb_bit)
return pb_bit >= PB_compact_skip && pb_bit < __NR_PAGEBLOCK_BITS;
}
+/* Memory hotplug can resize the zone while we look at its span */
+static bool __maybe_unused zone_spans_pfn_stable(struct zone *zone,
+ unsigned long pfn)
+{
+ unsigned int seq;
+ bool ret;
+
+ do {
+ seq = zone_span_seqbegin(zone);
+ ret = zone_spans_pfn(zone, pfn);
+ } while (zone_span_seqretry(zone, seq));
+
+ return ret;
+}
+
static __always_inline void
get_pfnblock_bitmap_bitidx(const struct page *page, unsigned long pfn,
unsigned long **bitmap_word, unsigned long *bitidx)
@@ -370,7 +385,7 @@ get_pfnblock_bitmap_bitidx(const struct page *page, unsigned long pfn,
BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);
#endif
BUILD_BUG_ON(__MIGRATE_TYPE_END > PAGEBLOCK_MIGRATETYPE_MASK);
- VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page);
+ VM_BUG_ON_PAGE(!zone_spans_pfn_stable(page_zone(page), pfn), page);
bitmap = get_pageblock_bitmap(page, pfn);
*bitidx = pfn_to_bitidx(page, pfn);
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread