* [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges
2026-10-07 2:12 [RFC PATCH 0/5] mm/page_alloc: keep non-movable pages out of movable pageblocks for THP Rik van Riel
@ 2026-10-07 2:12 ` Rik van Riel
2026-10-14 13:43 ` Dev Jain
` (2 more replies)
2026-10-07 2:12 ` [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type Rik van Riel
` (3 subsequent siblings)
4 siblings, 3 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-07 2:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, Rik van Riel, stable
With debug_guardpage_minorder set, expand() turns the unused halves of a
split into guard pages, which page_del_and_expand() leaves out of the free
counts. Freeing the allocated half credits only that half, and
__free_one_page() then merges the guard buddy with clear_page_guard(),
which no longer touches the counters.
The merged block goes on a free list with the guard pages never counted,
so NR_FREE_PAGES falls behind the free lists by every guard that merges.
Before commit e0932b6c1f94 ("mm: page_alloc: consolidate free page
accounting"), __set_page_guard() and __clear_page_guard() adjusted the
counts themselves; that commit dropped both adjustments but kept the
subtraction implicit in expand().
Count the guard pages under the merged block's migratetype when the
buddy is cleared. account_freepages() skips an isolated block as it does
for every other free page, and moving the block off the isolated list
counts it then.
Booting a 16GB VM with debug_pagealloc=on debug_guardpage_minorder=1,
check the difference in free pages reported between /proc/vmstat
and /proc/buddyinfo.
At three points (idle, after a read and file-creation load, after
a second load), check the difference between nr_free_pages and the
buddyinfo numbers in the normal zone, as a number of pages:
idle after load after second load
unpatched -130 79205 99121
patched -16 45 0
The small remaining difference seems to be due to the numbers
not being read at exactly the same time, and is also seen
without guard pages.
Fixes: e0932b6c1f94 ("mm: page_alloc: consolidate free page accounting")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rik van Riel <riel@surriel.com>
---
mm/page_alloc.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 12fac9084c483..4658af97be010 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -988,10 +988,12 @@ static inline void __free_one_page(struct page *page,
* Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
* merge with it and move up one order.
*/
- if (page_is_guard(buddy))
+ if (page_is_guard(buddy)) {
clear_page_guard(zone, buddy, order);
- else
+ account_freepages(zone, 1 << order, migratetype);
+ } else {
__del_page_from_free_list(buddy, zone, order, buddy_mt);
+ }
if (unlikely(buddy_mt != migratetype)) {
/*
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges
2026-10-07 2:12 ` [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges Rik van Riel
@ 2026-10-14 13:43 ` Dev Jain
2026-10-14 20:37 ` Johannes Weiner
2026-10-14 20:57 ` Zi Yan
2 siblings, 0 replies; 20+ messages in thread
From: Dev Jain @ 2026-10-14 13:43 UTC (permalink / raw)
To: Rik van Riel, linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, stable
On 07/10/26 7:42 am, Rik van Riel wrote:
> With debug_guardpage_minorder set, expand() turns the unused halves of a
> split into guard pages, which page_del_and_expand() leaves out of the free
> counts. Freeing the allocated half credits only that half, and
> __free_one_page() then merges the guard buddy with clear_page_guard(),
> which no longer touches the counters.
>
> The merged block goes on a free list with the guard pages never counted,
> so NR_FREE_PAGES falls behind the free lists by every guard that merges.
> Before commit e0932b6c1f94 ("mm: page_alloc: consolidate free page
> accounting"), __set_page_guard() and __clear_page_guard() adjusted the
> counts themselves; that commit dropped both adjustments but kept the
> subtraction implicit in expand().
>
> Count the guard pages under the merged block's migratetype when the
> buddy is cleared. account_freepages() skips an isolated block as it does
> for every other free page, and moving the block off the isolated list
> counts it then.
>
> Booting a 16GB VM with debug_pagealloc=on debug_guardpage_minorder=1,
> check the difference in free pages reported between /proc/vmstat
> and /proc/buddyinfo.
>
> At three points (idle, after a read and file-creation load, after
> a second load), check the difference between nr_free_pages and the
> buddyinfo numbers in the normal zone, as a number of pages:
>
> idle after load after second load
> unpatched -130 79205 99121
> patched -16 45 0
>
> The small remaining difference seems to be due to the numbers
> not being read at exactly the same time, and is also seen
> without guard pages.
>
> Fixes: e0932b6c1f94 ("mm: page_alloc: consolidate free page accounting")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
LGTM
Reviewed-by: Dev Jain <dev.jain@arm.com>
Probably send this separately? RFC on a fix will delay
the patch.
> mm/page_alloc.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 12fac9084c483..4658af97be010 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -988,10 +988,12 @@ static inline void __free_one_page(struct page *page,
> * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
> * merge with it and move up one order.
> */
> - if (page_is_guard(buddy))
> + if (page_is_guard(buddy)) {
> clear_page_guard(zone, buddy, order);
> - else
> + account_freepages(zone, 1 << order, migratetype);
> + } else {
> __del_page_from_free_list(buddy, zone, order, buddy_mt);
> + }
>
> if (unlikely(buddy_mt != migratetype)) {
> /*
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges
2026-10-07 2:12 ` [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges Rik van Riel
2026-10-14 13:43 ` Dev Jain
@ 2026-10-14 20:37 ` Johannes Weiner
2026-10-14 20:57 ` Zi Yan
2 siblings, 0 replies; 20+ messages in thread
From: Johannes Weiner @ 2026-10-14 20:37 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, stable
On Tue, Oct 06, 2026 at 10:12:46PM -0400, Rik van Riel wrote:
> With debug_guardpage_minorder set, expand() turns the unused halves of a
> split into guard pages, which page_del_and_expand() leaves out of the free
> counts. Freeing the allocated half credits only that half, and
> __free_one_page() then merges the guard buddy with clear_page_guard(),
> which no longer touches the counters.
>
> The merged block goes on a free list with the guard pages never counted,
> so NR_FREE_PAGES falls behind the free lists by every guard that merges.
> Before commit e0932b6c1f94 ("mm: page_alloc: consolidate free page
> accounting"), __set_page_guard() and __clear_page_guard() adjusted the
> counts themselves; that commit dropped both adjustments but kept the
> subtraction implicit in expand().
>
> Count the guard pages under the merged block's migratetype when the
> buddy is cleared. account_freepages() skips an isolated block as it does
> for every other free page, and moving the block off the isolated list
> counts it then.
>
> Booting a 16GB VM with debug_pagealloc=on debug_guardpage_minorder=1,
> check the difference in free pages reported between /proc/vmstat
> and /proc/buddyinfo.
>
> At three points (idle, after a read and file-creation load, after
> a second load), check the difference between nr_free_pages and the
> buddyinfo numbers in the normal zone, as a number of pages:
>
> idle after load after second load
> unpatched -130 79205 99121
> patched -16 45 0
>
> The small remaining difference seems to be due to the numbers
> not being read at exactly the same time, and is also seen
> without guard pages.
>
> Fixes: e0932b6c1f94 ("mm: page_alloc: consolidate free page accounting")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
This should probably be not RFC?
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges
2026-10-07 2:12 ` [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges Rik van Riel
2026-10-14 13:43 ` Dev Jain
2026-10-14 20:37 ` Johannes Weiner
@ 2026-10-14 20:57 ` Zi Yan
2 siblings, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-10-14 20:57 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Johannes Weiner, Kairui Song,
Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Mike Rapoport, linux-mm, stable
On 6 Oct 2026, at 22:12, Rik van Riel wrote:
> With debug_guardpage_minorder set, expand() turns the unused halves of a
> split into guard pages, which page_del_and_expand() leaves out of the free
> counts. Freeing the allocated half credits only that half, and
> __free_one_page() then merges the guard buddy with clear_page_guard(),
> which no longer touches the counters.
>
> The merged block goes on a free list with the guard pages never counted,
> so NR_FREE_PAGES falls behind the free lists by every guard that merges.
> Before commit e0932b6c1f94 ("mm: page_alloc: consolidate free page
> accounting"), __set_page_guard() and __clear_page_guard() adjusted the
> counts themselves; that commit dropped both adjustments but kept the
> subtraction implicit in expand().
>
> Count the guard pages under the merged block's migratetype when the
> buddy is cleared. account_freepages() skips an isolated block as it does
> for every other free page, and moving the block off the isolated list
> counts it then.
>
> Booting a 16GB VM with debug_pagealloc=on debug_guardpage_minorder=1,
> check the difference in free pages reported between /proc/vmstat
> and /proc/buddyinfo.
>
> At three points (idle, after a read and file-creation load, after
> a second load), check the difference between nr_free_pages and the
> buddyinfo numbers in the normal zone, as a number of pages:
>
> idle after load after second load
> unpatched -130 79205 99121
> patched -16 45 0
>
> The small remaining difference seems to be due to the numbers
> not being read at exactly the same time, and is also seen
> without guard pages.
>
> Fixes: e0932b6c1f94 ("mm: page_alloc: consolidate free page accounting")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
> mm/page_alloc.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
Good catch.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-07 2:12 [RFC PATCH 0/5] mm/page_alloc: keep non-movable pages out of movable pageblocks for THP Rik van Riel
2026-10-07 2:12 ` [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges Rik van Riel
@ 2026-10-07 2:12 ` Rik van Riel
2026-10-14 14:16 ` Dev Jain
2026-10-14 20:40 ` Johannes Weiner
2026-10-07 2:12 ` [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation Rik van Riel
` (2 subsequent siblings)
4 siblings, 2 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-07 2:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, Rik van Riel
__free_one_page() merges a freed page with a free buddy of another
mergeable type at pageblock order and above, and retypes the buddy to
match. The buddy's pages stay counted under the old type: nothing moves
them to the merged type in the free counts.
That is unobservable today. NR_FREE_PAGES does not separate types, so the
missing move nets to zero there. Any future per-type free count would
undercount on a merge from the other side, and allocating the merged
run later could wrap it.
Move the buddy's pages to the merged type in the count too.
Assisted-by: LLM
Signed-off-by: Rik van Riel <riel@surriel.com>
---
mm/page_alloc.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 4658af97be010..ae38e25971559 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -993,6 +993,11 @@ static inline void __free_one_page(struct page *page,
account_freepages(zone, 1 << order, migratetype);
} else {
__del_page_from_free_list(buddy, zone, order, buddy_mt);
+ /* The buddy's free pages join the merged block's type. */
+ if (unlikely(buddy_mt != migratetype)) {
+ account_freepages(zone, -(1 << order), buddy_mt);
+ account_freepages(zone, 1 << order, migratetype);
+ }
}
if (unlikely(buddy_mt != migratetype)) {
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-07 2:12 ` [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type Rik van Riel
@ 2026-10-14 14:16 ` Dev Jain
2026-10-14 14:27 ` Rik van Riel
2026-10-14 20:40 ` Johannes Weiner
1 sibling, 1 reply; 20+ messages in thread
From: Dev Jain @ 2026-10-14 14:16 UTC (permalink / raw)
To: Rik van Riel, linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On 07/10/26 7:42 am, Rik van Riel wrote:
> __free_one_page() merges a freed page with a free buddy of another
> mergeable type at pageblock order and above, and retypes the buddy to
> match. The buddy's pages stay counted under the old type: nothing moves
> them to the merged type in the free counts.
>
> That is unobservable today. NR_FREE_PAGES does not separate types, so the
> missing move nets to zero there. Any future per-type free count would
> undercount on a merge from the other side, and allocating the merged
> run later could wrap it.
>
> Move the buddy's pages to the merged type in the count too.
>
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
> mm/page_alloc.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 4658af97be010..ae38e25971559 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -993,6 +993,11 @@ static inline void __free_one_page(struct page *page,
> account_freepages(zone, 1 << order, migratetype);
> } else {
> __del_page_from_free_list(buddy, zone, order, buddy_mt);
> + /* The buddy's free pages join the merged block's type. */
> + if (unlikely(buddy_mt != migratetype)) {
> + account_freepages(zone, -(1 << order), buddy_mt);
> + account_freepages(zone, 1 << order, migratetype);
> + }
> }
>
MAX_PAGE_ORDER > pageblock_order confuses things so much : (
Sashiko raises something:
https://sashiko.dev/#/patchset/20261007021250.1665929-1-riel%40surriel.com
Although feels like it is exaggerating.
My concern is for systems with a high diff between MAX_PAGE_ORDER and
pageblock_order. So on arm64 systems with 64K pagesize but pageblock
size as 2M (and PMD size 512M), the pageblock order is 5. We use this
configuration to reliably get TLB optimization from contpte size mappings.
Will we then repeatedly overflow the pcpu counter into the zone counter,
regressing stuff?
Perhaps I am overthinking. Your approach is clean, but perhaps we could
also do:
void account_freepages_move(zone, order, buddy_mt, mt)
/* PCP-types do not have per-migratetype free counts. */
if (likely (buddy_mt < MIGRATE_PCPTYPES && mt < MIGRATE_PCPTYPES))
return;
account_freepages(zone, -(1 << order), buddy_mt);
account_freepages(zone, 1 << order, migratetype);
> if (unlikely(buddy_mt != migratetype)) {
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-14 14:16 ` Dev Jain
@ 2026-10-14 14:27 ` Rik van Riel
2026-10-14 15:19 ` Dev Jain
0 siblings, 1 reply; 20+ messages in thread
From: Rik van Riel @ 2026-10-14 14:27 UTC (permalink / raw)
To: Dev Jain, linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Wed, 2026-10-14 at 19:46 +0530, Dev Jain wrote:
>
>
> > +++ b/mm/page_alloc.c
> > @@ -993,6 +993,11 @@ static inline void __free_one_page(struct page
> > *page,
> > account_freepages(zone, 1 << order,
> > migratetype);
> > } else {
> > __del_page_from_free_list(buddy, zone,
> > order, buddy_mt);
> > + /* The buddy's free pages join the merged
> > block's type. */
> > + if (unlikely(buddy_mt != migratetype)) {
> > + account_freepages(zone, -(1 <<
> > order), buddy_mt);
> > + account_freepages(zone, 1 <<
> > order, migratetype);
> > + }
> > }
> >
>
> MAX_PAGE_ORDER > pageblock_order confuses things so much : (
I'm not a fan, either. It trips me up every time.
>
> Sashiko raises something:
> https://sashiko.dev/#/patchset/20261007021250.1665929-1-riel%40surriel.com
>
> Although feels like it is exaggerating.
I suppose if we switch the two lines around, doing
the increment on migratetype before decrementing
buddy_mt we could avoid that transient underflow?
>
> My concern is for systems with a high diff between MAX_PAGE_ORDER and
> pageblock_order. So on arm64 systems with 64K pagesize but pageblock
> size as 2M (and PMD size 512M), the pageblock order is 5. We use this
> configuration to reliably get TLB optimization from contpte size
> mappings.
>
> Will we then repeatedly overflow the pcpu counter into the zone
> counter,
> regressing stuff?
The per_cpu_pages struct does not seem to keep
counts on a per migratetype basis at all. It
has per migratetype lists, but only one
pcp->count.
Also doesn't the pcp only carry order zero
pages currently?
I don't really understand your worry.
>
> Perhaps I am overthinking. Your approach is clean, but perhaps we
> could
> also do:
>
> void account_freepages_move(zone, order, buddy_mt, mt)
>
> /* PCP-types do not have per-migratetype free counts. */
> if (likely (buddy_mt < MIGRATE_PCPTYPES && mt <
> MIGRATE_PCPTYPES))
> return;
Wouldn't that throw off the counts when we merge
e.g. a MIGRATE_UNMOVABLE page with a MIGRATE_MOVABLE
buddy?
With your test we would only ever do the free count
adjustment for mismatched types if one of the pages
was HIGHATOMIC or CMA. That leaves the common case
without coverage.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-14 14:27 ` Rik van Riel
@ 2026-10-14 15:19 ` Dev Jain
2026-10-14 15:40 ` Rik van Riel
0 siblings, 1 reply; 20+ messages in thread
From: Dev Jain @ 2026-10-14 15:19 UTC (permalink / raw)
To: Rik van Riel, linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On 14/10/26 7:57 pm, Rik van Riel wrote:
> On Wed, 2026-10-14 at 19:46 +0530, Dev Jain wrote:
>>
>>
>>> +++ b/mm/page_alloc.c
>>> @@ -993,6 +993,11 @@ static inline void __free_one_page(struct page
>>> *page,
>>> account_freepages(zone, 1 << order,
>>> migratetype);
>>> } else {
>>> __del_page_from_free_list(buddy, zone,
>>> order, buddy_mt);
>>> + /* The buddy's free pages join the merged
>>> block's type. */
>>> + if (unlikely(buddy_mt != migratetype)) {
>>> + account_freepages(zone, -(1 <<
>>> order), buddy_mt);
>>> + account_freepages(zone, 1 <<
>>> order, migratetype);
>>> + }
>>> }
>>>
>>
>> MAX_PAGE_ORDER > pageblock_order confuses things so much : (
>
> I'm not a fan, either. It trips me up every time.
>
>>
>> Sashiko raises something:
>> https://sashiko.dev/#/patchset/20261007021250.1665929-1-riel%40surriel.com
>>
>> Although feels like it is exaggerating.
>
> I suppose if we switch the two lines around, doing
> the increment on migratetype before decrementing
> buddy_mt we could avoid that transient underflow?
That would become a transient overflow. See below.
>
>>
>> My concern is for systems with a high diff between MAX_PAGE_ORDER and
>> pageblock_order. So on arm64 systems with 64K pagesize but pageblock
>> size as 2M (and PMD size 512M), the pageblock order is 5. We use this
>> configuration to reliably get TLB optimization from contpte size
>> mappings.
>>
>> Will we then repeatedly overflow the pcpu counter into the zone
>> counter,
>> regressing stuff?
>
> The per_cpu_pages struct does not seem to keep
> counts on a per migratetype basis at all. It
> has per migratetype lists, but only one
> pcp->count.
>
> Also doesn't the pcp only carry order zero
> pages currently?
>
> I don't really understand your worry.
Sorry I was not talking about the pcp list (also, pcp carries
order 0 till 3?)
In __mod_zone_page_state, we use the per-cpu variable pcp->vm_stat_diff,
and when it overflows or underflows past a pcp->stat_threshold, we
push the diff to the global (zone) state.
The reason we do so is to optimize by batching zone operations into
per-cpu local operations.
My concern is that for large delta, we will always overflow
or underflow, defeating the purpose of the per-cpu caching.
So on said arm64 systems, we may frequently enter your
if (unlikely(buddy_mt != migratetype)
branch, and if pcp->stat_threshold is not enough, we may repeatedly
overflow and underflow, defeating this per-cpu optimization.
>
>>
>> Perhaps I am overthinking. Your approach is clean, but perhaps we
>> could
>> also do:
>>
>> void account_freepages_move(zone, order, buddy_mt, mt)
>>
>> /* PCP-types do not have per-migratetype free counts. */
>> if (likely (buddy_mt < MIGRATE_PCPTYPES && mt <
>> MIGRATE_PCPTYPES))
>> return;
>
> Wouldn't that throw off the counts when we merge
> e.g. a MIGRATE_UNMOVABLE page with a MIGRATE_MOVABLE
> buddy?
>
As you said, in account_freepages(), there is currently
no conditional based on MOVABLE and UNMOVABLE. So we
are essentially doing -x and +x, netting to zero.
So we could write my function, make it clear that currently
this is not needed for the MIGRATE_PCPTYPES, as opposed
to MIGRATE_CMA and MIGRATE_HIGHATOMIC which have separate
accounting.
> With your test we would only ever do the free count
> adjustment for mismatched types if one of the pages
> was HIGHATOMIC or CMA. That leaves the common case
> without coverage.
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-14 15:19 ` Dev Jain
@ 2026-10-14 15:40 ` Rik van Riel
0 siblings, 0 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-14 15:40 UTC (permalink / raw)
To: Dev Jain, linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Wed, 2026-10-14 at 20:49 +0530, Dev Jain wrote:
>
> As you said, in account_freepages(), there is currently
> no conditional based on MOVABLE and UNMOVABLE. So we
> are essentially doing -x and +x, netting to zero.
Lets defer this change until later.
During a later stage of the gigablock code, I
do need to track each zone's free pages by
migratetype, but it's not needed yet.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type
2026-10-07 2:12 ` [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type Rik van Riel
2026-10-14 14:16 ` Dev Jain
@ 2026-10-14 20:40 ` Johannes Weiner
1 sibling, 0 replies; 20+ messages in thread
From: Johannes Weiner @ 2026-10-14 20:40 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Tue, Oct 06, 2026 at 10:12:47PM -0400, Rik van Riel wrote:
> __free_one_page() merges a freed page with a free buddy of another
> mergeable type at pageblock order and above, and retypes the buddy to
> match. The buddy's pages stay counted under the old type: nothing moves
> them to the merged type in the free counts.
>
> That is unobservable today. NR_FREE_PAGES does not separate types, so the
> missing move nets to zero there. Any future per-type free count would
> undercount on a merge from the other side, and allocating the merged
> run later could wrap it.
>
> Move the buddy's pages to the merged type in the count too.
>
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
> mm/page_alloc.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 4658af97be010..ae38e25971559 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -993,6 +993,11 @@ static inline void __free_one_page(struct page *page,
> account_freepages(zone, 1 << order, migratetype);
> } else {
> __del_page_from_free_list(buddy, zone, order, buddy_mt);
> + /* The buddy's free pages join the merged block's type. */
> + if (unlikely(buddy_mt != migratetype)) {
> + account_freepages(zone, -(1 << order), buddy_mt);
> + account_freepages(zone, 1 << order, migratetype);
Reasonable on one hand, but I think at that point it would be simpler
and more robust to drop that 'single account_freepages() + __add/__del
operations' optimization, and just use the accounting add/del variants
directly instead.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-07 2:12 [RFC PATCH 0/5] mm/page_alloc: keep non-movable pages out of movable pageblocks for THP Rik van Riel
2026-10-07 2:12 ` [RFC PATCH 1/5] mm/page_alloc: count guard pages as free again when their buddy merges Rik van Riel
2026-10-07 2:12 ` [RFC PATCH 2/5] mm/page_alloc: count a merged buddy's pages under the merged type Rik van Riel
@ 2026-10-07 2:12 ` Rik van Riel
2026-10-10 15:49 ` kernel test robot
` (2 more replies)
2026-10-07 2:12 ` [RFC PATCH 4/5] mm/page_alloc: skip movable blocks during non-movable steals Rik van Riel
2026-10-07 2:12 ` [RFC PATCH 5/5] mm/page_alloc: claim blocks captured for non-movable use Rik van Riel
4 siblings, 3 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-07 2:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, Rik van Riel
try_to_claim_block() converts a movable pageblock for a non-movable
allocation only when half its pages are free or compatible.
Otherwise the steal path takes pages without converting, and
non-movable content sits in a block still typed movable.
Convert a movable block on any non-movable claim, however few of
its pages are free, so the block's type matches the allocation it
serves.
When other pages in the block are freed later, they end up on the
non-movable free lists, directing more non-movable allocations to
the already non-movable page blocks.
Converting a non-movable pageblock to movable is made stricter:
a movable claim takes a non-movable block only when every page
is free or movable.
Blocks straddling a zone edge are never claimed;
zone_spans_pageblock() tests for a block wholly inside its zone.
Assisted-by: LLM
Signed-off-by: Rik van Riel <riel@surriel.com>
---
include/linux/mmzone.h | 13 ++++++++
mm/page_alloc.c | 71 ++++++++++++++++++++----------------------
2 files changed, 46 insertions(+), 38 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 94f9c3ff54160..81ad83ff69073 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1227,6 +1227,19 @@ static inline bool zone_spans_pfn(const struct zone *zone, unsigned long pfn)
return zone->zone_start_pfn <= pfn && pfn < zone_end_pfn(zone);
}
+/*
+ * Whether the pageblock holding @pfn lies wholly inside @zone. Zone
+ * spans are not pageblock-aligned, so the edge pageblocks of a zone
+ * can straddle into the next zone; those never change type.
+ */
+static inline bool zone_spans_pageblock(const struct zone *zone, unsigned long pfn)
+{
+ unsigned long start = pageblock_start_pfn(pfn);
+
+ return zone->zone_start_pfn <= start &&
+ start + pageblock_nr_pages <= zone_end_pfn(zone);
+}
+
static inline bool zone_is_initialized(const struct zone *zone)
{
return zone->initialized;
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index ae38e25971559..9f1520506ae1d 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -2295,18 +2295,39 @@ find_suitable_fallback(struct free_area *area, unsigned int order,
}
/*
- * This function implements actual block claiming behaviour. If order is large
- * enough, we can claim the whole pageblock for the requested migratetype. If
- * not, we check the pageblock for constituent pages; if at least half of the
- * pages are free or compatible, we can still claim the whole block, so pages
- * freed in the future will be put on the correct free list.
+ * Whether to retype a partly used block. A block wholly inside its zone
+ * takes the allocation's type, so no non-movable page sits in a
+ * movable-typed block; a straddling block is never retyped.
*/
+static bool should_claim_used_block(struct zone *zone, unsigned long start_pfn,
+ int start_type, int block_type,
+ int free_pages, int movable_pages)
+{
+ if (!zone_spans_pageblock(zone, start_pfn))
+ return false;
+
+ if (page_group_by_mobility_disabled)
+ return true;
+
+ /* Convert to movable only if no non-movable pages are present. */
+ if (is_migrate_movable(start_type))
+ return free_pages + movable_pages == pageblock_nr_pages;
+
+ /* Non-movable pages break compaction; claim as non-movable */
+ if (is_migrate_movable(block_type))
+ return true;
+
+ /* Unmovable or reclaimable claiming from the other. */
+ return free_pages >= (1 << (pageblock_order - 1));
+}
+
+/* Convert the block so later frees use the allocation's migratetype. */
static struct page *
try_to_claim_block(struct zone *zone, struct page *page,
int current_order, int order, int start_type,
int block_type, unsigned int alloc_flags)
{
- int free_pages, movable_pages, alike_pages;
+ int free_pages, movable_pages;
unsigned long start_pfn;
/* Take ownership for orders >= pageblock_order */
@@ -2333,39 +2354,13 @@ try_to_claim_block(struct zone *zone, struct page *page,
&movable_pages))
return NULL;
- /*
- * Determine how many pages are compatible with our allocation.
- * For movable allocation, it's the number of movable pages which
- * we just obtained. For other types it's a bit more tricky.
- */
- if (start_type == MIGRATE_MOVABLE) {
- alike_pages = movable_pages;
- } else {
- /*
- * If we are falling back a RECLAIMABLE or UNMOVABLE allocation
- * to MOVABLE pageblock, consider all non-movable pages as
- * compatible. If it's UNMOVABLE falling back to RECLAIMABLE or
- * vice versa, be conservative since we can't distinguish the
- * exact migratetype of non-movable pages.
- */
- if (block_type == MIGRATE_MOVABLE)
- alike_pages = pageblock_nr_pages
- - (free_pages + movable_pages);
- else
- alike_pages = 0;
- }
- /*
- * If a sufficient number of pages in the block are either free or of
- * compatible migratability as our allocation, claim the whole block.
- */
- if (free_pages + alike_pages >= (1 << (pageblock_order-1)) ||
- page_group_by_mobility_disabled) {
- __move_freepages_block(zone, start_pfn, block_type, start_type);
- set_pageblock_migratetype(pfn_to_page(start_pfn), start_type);
- return __rmqueue_smallest(zone, order, start_type);
- }
+ if (!should_claim_used_block(zone, start_pfn, start_type, block_type,
+ free_pages, movable_pages))
+ return NULL;
- return NULL;
+ __move_freepages_block(zone, start_pfn, block_type, start_type);
+ set_pageblock_migratetype(pfn_to_page(start_pfn), start_type);
+ return __rmqueue_smallest(zone, order, start_type);
}
/*
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-07 2:12 ` [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation Rik van Riel
@ 2026-10-10 15:49 ` kernel test robot
2026-10-10 17:14 ` kernel test robot
2026-10-14 20:48 ` Johannes Weiner
2 siblings, 0 replies; 20+ messages in thread
From: kernel test robot @ 2026-10-10 15:49 UTC (permalink / raw)
To: Rik van Riel; +Cc: llvm, oe-kbuild-all
Hi Rik,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build errors:
[auto build test ERROR on 67f0943b394d920b6c142aad8c6af94340342ae7]
url: https://github.com/intel-lab-lkp/linux/commits/Rik-van-Riel/mm-page_alloc-count-guard-pages-as-free-again-when-their-buddy-merges/20261006-221246
base: 67f0943b394d920b6c142aad8c6af94340342ae7
patch link: https://lore.kernel.org/r/20261007021250.1665929-4-riel%40surriel.com
patch subject: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
config: riscv-allyesconfig (https://download.01.org/0day-ci/archive/20261010/202610102333.1wHL7KPQ-lkp@intel.com/config)
compiler: clang version 24.0.0git (https://github.com/llvm/llvm-project 51b433b4f56c531c0d95ba15e1a15e06c399d552)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261010/202610102333.1wHL7KPQ-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610102333.1wHL7KPQ-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'PMD_SHIFT'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
include/linux/mmzone.h:1240:17: error: use of undeclared identifier 'PMD_SHIFT'
1240 | start + pageblock_nr_pages <= zone_end_pfn(zone);
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:8:
In file included from include/linux/gfp.h:7:
include/linux/mmzone.h:1240:17: error: use of undeclared identifier 'PMD_SHIFT'
1240 | start + pageblock_nr_pages <= zone_end_pfn(zone);
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:60:46: note: expanded from macro 'pageblock_order'
60 | #define pageblock_order MIN_T(unsigned int, HUGETLB_PAGE_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~~~~
arch/riscv/include/asm/page.h:20:34: note: expanded from macro 'HUGETLB_PAGE_ORDER'
20 | #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
| ^~~~~~~~~~~
arch/riscv/include/asm/page.h:17:22: note: expanded from macro 'HPAGE_SHIFT'
17 | #define HPAGE_SHIFT PMD_SHIFT
| ^~~~~~~~~
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:36:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:98:11: warning: array index 3 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
98 | return (set->sig[3] | set->sig[2] |
| ^ ~
include/uapi/asm-generic/signal.h:62:2: note: array 'sig' declared here
62 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:36:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:98:25: warning: array index 2 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
98 | return (set->sig[3] | set->sig[2] |
| ^ ~
include/uapi/asm-generic/signal.h:62:2: note: array 'sig' declared here
62 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:36:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:99:4: warning: array index 1 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
99 | set->sig[1] | set->sig[0]) == 0;
| ^ ~
include/uapi/asm-generic/signal.h:62:2: note: array 'sig' declared here
62 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:36:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:101:11: warning: array index 1 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
101 | return (set->sig[1] | set->sig[0]) == 0;
| ^ ~
include/uapi/asm-generic/signal.h:62:2: note: array 'sig' declared here
62 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/riscv/kernel/asm-offsets.c:9:
In file included from include/linux/mm.h:36:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:114:11: warning: array index 3 is past the end of the array (that has type 'const unsigned long[1]') [-Warray-bounds]
114 | return (set1->sig[3] == set2->sig[3]) &&
| ^ ~
vim +/PMD_SHIFT +1237 include/linux/mmzone.h
1229
1230 /*
1231 * Whether the pageblock holding @pfn lies wholly inside @zone. Zone
1232 * spans are not pageblock-aligned, so the edge pageblocks of a zone
1233 * can straddle into the next zone; those never change type.
1234 */
1235 static inline bool zone_spans_pageblock(const struct zone *zone, unsigned long pfn)
1236 {
> 1237 unsigned long start = pageblock_start_pfn(pfn);
1238
1239 return zone->zone_start_pfn <= start &&
1240 start + pageblock_nr_pages <= zone_end_pfn(zone);
1241 }
1242
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-07 2:12 ` [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation Rik van Riel
2026-10-10 15:49 ` kernel test robot
@ 2026-10-10 17:14 ` kernel test robot
2026-10-14 20:48 ` Johannes Weiner
2 siblings, 0 replies; 20+ messages in thread
From: kernel test robot @ 2026-10-10 17:14 UTC (permalink / raw)
To: Rik van Riel; +Cc: llvm, oe-kbuild-all
Hi Rik,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build errors:
[auto build test ERROR on 67f0943b394d920b6c142aad8c6af94340342ae7]
url: https://github.com/intel-lab-lkp/linux/commits/Rik-van-Riel/mm-page_alloc-count-guard-pages-as-free-again-when-their-buddy-merges/20261006-221246
base: 67f0943b394d920b6c142aad8c6af94340342ae7
patch link: https://lore.kernel.org/r/20261007021250.1665929-4-riel%40surriel.com
patch subject: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
config: x86_64-randconfig-1006-20261010 (https://download.01.org/0day-ci/archive/20261011/202610110115.cCikSIza-lkp@intel.com/config)
compiler: clang version 22.1.3 (https://github.com/llvm/llvm-project e9846648fd6183ee6d8cbdb4502213fcf902a211)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261011/202610110115.cCikSIza-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610110115.cCikSIza-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
>> include/linux/mmzone.h:1237:24: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1237 | unsigned long start = pageblock_start_pfn(pfn);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:78:52: note: expanded from macro 'pageblock_start_pfn'
78 | #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages)
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
include/linux/mmzone.h:1240:17: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1240 | start + pageblock_nr_pages <= zone_end_pfn(zone);
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:9:
In file included from include/linux/crypto.h:18:
In file included from include/linux/slab.h:17:
In file included from include/linux/gfp.h:7:
include/linux/mmzone.h:1240:17: error: use of undeclared identifier 'HPAGE_PMD_ORDER'
1240 | start + pageblock_nr_pages <= zone_end_pfn(zone);
| ^~~~~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:75:36: note: expanded from macro 'pageblock_nr_pages'
75 | #define pageblock_nr_pages (1UL << pageblock_order)
| ^~~~~~~~~~~~~~~
include/linux/pageblock-flags.h:66:46: note: expanded from macro 'pageblock_order'
66 | #define pageblock_order MIN_T(unsigned int, HPAGE_PMD_ORDER, PAGE_BLOCK_MAX_ORDER)
| ^~~~~~~~~~~~~~~
In file included from arch/x86/kernel/asm-offsets.c:10:
In file included from include/crypto/aria.h:22:
In file included from include/linux/module.h:20:
In file included from include/linux/elf.h:6:
In file included from arch/x86/include/asm/elf.h:10:
In file included from arch/x86/include/asm/ia32.h:7:
In file included from include/linux/compat.h:17:
In file included from include/linux/fs.h:5:
In file included from include/linux/fs/super.h:5:
In file included from include/linux/fs/super_types.h:13:
In file included from include/linux/percpu-rwsem.h:7:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:98:11: warning: array index 3 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
98 | return (set->sig[3] | set->sig[2] |
| ^ ~
arch/x86/include/asm/signal.h:24:2: note: array 'sig' declared here
24 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/x86/kernel/asm-offsets.c:10:
In file included from include/crypto/aria.h:22:
In file included from include/linux/module.h:20:
In file included from include/linux/elf.h:6:
In file included from arch/x86/include/asm/elf.h:10:
In file included from arch/x86/include/asm/ia32.h:7:
In file included from include/linux/compat.h:17:
In file included from include/linux/fs.h:5:
In file included from include/linux/fs/super.h:5:
In file included from include/linux/fs/super_types.h:13:
In file included from include/linux/percpu-rwsem.h:7:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:98:25: warning: array index 2 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
98 | return (set->sig[3] | set->sig[2] |
| ^ ~
arch/x86/include/asm/signal.h:24:2: note: array 'sig' declared here
24 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/x86/kernel/asm-offsets.c:10:
In file included from include/crypto/aria.h:22:
In file included from include/linux/module.h:20:
In file included from include/linux/elf.h:6:
In file included from arch/x86/include/asm/elf.h:10:
In file included from arch/x86/include/asm/ia32.h:7:
In file included from include/linux/compat.h:17:
In file included from include/linux/fs.h:5:
In file included from include/linux/fs/super.h:5:
In file included from include/linux/fs/super_types.h:13:
In file included from include/linux/percpu-rwsem.h:7:
In file included from include/linux/rcuwait.h:6:
In file included from include/linux/sched/signal.h:6:
include/linux/signal.h:99:4: warning: array index 1 is past the end of the array (that has type 'unsigned long[1]') [-Warray-bounds]
99 | set->sig[1] | set->sig[0]) == 0;
| ^ ~
arch/x86/include/asm/signal.h:24:2: note: array 'sig' declared here
24 | unsigned long sig[_NSIG_WORDS];
| ^
In file included from arch/x86/kernel/asm-offsets.c:10:
In file included from include/crypto/aria.h:22:
In file included from include/linux/module.h:20:
In file included from include/linux/elf.h:6:
In file included from arch/x86/include/asm/elf.h:10:
In file included from arch/x86/include/asm/ia32.h:7:
vim +/HPAGE_PMD_ORDER +1237 include/linux/mmzone.h
1229
1230 /*
1231 * Whether the pageblock holding @pfn lies wholly inside @zone. Zone
1232 * spans are not pageblock-aligned, so the edge pageblocks of a zone
1233 * can straddle into the next zone; those never change type.
1234 */
1235 static inline bool zone_spans_pageblock(const struct zone *zone, unsigned long pfn)
1236 {
> 1237 unsigned long start = pageblock_start_pfn(pfn);
1238
1239 return zone->zone_start_pfn <= start &&
1240 start + pageblock_nr_pages <= zone_end_pfn(zone);
1241 }
1242
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-07 2:12 ` [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation Rik van Riel
2026-10-10 15:49 ` kernel test robot
2026-10-10 17:14 ` kernel test robot
@ 2026-10-14 20:48 ` Johannes Weiner
2026-10-14 20:53 ` Rik van Riel
2 siblings, 1 reply; 20+ messages in thread
From: Johannes Weiner @ 2026-10-14 20:48 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Tue, Oct 06, 2026 at 10:12:48PM -0400, Rik van Riel wrote:
> try_to_claim_block() converts a movable pageblock for a non-movable
> allocation only when half its pages are free or compatible.
> Otherwise the steal path takes pages without converting, and
> non-movable content sits in a block still typed movable.
>
> Convert a movable block on any non-movable claim, however few of
> its pages are free, so the block's type matches the allocation it
> serves.
>
> When other pages in the block are freed later, they end up on the
> non-movable free lists, directing more non-movable allocations to
> the already non-movable page blocks.
>
> Converting a non-movable pageblock to movable is made stricter:
> a movable claim takes a non-movable block only when every page
> is free or movable.
>
> Blocks straddling a zone edge are never claimed;
> zone_spans_pageblock() tests for a block wholly inside its zone.
>
> Assisted-by: LLM
> Signed-off-by: Rik van Riel <riel@surriel.com>
I like this in theory, but wonder how this behaves in practice.
The block is claimed, but the movable constituents aren't proactively
kicked out. The next unmovable request might well claim another mostly
movable block, and so forth. With a flurry of short-lived unmovable
allocations, this might explode the unmovable blocks, which will be
hard to recover especially when you raise the bar on *that* direction.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-14 20:48 ` Johannes Weiner
@ 2026-10-14 20:53 ` Rik van Riel
2026-10-15 8:20 ` Dev Jain
0 siblings, 1 reply; 20+ messages in thread
From: Rik van Riel @ 2026-10-14 20:53 UTC (permalink / raw)
To: Johannes Weiner
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Wed, 2026-10-14 at 22:48 +0200, Johannes Weiner wrote:
> On Tue, Oct 06, 2026 at 10:12:48PM -0400, Rik van Riel wrote:
> > try_to_claim_block() converts a movable pageblock for a non-movable
> > allocation only when half its pages are free or compatible.
> > Otherwise the steal path takes pages without converting, and
> > non-movable content sits in a block still typed movable.
> >
> > Convert a movable block on any non-movable claim, however few of
> > its pages are free, so the block's type matches the allocation it
> > serves.
> >
> > When other pages in the block are freed later, they end up on the
> > non-movable free lists, directing more non-movable allocations to
> > the already non-movable page blocks.
> >
> > Converting a non-movable pageblock to movable is made stricter:
> > a movable claim takes a non-movable block only when every page
> > is free or movable.
> >
> > Blocks straddling a zone edge are never claimed;
> > zone_spans_pageblock() tests for a block wholly inside its zone.
> >
> > Assisted-by: LLM
> > Signed-off-by: Rik van Riel <riel@surriel.com>
>
> I like this in theory, but wonder how this behaves in practice.
>
> The block is claimed, but the movable constituents aren't proactively
> kicked out.
I have code for that, too, but it seems like
it's a lot easier to get things reviewed in
small portions.
If you want I can resend this as a larger
series, with:
1) the pageblock claiming
2) per migrate type free lists
3) evacuation of movable pages from unmovable
pageblocks, when the kernel free lists fall
below a watermark
That would be the full extent of the 2MB granularity
fragmentation avoidance stuff I have here.
Just let me know your preference.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-14 20:53 ` Rik van Riel
@ 2026-10-15 8:20 ` Dev Jain
2026-10-15 12:53 ` Rik van Riel
0 siblings, 1 reply; 20+ messages in thread
From: Dev Jain @ 2026-10-15 8:20 UTC (permalink / raw)
To: Rik van Riel, Johannes Weiner
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On 15/10/26 2:23 am, Rik van Riel wrote:
> On Wed, 2026-10-14 at 22:48 +0200, Johannes Weiner wrote:
>> On Tue, Oct 06, 2026 at 10:12:48PM -0400, Rik van Riel wrote:
>>> try_to_claim_block() converts a movable pageblock for a non-movable
>>> allocation only when half its pages are free or compatible.
>>> Otherwise the steal path takes pages without converting, and
>>> non-movable content sits in a block still typed movable.
>>>
>>> Convert a movable block on any non-movable claim, however few of
>>> its pages are free, so the block's type matches the allocation it
>>> serves.
>>>
>>> When other pages in the block are freed later, they end up on the
>>> non-movable free lists, directing more non-movable allocations to
>>> the already non-movable page blocks.
>>>
>>> Converting a non-movable pageblock to movable is made stricter:
>>> a movable claim takes a non-movable block only when every page
>>> is free or movable.
>>>
>>> Blocks straddling a zone edge are never claimed;
>>> zone_spans_pageblock() tests for a block wholly inside its zone.
>>>
>>> Assisted-by: LLM
>>> Signed-off-by: Rik van Riel <riel@surriel.com>
>>
>> I like this in theory, but wonder how this behaves in practice.
>>
>> The block is claimed, but the movable constituents aren't proactively
>> kicked out.
>
> I have code for that, too, but it seems like
> it's a lot easier to get things reviewed in
> small portions.
>
> If you want I can resend this as a larger
> series, with:
> 1) the pageblock claiming
> 2) per migrate type free lists
> 3) evacuation of movable pages from unmovable
> pageblocks, when the kernel free lists fall
> below a watermark
>
> That would be the full extent of the 2MB granularity
> fragmentation avoidance stuff I have here.
I think your patch series applied as-is would regress stuff:
we may have lots of UNMOVABLE pageblocks with lots of
movable allocations, unable to be rescued, hurting mTHP
allocations.
>
> Just let me know your preference.
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation
2026-10-15 8:20 ` Dev Jain
@ 2026-10-15 12:53 ` Rik van Riel
0 siblings, 0 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-15 12:53 UTC (permalink / raw)
To: Dev Jain, Johannes Weiner
Cc: linux-kernel, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Brendan Jackman, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm
On Thu, 2026-10-15 at 13:50 +0530, Dev Jain wrote:
>
> >
> > I have code for that, too, but it seems like
> > it's a lot easier to get things reviewed in
> > small portions.
> >
> > If you want I can resend this as a larger
> > series, with:
> > 1) the pageblock claiming
> > 2) per migrate type free lists
> > 3) evacuation of movable pages from unmovable
> > pageblocks, when the kernel free lists fall
> > below a watermark
> >
> > That would be the full extent of the 2MB granularity
> > fragmentation avoidance stuff I have here.
>
> I think your patch series applied as-is would regress stuff:
> we may have lots of UNMOVABLE pageblocks with lots of
> movable allocations, unable to be rescued, hurting mTHP
> allocations.
How should we move forward with this?
Do do guys want to review this series first,
and then I post an update with the other
stuff, before any gets merged?
Or do you want me to post a larger series
all at once?
Before I move on, do the combined policy
changes from patches 3, 4, and 5 make sense
to you (with or without evacuation) ?
Patch 1 from this series can be merged all
by itself as a separate thing.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [RFC PATCH 4/5] mm/page_alloc: skip movable blocks during non-movable steals
2026-10-07 2:12 [RFC PATCH 0/5] mm/page_alloc: keep non-movable pages out of movable pageblocks for THP Rik van Riel
` (2 preceding siblings ...)
2026-10-07 2:12 ` [RFC PATCH 3/5] mm/page_alloc: convert any movable pageblock on a non-movable allocation Rik van Riel
@ 2026-10-07 2:12 ` Rik van Riel
2026-10-07 2:12 ` [RFC PATCH 5/5] mm/page_alloc: claim blocks captured for non-movable use Rik van Riel
4 siblings, 0 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-07 2:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, Rik van Riel
__rmqueue_steal() hands a non-movable allocation pages from a
movable pageblock and leaves the block's type alone. Those pages
can never migrate, so the block reads movable to compaction while
holding content compaction cannot move. On a 3 GB guest running
ls -lR / and a 2.4 GB block device read against a fragmenter, a
temporary counter found 2654, 5341 and 5272 such allocations in
three boots.
__rmqueue_claim() runs first and would convert the block, but
rmqueue_bulk() remembers the mode across its batch. Prevent
those allocations from placing non-movable pages inside
movable pageblocks by refusing __rmqueue_steal() for non-movable
allocations in movable pageblocks.
Movable is last in fallbacks[] for both non-movable types, so
only a larger order is left to try; failing that, the steal
returns NULL and the allocation will loop around to a claim,
another zone, or reclaim.
Keep the steal where mobility grouping is disabled, or where
the block straddles a zone edge. A block that straddles zones
cannot be claimed, because the allocator only holds the lock
for one zone, which leaves stealing as the alloc path there.
Movable allocations can steal, because kcompactd can always
move those pages out of non-movable blocks later.
Over three boots allocstall_normal runs 48 to 129 against 32 to
134 on the base, and compact_stall 324 to 415 against 288 to 387.
Neither direct reclaim nor compaction stalls rise beyond
run-to-run spread.
Assisted-by: LLM
Signed-off-by: Rik van Riel <riel@surriel.com>
---
mm/page_alloc.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 9f1520506ae1d..16d3594d913a0 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -2423,8 +2423,10 @@ __rmqueue_claim(struct zone *zone, int order, int start_migratetype,
}
/*
- * Try to steal a single page from some fallback migratetype. Leave the rest of
- * the block as its current migratetype, potentially causing fragmentation.
+ * Try to steal one page from a fallback type, leaving the rest of the block
+ * unchanged and possibly fragmented. A non-movable allocation must not steal
+ * from a movable block: without converting its type, the steal would leave
+ * non-movable content under a movable type.
*/
static __always_inline struct page *
__rmqueue_steal(struct zone *zone, int order, int start_migratetype)
@@ -2444,6 +2446,19 @@ __rmqueue_steal(struct zone *zone, int order, int start_migratetype)
continue;
page = get_page_from_free_area(area, fallback_mt);
+
+ /*
+ * Do not allow non-movable allocations in movable
+ * pageblocks; that could break compaction.
+ * Non-movable allocations should claim pageblocks, instead.
+ */
+ if (!is_migrate_movable(start_migratetype) &&
+ is_migrate_movable(fallback_mt) &&
+ !page_group_by_mobility_disabled &&
+ zone_spans_pageblock(zone, page_to_pfn(page))) {
+ continue;
+ }
+
page_del_and_expand(zone, page, order, current_order, fallback_mt);
trace_mm_page_alloc_extfrag(page, order, current_order,
start_migratetype, fallback_mt);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 20+ messages in thread* [RFC PATCH 5/5] mm/page_alloc: claim blocks captured for non-movable use
2026-10-07 2:12 [RFC PATCH 0/5] mm/page_alloc: keep non-movable pages out of movable pageblocks for THP Rik van Riel
` (3 preceding siblings ...)
2026-10-07 2:12 ` [RFC PATCH 4/5] mm/page_alloc: skip movable blocks during non-movable steals Rik van Riel
@ 2026-10-07 2:12 ` Rik van Riel
4 siblings, 0 replies; 20+ messages in thread
From: Rik van Riel @ 2026-10-07 2:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Kairui Song, Qi Zheng,
Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
Baoquan He, Baolin Wang, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Mike Rapoport, linux-mm, Rik van Riel
compaction_capture() hands any merged block to the compacting task
with no type change, except it refuses a movable block to a
non-movable request below pageblock order. A non-movable request
capturing a whole movable block leaves non-movable content in a
movable-typed block.
Make a non-movable capture claim a movable block: convert it to
the request. A partial block cannot be retyped, so the refusal
below pageblock order stays; reaching the conversion means a
whole block. A movable capture steals: it takes the pages and
leaves the type alone.
Assisted-by: LLM
Signed-off-by: Rik van Riel <riel@surriel.com>
---
mm/page_alloc.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 16d3594d913a0..edb69d8a74c73 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -728,6 +728,9 @@ static inline struct capture_control *task_capc(struct zone *zone)
capc->zone == zone ? capc : NULL;
}
+static void change_pageblock_range(struct page *pageblock_page,
+ int start_order, int migratetype);
+
static inline bool
compaction_capture(struct capture_control *capc, struct page *page,
int order, int migratetype)
@@ -751,6 +754,19 @@ compaction_capture(struct capture_control *capc, struct page *page,
capc->migratetype != MIGRATE_MOVABLE)
return false;
+ /*
+ * A non-movable capture claims a movable block: convert it to
+ * the request. A movable capture steals: take the pages and
+ * leave the type alone. Partial movable blocks returned above,
+ * so reaching here means a whole block.
+ */
+ if (capc->migratetype != MIGRATE_MOVABLE &&
+ migratetype == MIGRATE_MOVABLE) {
+ change_pageblock_range(page, order, capc->migratetype);
+ /* Converted whole, so no fragmentation to report below. */
+ migratetype = capc->migratetype;
+ }
+
if (migratetype != capc->migratetype)
trace_mm_page_alloc_extfrag(page, capc->order, order,
capc->migratetype, migratetype);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 20+ messages in thread