mm/page_owner: misc cleanups
Subsystems (7)
v6 cover mm/page_owner: misc cleanups
This series collects a few cleanups for mm/page_owner.c that have been accumulated while reading through the file. There is no functional change -- the goal is to make the code easier to read and maintain.
Patch 1 consolidates three identical PageBuddy skip blocks into a single skip_buddy_pages() helper, eliminating the duplication and keeping the lockless-read comment in one place.
Patch 2 replaces the -1 magic number used for "never migrated" with a proper MR_NEVER member in enum migrate_reason, adds the corresponding "never_migrated" string in the MIGRATE_REASON trace macro, and updates the GDB page_owner script to use MR_NEVER so that lx-dump-page-owner correctly detects unmigrated pages.
Patch 3 follows up by converting the remaining 'int reason' parameters throughout the migration and hugetlb callchains to 'enum migrate_reason', making the type explicit and gaining compiler checking. The 'short last_migrate_reason' struct field in page_owner is intentionally left as 'short' since it is per-page metadata where size matters.
Patch 4 hoists the CONFIG_MEMCG guard out of print_page_owner_memcg()'s body so that the real implementation and the empty stub are two clearly separate definitions, the common kernel idiom.
Patch 5 adds a missing \n to the count_threshold debugfs attribute format string so that cat(1) output is properly terminated.
Patch 6 moves free_ts_nsec from the allocation summary line to the free section in __dump_page_owner(), grouping it with free_pid and free_tgid where it logically belongs. This also makes the dump output consistent with print_page_owner().
Patch 7 drops the redundant page_owner_ prefix from file-scoped static symbols (stack_fops, threshold_fops, etc.). Since they cannot collide across translation units, the prefix carries no information.
Patch 8 clamps the PFN advance in skip_buddy_pages() at the next MAX_ORDER_NR_PAGES boundary. The lockless buddy_order_unsafe() read can return a garbage order value if the page is concurrently allocated between the PageBuddy check and the private read, potentially causing the PFN to advance past the next bounadry whose pfn_valid() check would have caught an offline memory section. In read_page_owner(), which relies solely on boundary-aligned pfn_valid() to guard pfn_to_page(), this could lead to an unmapped mem_section access.
Patch 9 avoids two TOCTOU issues in print_page_owner_memcg() by reusing the page->memcg_data snapshot already taken via READ_ONCE at the top of the function throughout, instead of calling page_memcg_check() and PageMemcgKmem() which re-read page->memcg_data locklessly with VM_BUG_ON assertions. If the page is concurrently freed and reallocated as a THP tail or slab page between the initial guards and these later calls, those assertions can fire on CONFIG_DEBUG_VM=y builds. The OBJEXTS (slab) case is also simplified with an early return since objcg != memcg for slabs.
Ye Liu (9):
mm/page_owner: extract skip_buddy_pages() helper to unify buddy page
skipping
mm/page_owner: add MR_NEVER to enum migrate_reason and use it for
last_migrate_reason
mm: use enum migrate_reason instead of int for migration reason
parameters
mm/page_owner: hoist CONFIG_MEMCG to function level for
print_page_owner_memcg()
mm/page_owner: add missing newline to count_threshold format string mm/page_owner: move free_ts_nsec output to free section in
__dump_page_owner()
mm/page_owner: drop redundant page_owner prefix from static symbols mm/page_owner: clamp skip_buddy_pages() PFN advance at
MAX_ORDER_NR_PAGES boundary
mm/page_owner: use memcg_data snapshot to avoid TOCTOU in
print_page_owner_memcg()
include/linux/hugetlb.h | 9 ++- include/linux/migrate.h | 8 +- include/linux/migrate_mode.h | 1 + include/linux/page_owner.h | 7 +- include/trace/events/migrate.h | 11 +-- mm/hugetlb.c | 3 +- mm/migrate.c | 12 +-- mm/page_owner.c | 135 ++++++++++++++++++-------------- scripts/gdb/linux/page_owner.py | 4 +- 9 files changed, 109 insertions(+), 81 deletions(-)
---
v6:
- Patch 3: drop unnecessary 'extern' from __folio_set_owner_migrate_reason() declaration in page_owner.h; Adjust the indentation; add Reviewed-by from Lorenzo Stoakes.
- Patch 8: clarify commit message wording; add Reviewed-by from Zi Yan and Vlastimil Babka.
- Patch 9: rename patch to cover both TOCTOU fixes; also replace page_memcg_check(page) with extracting objcg from the memcg_data snapshot to fix a second TOCTOU issue; add early return for the MEMCG_DATA_OBJEXTS (slab) case.
- Link: https://lore.kernel.org/all/[email protected]/
v5:
- Place the two patches corresponding to the Close connection, patch8 and patch9, together in this part.
- Close: https://lore.kernel.org/all/[email protected]/
- Link: https://lore.kernel.org/all/[email protected]/
v4:
- Patch 2: also update scripts/gdb/linux/page_owner.py to use MR_NEVER instead of the hardcoded -1.
- https://lore.kernel.org/all/[email protected]/
v3:
- Patch 2: add MR_NEVER directly to enum migrate_reason instead of using a local MIGRATE_REASON_NONE define (Zi Yan and Vlastimil Babka).
- Patch 3 (new): convert all 'int reason' parameters in the migration callchain to 'enum migrate_reason' so the type system reflects the actual semantics.
- Patch 7: make threshold_fops a single line.
- Link: https://lore.kernel.org/all/[email protected]/
v2:
- Add cover letter (no code changes).
- Link: https://lore.kernel.org/all/[email protected]/
Signature · 1 line
2.43.0
PATCH v6 1/9 mm/page_owner: extract skip_buddy_pages() helper to unify buddy page skipping
Three places in page_owner.c duplicate the same pattern: check if a page is PageBuddy, read its order via buddy_order_unsafe(), advance the pfn past the buddy block if the order is valid, and continue.
Consolidate them into a single inline helper skip_buddy_pages(). The function returns true (skip) for any buddy page and advances @pfn past the block when the order is valid; returns false if the page is not a buddy page and should be processed normally.
The old init_pages_in_zone() variant used "order > 0" as an extra guard before advancing pfn, but the continue was unconditional and (1UL << 0) - 1 == 0, so the behaviour is identical. The comment about zone->lock is preserved in the helper's kernel-doc.
No functional change.
--- mm/page_owner.c | 52 ++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-)
mm/page_owner.c +26 -26
@@ -422,6 +422,29 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old)rcu_read_unlock (); } +/* +* Check if a page is a buddy page and advance @pfn past the entire buddy block. +* This safely reads the buddy order without the zone lock, which may cause us +* to skip less than the full buddy block, but that is acceptable for page owner +* iteration purposes. +* +* Return: true if the page was skipped (caller should continue its loop), +* false if the page is not a buddy page and should be processed normally. +*/ +static inline bool skip_buddy_pages (unsigned long *pfn,struct page *page) +{ +unsigned long order; + +if (!PageBuddy (page)) +return false; + + order =buddy_order_unsafe (page); +if (order <=MAX_PAGE_ORDER ) + *pfn += (1UL << order) -1 ; + +return true; +} +void pagetypeinfo_showmixedcount_print (struct seq_file *m,pg_data_t *pgdat,struct zone *zone) {
@@ -461,14 +484,8 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,if (page_zone (page) != zone)continue ; -if (PageBuddy (page)) { -unsigned long freepage_order; - - freepage_order =buddy_order_unsafe (page); -if (freepage_order <=MAX_PAGE_ORDER ) - pfn += (1UL << freepage_order) -1 ; +if (skip_buddy_pages (&pfn, page))continue ; - }if (PageReserved (page))continue ;
@@ -697,13 +714,8 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) } page =pfn_to_page (pfn); -if (PageBuddy (page)) { -unsigned long freepage_order =buddy_order_unsafe (page); - -if (freepage_order <=MAX_PAGE_ORDER ) - pfn += (1UL << freepage_order) -1 ; +if (skip_buddy_pages (&pfn, page))continue ; - } page_ext =page_ext_get (page);if (unlikely (!page_ext))
@@ -798,20 +810,8 @@ static void init_pages_in_zone(struct zone *zone)if (page_zone (page) != zone)continue ; -/* -* To avoid having to grab zone->lock, be a little -* careful when reading buddy page order. The only -* danger is that we skip too much and potentially miss -* some early allocated pages, which is better than -* heavy lock contention. -*/ -if (PageBuddy (page)) { -unsigned long order =buddy_order_unsafe (page); - -if (order >0 && order <=MAX_PAGE_ORDER ) - pfn += (1UL << order) -1 ; +if (skip_buddy_pages (&pfn, page))continue ; - }if (PageReserved (page))continue ;
Signature · 1 line
2.43.0
PATCH v6 2/9 mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
In reply to Ye LiuThe last_migrate_reason field uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a proper MR_NEVER member to enum migrate_reason, defining a corresponding "never_migrated" string in the MIGRATE_REASON trace macro, and updating the GDB page_owner script to use MR_NEVER instead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated.
No functional change.
--- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-)
include/linux/migrate_mode.h +1 -0
@@ -25,6 +25,7 @@ enum migrate_reason {MR_LONGTERM_PIN ,MR_DEMOTION ,MR_DAMON , +MR_NEVER ,/* page has never been migrated */ MR_TYPES };
include/trace/events/migrate.h +2 -1
@@ -23,7 +23,8 @@ EM( MR_CONTIG_RANGE, "contig_range") \ EM( MR_LONGTERM_PIN, "longterm_pin") \ EM( MR_DEMOTION, "demotion") \ -EMe (MR_DAMON ,"damon" ) + EM( MR_DAMON, "damon") \ +EMe (MR_NEVER ,"never_migrated" )/* * First define the enums in the above macros to be exported to userspace
mm/page_owner.c +4 -4
@@ -339,7 +339,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order,depot_stack_handle_t handle; handle =save_stack (gfp_mask); -__update_page_owner_handle (page, handle, order, gfp_mask,-1 , +__update_page_owner_handle (page, handle, order, gfp_mask,MR_NEVER , ts_nsec, current->pid, current->tgid, current->comm);inc_stack_record_count (handle, gfp_mask,1 << order);
@@ -596,7 +596,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,if (ret >= count) goto err; -if (page_owner->last_migrate_reason !=-1 ) { +if (page_owner->last_migrate_reason !=MR_NEVER ) { ret +=scnprintf (kbuf + ret, count - ret,"Page has been migrated, last migrate reason: %s\n" , migrate_reason_names[page_owner->last_migrate_reason]);
@@ -667,7 +667,7 @@ void __dump_page_owner(const struct page *page)stack_depot_print (handle); } -if (page_owner->last_migrate_reason !=-1 ) +if (page_owner->last_migrate_reason !=MR_NEVER )pr_alert ("page has been migrated, last migrate reason: %s\n" , migrate_reason_names[page_owner->last_migrate_reason]);page_ext_put (page_ext);
@@ -826,7 +826,7 @@ static void init_pages_in_zone(struct zone *zone)/* Found early allocated page */ __update_page_owner_handle (page, early_handle,0 ,0 , --1 ,local_clock (), current->pid, +MR_NEVER ,local_clock (), current->pid, current->tgid, current->comm); count++; ext_put_continue:
scripts/gdb/linux/page_owner.py +3 -1
@@ -34,6 +34,7 @@ class DumpPageOwner(gdb.Command): max_pfn = None p_ops = None migrate_reason_names = None + mr_never = None def __init__(self): super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT)
@@ -65,6 +66,7 @@ class DumpPageOwner(gdb.Command): self.max_pfn = int(gdb.parse_and_eval("max_pfn")) self.page_ext_size = int(gdb.parse_and_eval("page_ext_size")) self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names') + self.mr_never = int(gdb.parse_and_eval('MR_NEVER')) def page_ext_invalid(self, page_ext): if page_ext == gdb.Value(0):
@@ -138,7 +140,7 @@ class DumpPageOwner(gdb.Command): else: gdb.write('page last free stack trace:\n') stackdepot.stack_depot_print(page_owner["free_handle"]) - if page_owner['last_migrate_reason'] != -1: + if page_owner['last_migrate_reason'] != self.mr_never: gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']]) def read_page_owner(self):
Signature · 1 line
2.43.0
David Hildenbrand (Arm)
maintainerRe: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 25 lines · Ye Liu
The
last_migrate_reasonfield uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a properMR_NEVERmember to enummigrate_reason, defining a corresponding "never_migrated" string in theMIGRATE_REASONtrace macro, and updating the GDBpage_ownerscript to useMR_NEVERinstead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated.No functional change.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>--- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-)include/linux/migrate_mode.h +0
-0
include/linux/migrate_mode.h enum migrate_reason { · line 28
Quoted 4 lines
include/linux/migrate_mode.h +1
-0@@ -25,6 +25,7 @@ enum migrate_reason {MR_LONGTERM_PIN ,MR_DEMOTION ,MR_DAMON , + MR_NEVER, /* page has never been migrated */
Shouldn't this be more like
"MR_NONE" ?
As "never" is not really a "reason" ?
Signature · 3 lines
Cheers, David
Vlastimil Babka (SUSE)
maintainerRe: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 33 lines · David Hildenbrand (Arm)
Quoted 25 lines · Ye Liu
The
last_migrate_reasonfield uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a properMR_NEVERmember to enummigrate_reason, defining a corresponding "never_migrated" string in theMIGRATE_REASONtrace macro, and updating the GDBpage_ownerscript to useMR_NEVERinstead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated.No functional change.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>--- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-)include/linux/migrate_mode.h +0
-0include/linux/migrate_mode.h enum migrate_reason { · line 28
Quoted 4 lines
include/linux/migrate_mode.h +1
-0@@ -25,6 +25,7 @@ enum migrate_reason {MR_LONGTERM_PIN ,MR_DEMOTION ,MR_DAMON , + MR_NEVER, /* page has never been migrated */Shouldn't this be more like
Quoted 3 lines
"
MR_NONE" ?As "never" is not really a "reason" ?
It's not used ever as a reason that would be actually passed to migration. So I think the name is more descriptive this way.
David Hildenbrand (Arm)
maintainerRe: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 34 lines · Vlastimil Babka (SUSE)
Quoted 33 lines · David Hildenbrand (Arm)
Quoted 25 lines · Ye Liu
The
last_migrate_reasonfield uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a properMR_NEVERmember to enummigrate_reason, defining a corresponding "never_migrated" string in theMIGRATE_REASONtrace macro, and updating the GDBpage_ownerscript to useMR_NEVERinstead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated.No functional change.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>--- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-)include/linux/migrate_mode.h +0
-0include/linux/migrate_mode.h enum migrate_reason { · line 28
Quoted 4 lines
include/linux/migrate_mode.h +1
-0@@ -25,6 +25,7 @@ enum migrate_reason {MR_LONGTERM_PIN ,MR_DEMOTION ,MR_DAMON , + MR_NEVER, /* page has never been migrated */Shouldn't this be more like
Quoted 5 lines
Quoted 3 lines
"
MR_NONE" ?As "never" is not really a "reason" ?
It's not used ever as a reason that would be actually passed to migration.
Right, it's a placeholder for "there is no migrate reason because it is unset"
Quoted 1 line
So I think the name is more descriptive this way.
Not sure I agree. The usual translation of -1 -> unset is NONE or UNSET.
At least I was confused by "NEVER".
Signature · 3 lines
Cheers, David
Vlastimil Babka (SUSE)
maintainerRe: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 39 lines · David Hildenbrand (Arm)
Quoted 38 lines · Vlastimil Babka (SUSE)
Quoted 36 lines · David Hildenbrand (Arm)
Quoted 25 lines · Ye Liu
The
last_migrate_reasonfield uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a properMR_NEVERmember to enummigrate_reason, defining a corresponding "never_migrated" string in theMIGRATE_REASONtrace macro, and updating the GDBpage_ownerscript to useMR_NEVERinstead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated.No functional change.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>--- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-)include/linux/migrate_mode.h +0
-0include/linux/migrate_mode.h enum migrate_reason { · line 28
Quoted 4 lines
include/linux/migrate_mode.h +1
-0@@ -25,6 +25,7 @@ enum migrate_reason {MR_LONGTERM_PIN ,MR_DEMOTION ,MR_DAMON , + MR_NEVER, /* page has never been migrated */Shouldn't this be more like
"
MR_NONE" ?As "never" is not really a "reason" ?
Quoted 7 lines
Quoted 1 line
It's not used ever as a reason that would be actually passed to migration.
Right, it's a placeholder for "there is no migrate reason because it is unset"
Quoted 1 line
So I think the name is more descriptive this way.
Not sure I agree. The usual translation of -1 -> unset is NONE or UNSET.
(note it's no longer -1 after the patch.)
Quoted 1 line
At least I was confused by "NEVER".
I won't bikeshed this, so whatever.
Just to avoid another extra respin, please also say how the following line should change, as if we only rename MR_NEVER to MR_NONE, it will be:
+ EMe(MR_NONE, "never_migrated")
Is that ok? Note it should never be actually printed anywhere per the current code.
David Hildenbrand (Arm)
maintainerRe: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 9 lines · Vlastimil Babka (SUSE)
Quoted 8 lines · David Hildenbrand (Arm)
Quoted 2 lines · Vlastimil Babka (SUSE)
It's not used ever as a reason that would be actually passed to migration.
Right, it's a placeholder for "there is no migrate reason because it is unset"
Quoted 1 line
So I think the name is more descriptive this way.
Quoted 7 lines
Quoted 1 line
Not sure I agree. The usual translation of -1 -> unset is NONE or UNSET.
(note it's no longer -1 after the patch.)
Quoted 1 line
At least I was confused by "NEVER".
I won't bikeshed this, so whatever.
Just to be clear: if everybody here agrees that MR_NEVER is the right thing to use, fine with me.
I just stumbled over it and it caught my attention.
Review
Quoted 5 lines
Just to avoid another extra respin, please also say how the following line should change, as if we only rename
MR_NEVERtoMR_NONE, it will be:(quoted diff) +1
-0+ EMe(MR_NONE, "never_migrated")
I would probably just have use "not set".
And if "last_migrate_reason == MR_NONE" that would imply "never".
Ye Liu, feel free to keep it as is if you agree that using NEVER is better here.
Signature · 3 lines
Cheers, David
Re: [PATCH v6 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
Quoted 27 lines · David Hildenbrand (Arm)
Quoted 9 lines · Vlastimil Babka (SUSE)
Quoted 8 lines · David Hildenbrand (Arm)
Quoted 2 lines · Vlastimil Babka (SUSE)
It's not used ever as a reason that would be actually passed to migration.
Right, it's a placeholder for "there is no migrate reason because it is unset"
Quoted 1 line
So I think the name is more descriptive this way.
Quoted 7 lines
Quoted 1 line
Not sure I agree. The usual translation of -1 -> unset is NONE or UNSET.
(note it's no longer -1 after the patch.)
Quoted 1 line
At least I was confused by "NEVER".
I won't bikeshed this, so whatever.
Just to be clear: if everybody here agrees that
MR_NEVERis the right thing to use, fine with me.I just stumbled over it and it caught my attention.
Quoted 4 lines
Just to avoid another extra respin, please also say how the following line should change, as if we only rename
MR_NEVERtoMR_NONE, it will be:
Quoted 9 lines
Review
Quoted 1 line
(quoted diff) +1
-0+ EMe(MR_NONE, "never_migrated")I would probably just have use "not set".
And if "
last_migrate_reason==MR_NONE" that would imply "never".Ye Liu, feel free to keep it as is if you agree that using NEVER is better here.
Let's keep things as they are.
Signature · 2 lines
Thanks, Ye Liu
PATCH v6 3/9 mm: use enum migrate_reason instead of int for migration reason parameters
In reply to Ye LiuReplace all 'int reason' function parameters that carry migrate_reason values with the proper 'enum migrate_reason' type. This makes the intent explicit and leverages compiler type checking. The affected subsystems are:
- page_owner: __folio_set_owner_migrate_reason(), folio_set_owner_migrate_reason()
- migrate: migrate_pages(), migrate_pages_sync(), migrate_pages_batch(), migrate_folios_move(), migrate_hugetlbs(), unmap_and_move_huge_page()
- hugetlb: move_hugetlb_state(), htlb_allow_alloc_fallback()
- trace: mm_migrate_pages and mm_migrate_pages_start events
The 'short last_migrate_reason' struct field and internal helper parameter in page_owner are intentionally left as 'short' since they store per-page metadata where size matters.
No functional change.
---
Changes in v6:
- Drop unnecessary 'extern' from __folio_set_owner_migrate_reason() declaration in page_owner.h.
- Adjust the indentation.
- Link: https://lore.kernel.org/all/[email protected]/
include/linux/hugetlb.h | 9 +++++---- include/linux/migrate.h | 8 +++++--- include/linux/page_owner.h | 7 ++++--- include/trace/events/migrate.h | 8 ++++---- mm/hugetlb.c | 3 ++- mm/migrate.c | 12 ++++++------ mm/page_owner.c | 2 +- 7 files changed, 27 insertions(+), 22 deletions(-)
include/linux/hugetlb.h +5 -4
@@ -154,7 +154,8 @@ long hugetlb_unreserve_pages(struct inode *inode, long start, long end,bool folio_isolate_hugetlb (struct folio *folio,struct list_head *list);int get_hwpoison_hugetlb_folio (struct folio *folio,bool *hugetlb,bool unpoison);void folio_putback_hugetlb (struct folio *folio); -void move_hugetlb_state (struct folio *old_folio,struct folio *new_folio,int reason); +void move_hugetlb_state (struct folio *old_folio,struct folio *new_folio, +enum migrate_reason reason);void hugetlb_fix_reserve_counts (struct inode *inode);extern struct mutex *hugetlb_fault_mutex_table;u32 hugetlb_fault_mutex_hash (struct address_space *mapping,pgoff_t idx);
@@ -424,7 +425,7 @@ static inline void folio_putback_hugetlb(struct folio *folio) }static inline void move_hugetlb_state (struct folio *old_folio, -struct folio *new_folio,int reason) +struct folio *new_folio,enum migrate_reason reason) { }
@@ -956,7 +957,7 @@ static inline gfp_t htlb_modify_alloc_mask(struct hstate *h, gfp_t gfp_mask)return modified_mask; } -static inline bool htlb_allow_alloc_fallback (int reason) +static inline bool htlb_allow_alloc_fallback (enum migrate_reason reason) {bool allowed_fallback = false;
@@ -1238,7 +1239,7 @@ static inline gfp_t htlb_modify_alloc_mask(struct hstate *h, gfp_t gfp_mask)return 0 ; } -static inline bool htlb_allow_alloc_fallback (int reason) +static inline bool htlb_allow_alloc_fallback (enum migrate_reason reason) {return false; }
include/linux/migrate.h +5 -3
@@ -57,8 +57,9 @@ void putback_movable_pages(struct list_head *l);int migrate_folio (struct address_space *mapping,struct folio *dst,struct folio *src,enum migrate_mode mode);int migrate_pages (struct list_head *l,new_folio_t new,free_folio_t free, -unsigned long private,enum migrate_mode mode,int reason, -unsigned int *ret_succeeded); +unsigned long private,enum migrate_mode mode, +enum migrate_reason reason, +unsigned int *ret_succeeded);struct folio *alloc_migration_target (struct folio *src,unsigned long private);bool isolate_movable_ops_page (struct page *page,isolate_mode_t mode);bool isolate_folio_to_list (struct folio *folio,struct list_head *list);
@@ -77,7 +78,8 @@ int set_movable_ops(const struct movable_operations *ops, enum pagetype type);static inline void putback_movable_pages (struct list_head *l) {}static inline int migrate_pages (struct list_head *l,new_folio_t new,free_folio_t free,unsigned long private, -enum migrate_mode mode,int reason,unsigned int *ret_succeeded) +enum migrate_mode mode,enum migrate_reason reason, +unsigned int *ret_succeeded) {return -ENOSYS ; }static inline struct folio *alloc_migration_target (struct folio *src,unsigned long private)
include/linux/page_owner.h +4 -3
@@ -3,6 +3,7 @@#define __LINUX_PAGE_OWNER_H#include <linux/jump_label.h> +#include <linux/migrate_mode.h> #ifdef CONFIG_PAGE_OWNER extern struct static_key_false page_owner_inited;
@@ -14,7 +15,7 @@ extern void __set_page_owner(struct page *page,extern void __split_page_owner (struct page *page,int old_order,int new_order);extern void __folio_copy_owner (struct folio *newfolio,struct folio *old); -extern void __folio_set_owner_migrate_reason (struct folio *folio,int reason); +void __folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason);extern void __dump_page_owner (const struct page *page);extern void pagetypeinfo_showmixedcount_print (struct seq_file *m,pg_data_t *pgdat,struct zone *zone);
@@ -43,7 +44,7 @@ static inline void folio_copy_owner(struct folio *newfolio, struct folio *old)if (static_branch_unlikely (&page_owner_inited))__folio_copy_owner (newfolio, old); } -static inline void folio_set_owner_migrate_reason (struct folio *folio,int reason) +static inline void folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason) {if (static_branch_unlikely (&page_owner_inited))__folio_set_owner_migrate_reason (folio, reason);
@@ -68,7 +69,7 @@ static inline void split_page_owner(struct page *page, int old_order,static inline void folio_copy_owner (struct folio *newfolio,struct folio *folio) { } -static inline void folio_set_owner_migrate_reason (struct folio *folio,int reason) +static inline void folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason) { }static inline void dump_page_owner (const struct page *page)
include/trace/events/migrate.h +4 -4
@@ -52,7 +52,7 @@ TRACE_EVENT(mm_migrate_pages, TP_PROTO(unsigned long succeeded, unsigned long failed, unsigned long thp_succeeded, unsigned long thp_failed, unsigned long thp_split, unsigned long large_folio_split, - enum migrate_mode mode, int reason), + enum migrate_mode mode, enum migrate_reason reason),TP_ARGS (succeeded, failed, thp_succeeded, thp_failed, thp_split, large_folio_split, mode, reason),
@@ -65,7 +65,7 @@ TRACE_EVENT(mm_migrate_pages, __field( unsigned long, thp_split) __field( unsigned long, large_folio_split) __field( enum migrate_mode, mode) - __field( int, reason) + __field( enum migrate_reason, reason) ), TP_fast_assign(
@@ -92,13 +92,13 @@ TRACE_EVENT(mm_migrate_pages, TRACE_EVENT(mm_migrate_pages_start, - TP_PROTO(enum migrate_mode mode, int reason), + TP_PROTO(enum migrate_mode mode, enum migrate_reason reason), TP_ARGS(mode, reason), TP_STRUCT__entry( __field(enum migrate_mode, mode) - __field(int, reason) + __field(enum migrate_reason, reason) ), TP_fast_assign(
mm/hugetlb.c +2 -1
@@ -7182,7 +7182,8 @@ void folio_putback_hugetlb(struct folio *folio)folio_put (folio); } -void move_hugetlb_state (struct folio *old_folio,struct folio *new_folio,int reason) +void move_hugetlb_state (struct folio *old_folio,struct folio *new_folio, +enum migrate_reason reason) {struct hstate *h =folio_hstate (old_folio);
mm/migrate.c +6 -6
@@ -1469,7 +1469,7 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,static int unmap_and_move_huge_page (new_folio_t get_new_folio,free_folio_t put_new_folio,unsigned long private,struct folio *src,int force,enum migrate_mode mode, -int reason,struct list_head *ret) +enum migrate_reason reason,struct list_head *ret) {struct folio *dst;int rc = -EAGAIN ;
@@ -1626,7 +1626,7 @@ struct migrate_pages_stats {*/ static int migrate_hugetlbs (struct list_head *from,new_folio_t get_new_folio,free_folio_t put_new_folio,unsigned long private, -enum migrate_mode mode,int reason, +enum migrate_mode mode,enum migrate_reason reason,struct migrate_pages_stats *stats,struct list_head *ret_folios) {
@@ -1716,7 +1716,7 @@ static int migrate_hugetlbs(struct list_head *from, new_folio_t get_new_folio, static void migrate_folios_move(struct list_head *src_folios, struct list_head *dst_folios, free_folio_t put_new_folio, unsigned long private, - enum migrate_mode mode, int reason, + enum migrate_mode mode, enum migrate_reason reason, struct list_head *ret_folios, struct migrate_pages_stats *stats, int *retry, int *thp_retry, int *nr_failed,
@@ -1799,7 +1799,7 @@ static void migrate_folios_undo(struct list_head *src_folios,*/ static int migrate_pages_batch (struct list_head *from,new_folio_t get_new_folio,free_folio_t put_new_folio, -unsigned long private,enum migrate_mode mode,int reason, +unsigned long private,enum migrate_mode mode,enum migrate_reason reason,struct list_head *ret_folios,struct list_head *split_folios,struct migrate_pages_stats *stats,int nr_pass) {
@@ -2011,7 +2011,7 @@ static int migrate_pages_batch(struct list_head *from,static int migrate_pages_sync (struct list_head *from,new_folio_t get_new_folio,free_folio_t put_new_folio,unsigned long private, -enum migrate_mode mode,int reason, +enum migrate_mode mode,enum migrate_reason reason,struct list_head *ret_folios,struct list_head *split_folios,struct migrate_pages_stats *stats) {
@@ -2088,7 +2088,7 @@ static int migrate_pages_sync(struct list_head *from, new_folio_t get_new_folio,*/ int migrate_pages (struct list_head *from,new_folio_t get_new_folio,free_folio_t put_new_folio,unsigned long private, -enum migrate_mode mode,int reason,unsigned int *ret_succeeded) +enum migrate_mode mode,enum migrate_reason reason,unsigned int *ret_succeeded) {int rc, rc_gather;int nr_pages;
mm/page_owner.c +1 -1
@@ -345,7 +345,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order,inc_stack_record_count (handle, gfp_mask,1 << order); } -void __folio_set_owner_migrate_reason (struct folio *folio,int reason) +void __folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason) {struct page_ext *page_ext =page_ext_get (&folio->page);struct page_owner *page_owner;
Signature · 1 line
2.43.0
Re: [PATCH v6 3/9] mm: use enum migrate_reason instead of int for migration reason parameters
Quoted 17 lines · Ye Liu
Replace all 'int reason' function parameters that carry
migrate_reasonvalues with the proper 'enummigrate_reason' type. This makes the intent explicit and leverages compiler type checking. The affected subsystems are:-
page_owner:__folio_set_owner_migrate_reason(),folio_set_owner_migrate_reason()
- migrate:migrate_pages(),migrate_pages_sync(),migrate_pages_batch(),migrate_folios_move(),migrate_hugetlbs(),unmap_and_move_huge_page()
- hugetlb:move_hugetlb_state(),htlb_allow_alloc_fallback()
- trace:mm_migrate_pagesandmm_migrate_pages_starteventsThe 'short
last_migrate_reason' struct field and internal helper parameter inpage_ownerare intentionally left as 'short' since they store per-page metadata where size matters.
Quoted 7 lines
No functional change.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>Reviewed-by Lorenzo Stoakes <[email protected]>---
Signature · 3 lines
Cheers, David
PATCH v6 4/9 mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg()
In reply to Ye LiuThe print_page_owner_memcg() function has CONFIG_MEMCG guarding its entire body via #ifdef inside the function, which leaves a no-op { return ret; } when the config is disabled. Hoist the #ifdef to the top level so the real implementation and the empty stub are two clearly separated definitions.
No functional change.
--- mm/page_owner.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
mm/page_owner.c +8 -2
@@ -522,13 +522,13 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,seq_putc (m,'\n' ); } +#ifdef CONFIG_MEMCG /* * Looking for memcg information and print it out */ static inline int print_page_owner_memcg (char *kbuf,size_t count,int ret,struct page *page) { -#ifdef CONFIG_MEMCG unsigned long memcg_data;struct mem_cgroup *memcg;bool online;
@@ -556,10 +556,16 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, name); out_unlock:rcu_read_unlock (); -#endif /* CONFIG_MEMCG */ return ret; } +#else +static inline int print_page_owner_memcg (char *kbuf,size_t count,int ret, +struct page *page) +{ +return ret; +} +#endif static ssize_t print_page_owner(char __user *buf, size_t count, unsigned long pfn,
Signature · 1 line
2.43.0
PATCH v6 5/9 mm/page_owner: add missing newline to count_threshold format string
In reply to Ye LiuThe DEFINE_SIMPLE_ATTRIBUTE format string for page_owner_threshold_fops is missing a trailing \n. simple_attr_read() uses scnprintf() with the format string, which does not append a newline, so reading /sys/kernel/debug/page_owner_stacks/count_threshold produces output without a terminating newline. Add the missing \n to match the standard debugfs attribute convention.
--- mm/page_owner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
mm/page_owner.c +1 -1
@@ -974,7 +974,7 @@ static int page_owner_threshold_set(void *data, u64 val) }DEFINE_SIMPLE_ATTRIBUTE (page_owner_threshold_fops, &page_owner_threshold_get, - &page_owner_threshold_set,"%llu" ); + &page_owner_threshold_set,"%llu\n" );static int __init pageowner_init (void )
Signature · 1 line
2.43.0
PATCH v6 6/9 mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner()
In reply to Ye LiuThe free_ts_nsec field is a free-event timestamp, but it was printed in the allocation summary line alongside ts_nsec (allocation time). Move it to the free section where it logically belongs, together with free_pid and free_tgid. This also makes __dump_page_owner() consistent with print_page_owner(), which only prints ts_nsec in the allocation summary.
The output now groups all free-related information (pid, tgid, timestamp, stack trace) in one place.
No functional change except output formatting.
--- mm/page_owner.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
mm/page_owner.c +5 -4
@@ -653,10 +653,10 @@ void __dump_page_owner(const struct page *page)else pr_alert ("page_owner tracks the page as freed\n" ); -pr_alert ("page last allocated via order %u, migratetype %s, gfp_mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu, free_ts %llu\n" , +pr_alert ("page last allocated via order %u, migratetype %s, gfp_mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu\n" , page_owner->order, migratetype_names[mt], gfp_mask, &gfp_mask, page_owner->pid, page_owner->tgid, page_owner->comm, - page_owner->ts_nsec, page_owner->free_ts_nsec); + page_owner->ts_nsec); handle =READ_ONCE (page_owner->handle);if (!handle)
@@ -668,8 +668,9 @@ void __dump_page_owner(const struct page *page)if (!handle) {pr_alert ("page_owner free stack trace missing\n" ); }else { -pr_alert ("page last free pid %d tgid %d stack trace:\n" , - page_owner->free_pid, page_owner->free_tgid); +pr_alert ("page last free pid %d tgid %d ts %llu stack trace:\n" , + page_owner->free_pid, page_owner->free_tgid, + page_owner->free_ts_nsec);stack_depot_print (handle); }
Signature · 1 line
2.43.0
PATCH v6 7/9 mm/page_owner: drop redundant page_owner prefix from static symbols
In reply to Ye LiuAll of these symbols are file-scoped (static) in page_owner.c, so the page_owner_ prefix is pure noise. Rename them to shorter, still-clear names:
page_owner_stack_op -> stack_op page_owner_stack_open -> stack_open page_owner_stack_fops -> stack_fops
page_owner_pages_threshold -> pages_threshold
page_owner_threshold_get -> threshold_get
page_owner_threshold_set -> threshold_set
page_owner_threshold_fops -> threshold_fops
No functional change.
--- mm/page_owner.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-)
mm/page_owner.c +16 -18
@@ -894,7 +894,7 @@ static void *stack_next(struct seq_file *m, void *v, loff_t *ppos)return stack; } -static unsigned long page_owner_pages_threshold; +static unsigned long pages_threshold;static int stack_print (struct seq_file *m,void *v) {
@@ -911,7 +911,7 @@ static int stack_print(struct seq_file *m, void *v) nr_base_pages =refcount_read (&stack_record->count) -1 ;if (ctx->flags &STACK_PRINT_FLAG_PAGES && - (nr_base_pages <1 || nr_base_pages < page_owner_pages_threshold)) + (nr_base_pages <1 || nr_base_pages < pages_threshold))return 0 ;if (ctx->flags &STACK_PRINT_FLAG_STACK ) {
@@ -933,16 +933,16 @@ static void stack_stop(struct seq_file *m, void *v) { } -static const struct seq_operations page_owner_stack_op = { +static const struct seq_operations stack_op = { .start = stack_start, .next = stack_next, .stop = stack_stop, .show = stack_print }; -static int page_owner_stack_open (struct inode *inode,struct file *file) +static int stack_open (struct inode *inode,struct file *file) { -int ret =seq_open_private (file, &page_owner_stack_op, +int ret =seq_open_private (file, &stack_op,sizeof (struct stack_print_ctx ));if (!ret) {
@@ -955,28 +955,26 @@ static int page_owner_stack_open(struct inode *inode, struct file *file)return ret; } -static const struct file_operations page_owner_stack_fops = { - .open = page_owner_stack_open, +static const struct file_operations stack_fops = { + .open = stack_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release_private, }; -static int page_owner_threshold_get (void *data,u64 *val) +static int threshold_get (void *data,u64 *val) { - *val =READ_ONCE (page_owner_pages_threshold); + *val =READ_ONCE (pages_threshold);return 0 ; } -static int page_owner_threshold_set (void *data,u64 val) +static int threshold_set (void *data,u64 val) { -WRITE_ONCE (page_owner_pages_threshold, val); +WRITE_ONCE (pages_threshold, val);return 0 ; } -DEFINE_SIMPLE_ATTRIBUTE (page_owner_threshold_fops, &page_owner_threshold_get, - &page_owner_threshold_set,"%llu\n" ); - +DEFINE_SIMPLE_ATTRIBUTE (threshold_fops, &threshold_get, &threshold_set,"%llu\n" );static int __init pageowner_init (void ) {
@@ -992,17 +990,17 @@ static int __init pageowner_init(void)debugfs_create_file ("show_stacks" ,0400 , dir, (void *)(STACK_PRINT_FLAG_STACK |STACK_PRINT_FLAG_PAGES ), - &page_owner_stack_fops); + &stack_fops);debugfs_create_file ("show_handles" ,0400 , dir, (void *)(STACK_PRINT_FLAG_HANDLE |STACK_PRINT_FLAG_PAGES ), - &page_owner_stack_fops); + &stack_fops);debugfs_create_file ("show_stacks_handles" ,0400 , dir, (void *)(STACK_PRINT_FLAG_STACK |STACK_PRINT_FLAG_HANDLE ), - &page_owner_stack_fops); + &stack_fops);debugfs_create_file ("count_threshold" ,0600 , dir,NULL , - &page_owner_threshold_fops); + &threshold_fops);return 0 ; }late_initcall (pageowner_init)
Signature · 1 line
2.43.0
PATCH v6 8/9 mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary
In reply to Ye LiuThe lockless buddy_order_unsafe() read can return a garbage order value if the page is concurrently allocated between the PageBuddy check and the private read. If this bogus order is <= MAX_PAGE_ORDER, skip_buddy_pages() would arbitrarily advance the PFN, potentially jumping past a MAX_ORDER_NR_PAGES boundary whose pfn_valid() check would have caught an offline memory section.
In read_page_owner(), which relies solely on boundary-aligned pfn_valid() to guard pfn_to_page(), skipping the boundary could cause pfn_to_page() to access an unmapped mem_section.
Clamp the advance so it never crosses the next MAX_ORDER_NR_PAGES boundary. This is safe for all three callers: the pageblock-iterating ones already handle boundary transitions in their outer loops, and for read_page_owner() the worst case is one extra PageBuddy check per 1024 pages when a bogus order would otherwise push past the boundary.
--- mm/page_owner.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
mm/page_owner.c +12 -2
@@ -428,6 +428,12 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old)* to skip less than the full buddy block, but that is acceptable for page owner * iteration purposes. * +* The lockless read of buddy_order_unsafe() can also return a garbage order if +* the page is concurrently allocated and PageBuddy is cleared between the check +* and the read. Clamp the advance at the next MAX_ORDER_NR_PAGES boundary so +* that a bogus order cannot carry @pfn into an unvalidated memory section, +* which would break callers that rely on boundary-aligned pfn_valid() checks. +* * Return: true if the page was skipped (caller should continue its loop), * false if the page is not a buddy page and should be processed normally. */
@@ -439,8 +445,12 @@ static inline bool skip_buddy_pages(unsigned long *pfn, struct page *page)return false; order =buddy_order_unsafe (page); -if (order <=MAX_PAGE_ORDER ) - *pfn += (1UL << order) -1 ; +if (order <=MAX_PAGE_ORDER ) { +unsigned long new_pfn = *pfn + (1UL << order); +unsigned long boundary =ALIGN (*pfn +1 ,MAX_ORDER_NR_PAGES ); + + *pfn =min (new_pfn, boundary) -1 ; + }return true; }
Signature · 1 line
2.43.0
PATCH v6 9/9 mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
In reply to Ye Liuprint_page_owner_memcg() takes a snapshot of page->memcg_data via READ_ONCE at the top of the function and guards against tail pages and NULL memcg_data. However, it later calls two functions that re-read page->memcg_data locklessly:
1) page_memcg_check(page) — re-reads page->memcg_data;
2) PageMemcgKmem(page) — calls folio_memcg_kmem(), which re-reads
folio->memcg_data and folio->page->compound_head, wrapping both
in VM_BUG_ON assertions:
VM_BUG_ON_PGFLAGS(PageTail(&folio->page), &folio->page);
VM_BUG_ON_FOLIO(folio->memcg_data & MEMCG_DATA_OBJEXTS, folio);If the page is concurrently freed and reallocated as a THP tail page or a slab page between the initial guards and these later calls, the VM_BUG_ON assertions can fire on debug builds (CONFIG_DEBUG_VM=y), causing a kernel panic.
Fix both TOCTOU issues by using the memcg_data snapshot throughout:
- Extract objcg from the snapshot via objcg = (void *)(memcg_data &
~OBJEXTS_FLAGS_MASK) instead of calling page_memcg_check(page);
- Test (memcg_data & MEMCG_DATA_KMEM) instead of calling PageMemcgKmem(page), which is semantically equivalent:
PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data & MEMCG_DATA_KMEM.
- When memcg_data has MEMCG_DATA_OBJEXTS set, early-return after printing "Slab cache page\n" since objcg != memcg for slab pages and there is no meaningful cgroup to look up.
This avoids both TOCTOU windows and the assertions entirely.
---
Changes in v6:
- Rename patch to cover both TOCTOU fixes rather than only PageMemcgKmem().
- Also replace page_memcg_check(page) with extracting objcg from the memcg_data snapshot to fix a second TOCTOU issue.
- Add early return for the MEMCG_DATA_OBJEXTS (slab) case since objcg != memcg for slab pages and there is no cgroup to look up.
- Update commit message to cover all changes.
- Link: https://lore.kernel.org/all/[email protected]/
mm/page_owner.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
mm/page_owner.c +7 -3
@@ -540,6 +540,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, struct page *page) {unsigned long memcg_data; +struct obj_cgroup *objcg;struct mem_cgroup *memcg;bool online;char name[80 ];
@@ -549,11 +550,14 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,if (!memcg_data ||PageTail (page)) goto out_unlock; -if (memcg_data &MEMCG_DATA_OBJEXTS ) +if (memcg_data &MEMCG_DATA_OBJEXTS ) { ret +=scnprintf (kbuf + ret, count - ret,"Slab cache page\n" ); + goto out_unlock; + } - memcg =page_memcg_check (page); + objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); + memcg = objcg ?obj_cgroup_memcg (objcg) :NULL ;if (!memcg) goto out_unlock;
@@ -561,7 +565,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,cgroup_name (memcg->css.cgroup, name,sizeof (name)); ret +=scnprintf (kbuf + ret, count - ret,"Charged %sto %smemcg %s\n" , -PageMemcgKmem (page) ?"(via objcg) " :"" , + (memcg_data &MEMCG_DATA_KMEM ) ?"(via objcg) " :"" , online ?"" :"offline " , name); out_unlock:
Signature · 1 line
2.43.0
Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
Quoted 42 lines · Ye Liu
print_page_owner_memcg()takes a snapshot ofpage->memcg_dataviaREAD_ONCEat the top of the function and guards against tail pages and NULLmemcg_data. However, it later calls two functions that re-readpage->memcg_datalocklessly:1)
page_memcg_check(page)— re-readspage->memcg_data;
2) PageMemcgKmem(page) — callsfolio_memcg_kmem(), which re-readsfolio->memcg_data and folio->page->compound_head, wrapping both in VM_BUG_ON assertions: VM_BUG_ON_PGFLAGS(PageTail(&folio->page), &folio->page); VM_BUG_ON_FOLIO(folio->memcg_data & MEMCG_DATA_OBJEXTS, folio);If the page is concurrently freed and reallocated as a THP tail page or a slab page between the initial guards and these later calls, the
VM_BUG_ONassertions can fire on debug builds (CONFIG_DEBUG_VM=y), causing a kernel panic.Fix both TOCTOU issues by using the
memcg_datasnapshot throughout:
- Extract objcg from the snapshot via objcg = (void *)(memcg_data &~OBJEXTS_FLAGS_MASK) instead of calling page_memcg_check(page);- Test (
memcg_data&MEMCG_DATA_KMEM) instead of calling PageMemcgKmem(page), which is semantically equivalent:
PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data &MEMCG_DATA_KMEM.
- Whenmemcg_datahasMEMCG_DATA_OBJEXTSset, early-return after printing "Slab cache page\n" since objcg != memcg for slab pages and there is no meaningful cgroup to look up.This avoids both TOCTOU windows and the assertions entirely.
Signed-off-by Ye Liu <[email protected]>---
Changes in v6:
- Rename patch to cover both TOCTOU fixes rather than onlyPageMemcgKmem().
- Also replacepage_memcg_check(page)with extracting objcg from thememcg_datasnapshot to fix a second TOCTOU issue.
- Add early return for theMEMCG_DATA_OBJEXTS(slab) case since objcg != memcg for slab pages and there is no cgroup to look up.
- Update commit message to cover all changes.
- Link: https://lore.kernel.org/all/[email protected]/
Quoted 3 lines
mm/page_owner.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
LGTM. Thanks.
Best Regards,
Yan, Zi
Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
In reply to Ye LiuQuoted 20 lines · Ye Liu
print_page_owner_memcg()takes a snapshot ofpage->memcg_dataviaREAD_ONCEat the top of the function and guards against tail pages and NULLmemcg_data. However, it later calls two functions that re-readpage->memcg_datalocklessly:1)
page_memcg_check(page)— re-readspage->memcg_data;
2) PageMemcgKmem(page) — callsfolio_memcg_kmem(), which re-readsfolio->memcg_data and folio->page->compound_head, wrapping both in VM_BUG_ON assertions: VM_BUG_ON_PGFLAGS(PageTail(&folio->page), &folio->page); VM_BUG_ON_FOLIO(folio->memcg_data & MEMCG_DATA_OBJEXTS, folio);If the page is concurrently freed and reallocated as a THP tail page or a slab page between the initial guards and these later calls, the
VM_BUG_ONassertions can fire on debug builds (CONFIG_DEBUG_VM=y), causing a kernel panic.Fix both TOCTOU issues by using the
memcg_datasnapshot throughout:
- Extract objcg from the snapshot via objcg = (void *)(memcg_data &
Quoted 8 lines
~OBJEXTS_FLAGS_MASK) instead of calling page_memcg_check(page);- Test (
memcg_data&MEMCG_DATA_KMEM) instead of calling PageMemcgKmem(page), which is semantically equivalent:
PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data &MEMCG_DATA_KMEM.
- Whenmemcg_datahasMEMCG_DATA_OBJEXTSset, early-return after printing "Slab cache page\n" since objcg != memcg for slab pages and there is no meaningful cgroup to look up.
These points have too much detail that's already in the code. Would just mention that we opencode applicable parts of page_memcg_check() and PageMemcgKmem() using the snapshot?
Quoted 3 lines
This avoids both TOCTOU windows and the assertions entirely.
Signed-off-by Ye Liu <[email protected]>
Was all of this reported by sashiko? At least the new-in-v6 was? Then:
But it's no longer a cleanup but a fix, so probably this?
It's not fixing a new regression so I think it's fine to keep it part of this series for next release and not need to split out for mm-hotfixes.
Quoted 51 lines
---
Changes in v6:
- Rename patch to cover both TOCTOU fixes rather than onlyPageMemcgKmem().
- Also replacepage_memcg_check(page)with extracting objcg from thememcg_datasnapshot to fix a second TOCTOU issue.
- Add early return for theMEMCG_DATA_OBJEXTS(slab) case since objcg != memcg for slab pages and there is no cgroup to look up.
- Update commit message to cover all changes.
- Link: https://lore.kernel.org/all/[email protected]/mm/page_owner.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)mm/page_owner.c +7
-3@@ -540,6 +540,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, struct page *page) {unsigned long memcg_data; +struct obj_cgroup *objcg;struct mem_cgroup *memcg;bool online;char name[80 ];@@ -549,11 +550,14 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,if (!memcg_data ||PageTail (page)) goto out_unlock; -if (memcg_data &MEMCG_DATA_OBJEXTS ) +if (memcg_data &MEMCG_DATA_OBJEXTS ) { ret +=scnprintf (kbuf + ret, count - ret,"Slab cache page\n" ); + goto out_unlock; + } - memcg =page_memcg_check (page); + objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); + memcg = objcg ?obj_cgroup_memcg (objcg) :NULL ;if (!memcg) goto out_unlock;@@ -561,7 +565,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,cgroup_name (memcg->css.cgroup, name,sizeof (name)); ret +=scnprintf (kbuf + ret, count - ret,"Charged %sto %smemcg %s\n" , -PageMemcgKmem (page) ?"(via objcg) " :"" , + (memcg_data &MEMCG_DATA_KMEM ) ?"(via objcg) " :"" , online ?"" :"offline " , name); out_unlock:
Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
Quoted 30 lines · Vlastimil Babka (SUSE)
Quoted 20 lines · Ye Liu
print_page_owner_memcg()takes a snapshot ofpage->memcg_dataviaREAD_ONCEat the top of the function and guards against tail pages and NULLmemcg_data. However, it later calls two functions that re-readpage->memcg_datalocklessly:1)
page_memcg_check(page)— re-readspage->memcg_data;
2) PageMemcgKmem(page) — callsfolio_memcg_kmem(), which re-readsfolio->memcg_data and folio->page->compound_head, wrapping both in VM_BUG_ON assertions: VM_BUG_ON_PGFLAGS(PageTail(&folio->page), &folio->page); VM_BUG_ON_FOLIO(folio->memcg_data & MEMCG_DATA_OBJEXTS, folio);If the page is concurrently freed and reallocated as a THP tail page or a slab page between the initial guards and these later calls, the
VM_BUG_ONassertions can fire on debug builds (CONFIG_DEBUG_VM=y), causing a kernel panic.Fix both TOCTOU issues by using the
memcg_datasnapshot throughout:
- Extract objcg from the snapshot via objcg = (void *)(memcg_data &Quoted 8 lines
~OBJEXTS_FLAGS_MASK) instead of calling page_memcg_check(page);- Test (
memcg_data&MEMCG_DATA_KMEM) instead of calling PageMemcgKmem(page), which is semantically equivalent:
PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data &MEMCG_DATA_KMEM.
- Whenmemcg_datahasMEMCG_DATA_OBJEXTSset, early-return after printing "Slab cache page\n" since objcg != memcg for slab pages and there is no meaningful cgroup to look up.
Quoted 3 lines
These points have too much detail that's already in the code. Would just mention that we opencode applicable parts of
page_memcg_check()andPageMemcgKmem()using the snapshot?
Yes,It's a bit wordy.
Quoted 12 lines
Quoted 4 lines
This avoids both TOCTOU windows and the assertions entirely.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>Was all of this reported by sashiko? At least the new-in-v6 was? Then:
Reported-by Sashiko <[email protected]>
Quoted 8 lines
But it's no longer a cleanup but a fix, so probably this?
Fixes fcf8935832b8 ("mm/page_owner: print memcg information")It's not fixing a new regression so I think it's fine to keep it part of this series for next release and not need to split out for mm-hotfixes.
Hi Andrew,
Could you please help me revise the above? Should I send you V7?
Quoted 52 lines
Quoted 51 lines
---
Changes in v6:
- Rename patch to cover both TOCTOU fixes rather than onlyPageMemcgKmem().
- Also replacepage_memcg_check(page)with extracting objcg from thememcg_datasnapshot to fix a second TOCTOU issue.
- Add early return for theMEMCG_DATA_OBJEXTS(slab) case since objcg != memcg for slab pages and there is no cgroup to look up.
- Update commit message to cover all changes.
- Link: https://lore.kernel.org/all/[email protected]/mm/page_owner.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)mm/page_owner.c +7
-3@@ -540,6 +540,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, struct page *page) {unsigned long memcg_data; +struct obj_cgroup *objcg;struct mem_cgroup *memcg;bool online;char name[80 ];@@ -549,11 +550,14 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,if (!memcg_data ||PageTail (page)) goto out_unlock; -if (memcg_data &MEMCG_DATA_OBJEXTS ) +if (memcg_data &MEMCG_DATA_OBJEXTS ) { ret +=scnprintf (kbuf + ret, count - ret,"Slab cache page\n" ); + goto out_unlock; + } - memcg =page_memcg_check (page); + objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); + memcg = objcg ?obj_cgroup_memcg (objcg) :NULL ;if (!memcg) goto out_unlock;@@ -561,7 +565,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,cgroup_name (memcg->css.cgroup, name,sizeof (name)); ret +=scnprintf (kbuf + ret, count - ret,"Charged %sto %smemcg %s\n" , -PageMemcgKmem (page) ?"(via objcg) " :"" , + (memcg_data &MEMCG_DATA_KMEM ) ?"(via objcg) " :"" , online ?"" :"offline " , name); out_unlock:
Signature · 2 lines
Thanks, Ye Liu
Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
Quoted 21 lines · On Mon, 20 Jul 2026 10:45:56 +0800 Ye Liu <[email protected]> wrote:
Quoted 3 lines
These points have too much detail that's already in the code. Would just mention that we opencode applicable parts of
page_memcg_check()andPageMemcgKmem()using the snapshot?Yes,It's a bit wordy.
Quoted 17 lines
Quoted 4 lines
This avoids both TOCTOU windows and the assertions entirely.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>Was all of this reported by sashiko? At least the new-in-v6 was? Then:
Reported-by Sashiko <[email protected]>But it's no longer a cleanup but a fix, so probably this?
Fixes fcf8935832b8 ("mm/page_owner: print memcg information")
Quoted 6 lines
Quoted 3 lines
It's not fixing a new regression so I think it's fine to keep it part of this series for next release and not need to split out for mm-hotfixes.
Hi Andrew,
Could you please help me revise the above?
Sure, how does this look?
From: Ye Liu <[email protected]>
Subject: mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
Date: Tue, 14 Jul 2026 09:51:08 +0800
print_page_owner_memcg() reads page->memcg_data via READ_ONCE() at the start to guard against tail pages and NULL data. However, it later re-reads page->memcg_data locklessly in two places:
1: page_memcg_check(page)
2: PageMemcgKmem(page) (via folio_memcg_kmem(), which includes
VM_BUG_ON assertions for tail pages and MEMCG_DATA_OBJEXTS)
If the page is concurrently freed and reallocated as a THP tail page or slab page between these calls, the VM_BUG_ON assertions can trigger on CONFIG_DEBUG_VM=y builds, crashing the kernel.
Fix both TOCTOU issues by using the memcg_data snapshot throughout.
Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()
Quoted 23 lines · Andrew Morton
Quoted 21 lines · On Mon, 20 Jul 2026 10:45:56 +0800 Ye Liu <[email protected]> wrote:
Quoted 3 lines
These points have too much detail that's already in the code. Would just mention that we opencode applicable parts of
page_memcg_check()andPageMemcgKmem()using the snapshot?Yes,It's a bit wordy.
Quoted 17 lines
Quoted 4 lines
This avoids both TOCTOU windows and the assertions entirely.
Signed-off-by Ye Liu <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>Was all of this reported by sashiko? At least the new-in-v6 was? Then:
Reported-by Sashiko <[email protected]>But it's no longer a cleanup but a fix, so probably this?
Fixes fcf8935832b8 ("mm/page_owner: print memcg information")
Quoted 9 lines
Quoted 6 lines
Quoted 3 lines
It's not fixing a new regression so I think it's fine to keep it part of this series for next release and not need to split out for mm-hotfixes.
Hi Andrew,
Could you please help me revise the above?
Sure, how does this look?
Looks great, thank you!
Quoted 32 lines
From: Ye Liu <[email protected]>
Subject:mm/page_owner: usememcg_datasnapshot to avoid TOCTOU inprint_page_owner_memcg()
Date: Tue, 14 Jul 2026 09:51:08 +0800
print_page_owner_memcg()readspage->memcg_dataviaREAD_ONCE()at the start to guard against tail pages and NULL data. However, it later re-readspage->memcg_datalocklessly in two places:1:
page_memcg_check(page)2: PageMemcgKmem(page) (via
folio_memcg_kmem(), which includes
VM_BUG_ONassertions for tail pages andMEMCG_DATA_OBJEXTS)If the page is concurrently freed and reallocated as a THP tail page or slab page between these calls, the
VM_BUG_ONassertions can trigger onCONFIG_DEBUG_VM=ybuilds, crashing the kernel.Fix both TOCTOU issues by using the
memcg_datasnapshot throughout.Fixes fcf8935832b8 ("mm/page_owner: print memcg information")Signed-off-by Ye Liu <[email protected]>Reviewed-by Zi Yan <[email protected]>Reviewed-by Vlastimil Babka (SUSE) <[email protected]>Cc Brendan Jackman <[email protected]>Cc Johannes Weiner <[email protected]>Cc Lorenzo Stoakes <[email protected]>Cc Michal Hocko <[email protected]>Cc Suren Baghdasaryan <[email protected]>Cc David Hildenbrand (Arm) <[email protected]>Cc <[email protected]>
Signature · 2 lines
Thanks, Ye Liu
Andrew Morton
maintainerQuoted 3 lines · On Tue, 14 Jul 2026 09:50:59 +0800 Ye Liu <[email protected]> wrote:
This series collects a few cleanups for
mm/page_owner.cthat have been accumulated while reading through the file. There is no functional change -- the goal is to make the code easier to read and maintain.
Thanks, I updated mm.git's mm-unstable branch to this version of the patchset.
Quoted 11 lines
v6:
- Patch 3: drop unnecessary 'extern' from__folio_set_owner_migrate_reason()declaration inpage_owner.h; Adjust the indentation; add Reviewed-by from Lorenzo Stoakes.
- Patch 8: clarify commit message wording; add Reviewed-by from Zi Yan and Vlastimil Babka.
- Patch 9: rename patch to cover both TOCTOU fixes; also replacepage_memcg_check(page)with extracting objcg from thememcg_datasnapshot to fix a second TOCTOU issue; add early return for theMEMCG_DATA_OBJEXTS(slab) case.
- Link: https://lore.kernel.org/all/[email protected]/
Here's how v6 altered mm.git:
include/linux/hugetlb.h | 4 ++-- include/linux/migrate.h | 6 +++--- include/linux/page_owner.h | 2 +- mm/page_owner.c | 8 ++++++-- 4 files changed, 12 insertions(+), 8 deletions(-)
include/linux/hugetlb.h renamed from include/linux/hugetlb.h~b +2 -2
@@ -155,7 +155,7 @@ bool folio_isolate_hugetlb(struct folioint get_hwpoison_hugetlb_folio (struct folio *folio,bool *hugetlb,bool unpoison);void folio_putback_hugetlb (struct folio *folio);void move_hugetlb_state (struct folio *old_folio,struct folio *new_folio, -enum migrate_reason reason); +enum migrate_reason reason);void hugetlb_fix_reserve_counts (struct inode *inode);extern struct mutex *hugetlb_fault_mutex_table;u32 hugetlb_fault_mutex_hash (struct address_space *mapping,pgoff_t idx);
@@ -425,7 +425,7 @@ static inline void folio_putback_hugetlb }static inline void move_hugetlb_state (struct folio *old_folio, -struct folio *new_folio,enum migrate_reason reason) +struct folio *new_folio,enum migrate_reason reason) { }
include/linux/migrate.h renamed from include/linux/migrate.h~b +3 -3
@@ -57,9 +57,9 @@ void putback_movable_pages(struct list_hint migrate_folio (struct address_space *mapping,struct folio *dst,struct folio *src,enum migrate_mode mode);int migrate_pages (struct list_head *l,new_folio_t new,free_folio_t free, -unsigned long private,enum migrate_mode mode, -enum migrate_reason reason, -unsigned int *ret_succeeded); +unsigned long private,enum migrate_mode mode, +enum migrate_reason reason, +unsigned int *ret_succeeded);struct folio *alloc_migration_target (struct folio *src,unsigned long private);bool isolate_movable_ops_page (struct page *page,isolate_mode_t mode);bool isolate_folio_to_list (struct folio *folio,struct list_head *list);
include/linux/page_owner.h renamed from include/linux/page_owner.h~b +1 -1
@@ -15,7 +15,7 @@ extern void __set_page_owner(struct pageextern void __split_page_owner (struct page *page,int old_order,int new_order);extern void __folio_copy_owner (struct folio *newfolio,struct folio *old); -extern void __folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason); +void __folio_set_owner_migrate_reason (struct folio *folio,enum migrate_reason reason);extern void __dump_page_owner (const struct page *page);extern void pagetypeinfo_showmixedcount_print (struct seq_file *m,pg_data_t *pgdat,struct zone *zone);
mm/page_owner.c renamed from mm/page_owner.c~b +6 -2
@@ -540,6 +540,7 @@ static inline int print_page_owner_memcg struct page *page) {unsigned long memcg_data; +struct obj_cgroup *objcg;struct mem_cgroup *memcg;bool online;char name[80 ];
@@ -549,11 +550,14 @@ static inline int print_page_owner_memcgif (!memcg_data ||PageTail (page)) goto out_unlock; -if (memcg_data &MEMCG_DATA_OBJEXTS ) +if (memcg_data &MEMCG_DATA_OBJEXTS ) { ret +=scnprintf (kbuf + ret, count - ret,"Slab cache page\n" ); + goto out_unlock; + } - memcg =page_memcg_check (page); + objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); + memcg = objcg ?obj_cgroup_memcg (objcg) :NULL ;if (!memcg) goto out_unlock;
_