* [PATCH v5 1/9] mm/page_owner: extract skip_buddy_pages() helper to unify buddy page skipping
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason Ye Liu
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_owner.c | 52 ++++++++++++++++++++++++-------------------------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 2dddcb6510aa..342549891a8d 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
2026-07-01 6:10 ` [PATCH v5 1/9] mm/page_owner: extract skip_buddy_pages() helper to unify buddy page skipping Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 3/9] mm: use enum migrate_reason instead of int for migration reason parameters Ye Liu
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Steven Rostedt,
Masami Hiramatsu, Vlastimil Babka, Jan Kiszka, Kieran Bingham
Cc: Ye Liu, Zi Yan, Matthew Brost, Joshua Hahn, Rakie Kim,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Mathieu Desnoyers, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, linux-mm, linux-kernel,
linux-trace-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
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(-)
diff --git a/include/linux/migrate_mode.h b/include/linux/migrate_mode.h
index 265c4328b36a..05102d4d2490 100644
--- a/include/linux/migrate_mode.h
+++ b/include/linux/migrate_mode.h
@@ -25,6 +25,7 @@ enum migrate_reason {
MR_LONGTERM_PIN,
MR_DEMOTION,
MR_DAMON,
+ MR_NEVER, /* page has never been migrated */
MR_TYPES
};
diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h
index cd01dd7b3640..11bc0aa14c7e 100644
--- a/include/trace/events/migrate.h
+++ b/include/trace/events/migrate.h
@@ -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
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 342549891a8d..c2f43ab860eb 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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:
diff --git a/scripts/gdb/linux/page_owner.py b/scripts/gdb/linux/page_owner.py
index 8e713a09cfe7..eeabaeed438b 100644
--- a/scripts/gdb/linux/page_owner.py
+++ b/scripts/gdb/linux/page_owner.py
@@ -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):
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 3/9] mm: use enum migrate_reason instead of int for migration reason parameters
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
2026-07-01 6:10 ` [PATCH v5 1/9] mm/page_owner: extract skip_buddy_pages() helper to unify buddy page skipping Ye Liu
2026-07-01 6:10 ` [PATCH v5 2/9] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 10:23 ` Lorenzo Stoakes
2026-07-01 6:10 ` [PATCH v5 4/9] mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg() Ye Liu
` (5 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Andrew Morton, David Hildenbrand,
Steven Rostedt, Masami Hiramatsu, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Matthew Brost, Joshua Hahn, Rakie Kim,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Lorenzo Stoakes, Liam R. Howlett, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Mathieu Desnoyers,
Brendan Jackman, Johannes Weiner, linux-mm, linux-kernel,
linux-trace-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
include/linux/hugetlb.h | 9 +++++----
include/linux/migrate.h | 6 ++++--
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, 26 insertions(+), 21 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 2abaf99321e9..fa828232dfcc 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -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;
}
diff --git a/include/linux/migrate.h b/include/linux/migrate.h
index d5af2b7f577b..1f83924615d6 100644
--- a/include/linux/migrate.h
+++ b/include/linux/migrate.h
@@ -57,7 +57,8 @@ 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 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);
@@ -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)
diff --git a/include/linux/page_owner.h b/include/linux/page_owner.h
index 3328357f6dba..9fe51dfccf26 100644
--- a/include/linux/page_owner.h
+++ b/include/linux/page_owner.h
@@ -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);
+extern 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)
diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h
index 11bc0aa14c7e..15ee2ef201b5 100644
--- a/include/trace/events/migrate.h
+++ b/include/trace/events/migrate.h
@@ -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(
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835..17732d1fdc5e 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -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);
diff --git a/mm/migrate.c b/mm/migrate.c
index d9b23909d716..49e10feeb094 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -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;
diff --git a/mm/page_owner.c b/mm/page_owner.c
index c2f43ab860eb..4e352941a6e2 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v5 3/9] mm: use enum migrate_reason instead of int for migration reason parameters
2026-07-01 6:10 ` [PATCH v5 3/9] mm: use enum migrate_reason instead of int for migration reason parameters Ye Liu
@ 2026-07-01 10:23 ` Lorenzo Stoakes
0 siblings, 0 replies; 13+ messages in thread
From: Lorenzo Stoakes @ 2026-07-01 10:23 UTC (permalink / raw)
To: Ye Liu
Cc: Muchun Song, Oscar Salvador, Andrew Morton, David Hildenbrand,
Steven Rostedt, Masami Hiramatsu, Vlastimil Babka, Zi Yan,
Matthew Brost, Joshua Hahn, Rakie Kim, Byungchul Park,
Gregory Price, Ying Huang, Alistair Popple, Liam R. Howlett,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Mathieu Desnoyers, Brendan Jackman, Johannes Weiner, linux-mm,
linux-kernel, linux-trace-kernel
On Wed, Jul 01, 2026 at 02:10:46PM +0800, Ye Liu wrote:
> 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.
Based on my own personal experience, it's ok to be short ;)
>
> No functional change.
>
> Signed-off-by: Ye Liu <ye.liu@linux.dev>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
1 nit below but otherwise LGTM so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> include/linux/hugetlb.h | 9 +++++----
> include/linux/migrate.h | 6 ++++--
> 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, 26 insertions(+), 21 deletions(-)
>
> diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
> index 2abaf99321e9..fa828232dfcc 100644
> --- a/include/linux/hugetlb.h
> +++ b/include/linux/hugetlb.h
> @@ -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;
> }
> diff --git a/include/linux/migrate.h b/include/linux/migrate.h
> index d5af2b7f577b..1f83924615d6 100644
> --- a/include/linux/migrate.h
> +++ b/include/linux/migrate.h
> @@ -57,7 +57,8 @@ 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 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);
> @@ -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)
> diff --git a/include/linux/page_owner.h b/include/linux/page_owner.h
> index 3328357f6dba..9fe51dfccf26 100644
> --- a/include/linux/page_owner.h
> +++ b/include/linux/page_owner.h
> @@ -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);
> +extern void __folio_set_owner_migrate_reason(struct folio *folio, enum migrate_reason reason);
NIT: We drop externs when we change them as a rule, the extern is unnecessary.
> 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)
> diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h
> index 11bc0aa14c7e..15ee2ef201b5 100644
> --- a/include/trace/events/migrate.h
> +++ b/include/trace/events/migrate.h
> @@ -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(
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 571212b80835..17732d1fdc5e 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -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);
>
> diff --git a/mm/migrate.c b/mm/migrate.c
> index d9b23909d716..49e10feeb094 100644
> --- a/mm/migrate.c
> +++ b/mm/migrate.c
> @@ -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;
> diff --git a/mm/page_owner.c b/mm/page_owner.c
> index c2f43ab860eb..4e352941a6e2 100644
> --- a/mm/page_owner.c
> +++ b/mm/page_owner.c
> @@ -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;
> --
> 2.43.0
>
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5 4/9] mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg()
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (2 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 3/9] mm: use enum migrate_reason instead of int for migration reason parameters Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 5/9] mm/page_owner: add missing newline to count_threshold format string Ye Liu
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_owner.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 4e352941a6e2..fe2bf2274d8a 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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,
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 5/9] mm/page_owner: add missing newline to count_threshold format string
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (3 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 4/9] mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg() Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 6/9] mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner() Ye Liu
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_owner.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index fe2bf2274d8a..7520718f63f1 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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)
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 6/9] mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner()
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (4 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 5/9] mm/page_owner: add missing newline to count_threshold format string Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 7/9] mm/page_owner: drop redundant page_owner prefix from static symbols Ye Liu
` (2 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Acked-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_owner.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 7520718f63f1..84eb44459478 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 7/9] mm/page_owner: drop redundant page_owner prefix from static symbols
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (5 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 6/9] mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner() Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:10 ` [PATCH v5 8/9] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary Ye Liu
2026-07-01 6:10 ` [PATCH v5 9/9] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON Ye Liu
8 siblings, 0 replies; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Zi Yan, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, linux-mm, linux-kernel
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.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
Acked-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
---
mm/page_owner.c | 34 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 84eb44459478..46a933f9c229 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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)
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 8/9] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (6 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 7/9] mm/page_owner: drop redundant page_owner prefix from static symbols Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:34 ` Vlastimil Babka (SUSE)
2026-07-01 6:10 ` [PATCH v5 9/9] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON Ye Liu
8 siblings, 1 reply; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-kernel
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 for a huge buddy block straddling the boundary.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
---
mm/page_owner.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 46a933f9c229..2e3880053a34 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -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;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v5 8/9] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary
2026-07-01 6:10 ` [PATCH v5 8/9] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary Ye Liu
@ 2026-07-01 6:34 ` Vlastimil Babka (SUSE)
0 siblings, 0 replies; 13+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-01 6:34 UTC (permalink / raw)
To: Ye Liu, Andrew Morton
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-kernel
On 7/1/26 08:10, Ye Liu wrote:
> 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 for a huge buddy block straddling the boundary.
I don't see how a huge buddy block can straddle the boundary, as the largest
buddy block order is MAX_ORDER?
> Signed-off-by: Ye Liu <ye.liu@linux.dev>
Other than that, LGTM
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/page_owner.c | 14 ++++++++++++--
> 1 file changed, 12 insertions(+), 2 deletions(-)
>
> diff --git a/mm/page_owner.c b/mm/page_owner.c
> index 46a933f9c229..2e3880053a34 100644
> --- a/mm/page_owner.c
> +++ b/mm/page_owner.c
> @@ -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;
> }
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5 9/9] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON
2026-07-01 6:10 [PATCH v5 0/9] mm/page_owner: misc cleanups Ye Liu
` (7 preceding siblings ...)
2026-07-01 6:10 ` [PATCH v5 8/9] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary Ye Liu
@ 2026-07-01 6:10 ` Ye Liu
2026-07-01 6:49 ` Vlastimil Babka (SUSE)
8 siblings, 1 reply; 13+ messages in thread
From: Ye Liu @ 2026-07-01 6:10 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka
Cc: Ye Liu, Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-kernel
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, at the end it calls PageMemcgKmem(page)
which internally calls folio_memcg_kmem() — and that function re-reads
folio->memcg_data and page->compound_head locklessly, 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 this final call, the
VM_BUG_ON assertions can fire on debug builds (CONFIG_DEBUG_VM=y),
causing a kernel panic.
Fix by reusing the memcg_data snapshot already taken at function entry
instead of calling PageMemcgKmem(), which is semantically equivalent:
PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data & MEMCG_DATA_KMEM.
This avoids both the TOCTOU window and the assertions entirely.
Signed-off-by: Ye Liu <ye.liu@linux.dev>
---
mm/page_owner.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 2e3880053a34..efbf67d54ee2 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -561,7 +561,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:
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v5 9/9] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON
2026-07-01 6:10 ` [PATCH v5 9/9] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON Ye Liu
@ 2026-07-01 6:49 ` Vlastimil Babka (SUSE)
0 siblings, 0 replies; 13+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-07-01 6:49 UTC (permalink / raw)
To: Ye Liu, Andrew Morton
Cc: Suren Baghdasaryan, Michal Hocko, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-kernel
On 7/1/26 08:10, Ye Liu wrote:
> 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, at the end it calls PageMemcgKmem(page)
> which internally calls folio_memcg_kmem() — and that function re-reads
> folio->memcg_data and page->compound_head locklessly, 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 this final call, the
> VM_BUG_ON assertions can fire on debug builds (CONFIG_DEBUG_VM=y),
> causing a kernel panic.
>
> Fix by reusing the memcg_data snapshot already taken at function entry
> instead of calling PageMemcgKmem(), which is semantically equivalent:
> PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data & MEMCG_DATA_KMEM.
> This avoids both the TOCTOU window and the assertions entirely.
>
> Signed-off-by: Ye Liu <ye.liu@linux.dev>
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/page_owner.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/mm/page_owner.c b/mm/page_owner.c
> index 2e3880053a34..efbf67d54ee2 100644
> --- a/mm/page_owner.c
> +++ b/mm/page_owner.c
> @@ -561,7 +561,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:
^ permalink raw reply [flat|nested] 13+ messages in thread