Skip to contentSkip to conversation

mm/page_owner: misc cleanups

PATCH Applied Ye Liu 5 participants 23 messages Jul 14 – Jul 20

Subsystems (7)
lore
Share & tools
lore

Ye Liu

author
#0 lore raw

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
author
#1 lore raw

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
index 2dddcb6510aa..342549891a8d 100644
@@ -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
author
#2 lore raw

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 Liu

The 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
index 265c4328b36a..05102d4d2490 100644
@@ -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
index cd01dd7b3640..11bc0aa14c7e 100644
@@ -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
index 342549891a8d..c2f43ab860eb 100644
@@ -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
index 8e713a09cfe7..eeabaeed438b 100644
@@ -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
#3 lore raw

Re: [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_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 +0 -0
index 265c4328b36a..05102d4d2490 100644

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
#4 lore raw

Re: [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_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 +0 -0
index 265c4328b36a..05102d4d2490 100644

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

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.

#5 lore raw

Re: [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_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 +0 -0
index 265c4328b36a..05102d4d2490 100644

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

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
#6 lore raw

Re: [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_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 +0 -0
index 265c4328b36a..05102d4d2490 100644

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" ?

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.

#7 lore raw

Re: [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_NEVER to MR_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
author
#8 lore raw

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_NEVER is 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_NEVER to MR_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
author
#9 lore raw

PATCH v6 3/9 mm: use enum migrate_reason instead of int for migration reason parameters

In reply to Ye Liu

Replace 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
index 2abaf99321e9..d6967fc94a49 100644
@@ -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
index d5af2b7f577b..78424b3824c2 100644
@@ -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
index 3328357f6dba..8188ddc5c412 100644
@@ -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
index 11bc0aa14c7e..15ee2ef201b5 100644
@@ -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
index 571212b80835..17732d1fdc5e 100644
@@ -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
index d9b23909d716..49e10feeb094 100644
@@ -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
index c2f43ab860eb..4e352941a6e2 100644
@@ -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
maintainer Acked-by on v6 3/9
#10 lore raw

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_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.

Quoted 7 lines

No functional change.

---

Signature · 3 lines
Cheers,

David
author
#11 lore raw

PATCH v6 4/9 mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg()

In reply to Ye Liu

The 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
index 4e352941a6e2..fe2bf2274d8a 100644
@@ -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
author
#12 lore raw

PATCH v6 5/9 mm/page_owner: add missing newline to count_threshold format string

In reply to Ye Liu

The 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
index fe2bf2274d8a..7520718f63f1 100644
@@ -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
author
#13 lore raw

PATCH v6 6/9 mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner()

In reply to Ye Liu

The 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
index 7520718f63f1..84eb44459478 100644
@@ -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
author
#14 lore raw

PATCH v6 7/9 mm/page_owner: drop redundant page_owner prefix from static symbols

In reply to Ye Liu

All 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
index 84eb44459478..46a933f9c229 100644
@@ -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
author
#15 lore raw

PATCH v6 8/9 mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary

In reply to Ye Liu

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. 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
index 46a933f9c229..2e3880053a34 100644
@@ -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
author
#16 lore raw

PATCH v6 9/9 mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()

In reply to Ye Liu

print_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
index 2e3880053a34..e18512a49e38 100644
@@ -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
reviewer Reviewed-by on v6 9/9
#17 lore raw

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 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]/

Quoted 3 lines
 mm/page_owner.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

LGTM. Thanks.

Best Regards,
Yan, Zi

maintainer Reviewed-by on v6 9/9
#18 lore raw

Re: [PATCH v6 9/9] mm/page_owner: use memcg_data snapshot to avoid TOCTOU in print_page_owner_memcg()

In reply to Ye Liu
Quoted 20 lines · Ye Liu

print_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 &

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.
- 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.

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.

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?

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.

Quoted 51 lines

---
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
index 2e3880053a34..e18512a49e38 100644
@@ -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:
author
#19 lore raw

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 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 &

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.
- 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.

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() and PageMemcgKmem() using the snapshot?

Yes,It's a bit wordy.

Quoted 12 lines
Quoted 4 lines

This avoids both TOCTOU windows and the assertions entirely.

Was all of this reported by sashiko? At least the new-in-v6 was? Then:

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 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
index 2e3880053a34..e18512a49e38 100644
@@ -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
maintainer Reviewed-by Zi Yan Reviewed-by Vlastimil Babka (SUSE)
#20 lore raw

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() and PageMemcgKmem() using the snapshot?

Yes,It's a bit wordy.

Quoted 17 lines
Quoted 4 lines

This avoids both TOCTOU windows and the assertions entirely.

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?

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.

Fixes fcf8935832b8 ("mm/page_owner: print memcg information")
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]>
author
#21 lore raw

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() and PageMemcgKmem() using the snapshot?

Yes,It's a bit wordy.

Quoted 17 lines
Quoted 4 lines

This avoids both TOCTOU windows and the assertions entirely.

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?

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: 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.

Fixes fcf8935832b8 ("mm/page_owner: print memcg information")
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]>
Signature · 2 lines
Thanks,
Ye Liu
maintainer
#22 lore raw
In reply to Ye Liu
Quoted 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.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.

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 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]/

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 folio
 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,
-			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_h
 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,
-		  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 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, 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_memcg
 	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;
 

_