* [PATCH RFC 1/9] mm: add config option SWAP_PAGE_OWNER
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 2/9] mm/page_owner: add parameter option 'page_owner=on,swap' Mauricio Faria de Oliveira
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add config option for page_owner support for pages in swap space.
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/Kconfig.debug | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index 7638d75b27db..60bc0b6c3d69 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -119,6 +119,15 @@ config PAGE_OWNER
If unsure, say N.
+config SWAP_PAGE_OWNER
+ bool "Track page owner in swap space (EXPERIMENTAL)"
+ depends on PAGE_OWNER
+ help
+ This also keeps track of what call chain is the owner of a page
+ in swap space, maintaining the initial call chain over swap-out
+ and swap-in (otherwise lost at swap-out and reset at swap-in).
+ You should pass the "page_owner=on,swap" parameter to enable it.
+
config PAGE_TABLE_CHECK
bool "Check for invalid mappings in user page tables"
depends on ARCH_SUPPORTS_PAGE_TABLE_CHECK
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 2/9] mm/page_owner: add parameter option 'page_owner=on,swap'
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 1/9] mm: add config option SWAP_PAGE_OWNER Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 3/9] mm/page_owner: add 'struct swap_page_owner' and helpers Mauricio Faria de Oliveira
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
CONFIG_SWAP_PAGE_OWNER will introduce a behavior change in page_owner on
systems with swap space. Thus, add the 'swap' option to the 'page_owner'
parameter to enable it ('page_owner=on,swap').
Currently, the allocation stack trace of a page is lost at swap-out (the
page is freed) and reset at swap-in (a new page is allocated).
With the new feature, if this option is enabled, the (initial) allocation
stack trace of a page is maintained over swap-out and swap-in, and the
refcounts for the initial and swap-in allocation stack traces are adjusted.
(Note: 'swap_page_owner_enabled' is parsed before 'page_owner_enabled', so
'page_owner=off,swap' could set the former but not the latter, but this is
a nop as then init_page_owner() returns early and doesn't enable anything.)
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/page_owner.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index a70245684206..a27958872b07 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -57,6 +57,11 @@ struct stack_print_ctx {
static bool page_owner_enabled __initdata;
DEFINE_STATIC_KEY_FALSE(page_owner_inited);
+#ifdef CONFIG_SWAP_PAGE_OWNER
+static bool swap_page_owner_enabled __initdata;
+DEFINE_STATIC_KEY_FALSE(swap_page_owner_inited);
+#endif
+
static depot_stack_handle_t dummy_handle;
static depot_stack_handle_t failure_handle;
static depot_stack_handle_t early_handle;
@@ -81,7 +86,18 @@ static inline void unset_current_in_page_owner(void)
static int __init early_page_owner_param(char *buf)
{
- int ret = kstrtobool(buf, &page_owner_enabled);
+ int ret;
+
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ char *swap = strchr(buf, ',');
+
+ if (swap) {
+ *swap++ = '\0';
+ if (!strcmp(swap, "swap"))
+ swap_page_owner_enabled = true;
+ }
+#endif
+ ret = kstrtobool(buf, &page_owner_enabled);
if (page_owner_enabled)
stack_depot_request_early_init();
@@ -138,6 +154,10 @@ static __init void init_page_owner(void)
dummy_stack.next = &failure_stack;
stack_list = &dummy_stack;
static_branch_enable(&page_owner_inited);
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ if (swap_page_owner_enabled)
+ static_branch_enable(&swap_page_owner_inited);
+#endif
}
struct page_ext_operations page_owner_ops = {
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 3/9] mm/page_owner: add 'struct swap_page_owner' and helpers
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 1/9] mm: add config option SWAP_PAGE_OWNER Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 2/9] mm/page_owner: add parameter option 'page_owner=on,swap' Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 4/9] mm/page_owner: add 'xarray swap_page_owners' " Mauricio Faria de Oliveira
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add 'struct swap_page_owner' to store the (initial) allocation stack trace
and some attributes, plus helpers to allocate, free, and copy it to/from
'struct page_owner'.
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/page_owner.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index a27958872b07..19ccbda1c2a4 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -60,6 +60,14 @@ DEFINE_STATIC_KEY_FALSE(page_owner_inited);
#ifdef CONFIG_SWAP_PAGE_OWNER
static bool swap_page_owner_enabled __initdata;
DEFINE_STATIC_KEY_FALSE(swap_page_owner_inited);
+
+struct swap_page_owner {
+ depot_stack_handle_t handle;
+ u64 ts_nsec;
+ char comm[TASK_COMM_LEN];
+ pid_t pid;
+ pid_t tgid;
+};
#endif
static depot_stack_handle_t dummy_handle;
@@ -442,6 +450,38 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old)
rcu_read_unlock();
}
+#ifdef CONFIG_SWAP_PAGE_OWNER
+static void *alloc_swap_page_owner(void)
+{
+ return kmalloc(sizeof(struct swap_page_owner), GFP_KERNEL);
+}
+
+static void free_swap_page_owner(void *spo)
+{
+ kfree(spo);
+}
+
+static void copy_to_swap_page_owner(struct swap_page_owner *spo,
+ struct page_owner *page_owner)
+{
+ spo->handle = page_owner->handle;
+ spo->ts_nsec = page_owner->ts_nsec;
+ spo->pid = page_owner->pid;
+ spo->tgid = page_owner->tgid;
+ strscpy(spo->comm, page_owner->comm, sizeof(page_owner->comm));
+}
+
+static void copy_from_swap_page_owner(struct page_owner *page_owner,
+ struct swap_page_owner *spo)
+{
+ page_owner->handle = spo->handle;
+ page_owner->ts_nsec = spo->ts_nsec;
+ page_owner->pid = spo->pid;
+ page_owner->tgid = spo->tgid;
+ strscpy(page_owner->comm, spo->comm, sizeof(page_owner->comm));
+}
+#endif
+
void pagetypeinfo_showmixedcount_print(struct seq_file *m,
pg_data_t *pgdat, struct zone *zone)
{
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 4/9] mm/page_owner: add 'xarray swap_page_owners' and helpers
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (2 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 3/9] mm/page_owner: add 'struct swap_page_owner' and helpers Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 5/9] mm/page_owner: add swap hooks Mauricio Faria de Oliveira
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add 'xarray swap_page_owners' to index 'struct swap_page_owner' objects
by key 'swp_entry_t', and helpers to store, load, and erase.
This is used to track the allocation stack trace of pages in swap space.
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/page_owner.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 19ccbda1c2a4..5cd7de1f8023 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -68,6 +68,7 @@ struct swap_page_owner {
pid_t pid;
pid_t tgid;
};
+static DEFINE_XARRAY(swap_page_owners);
#endif
static depot_stack_handle_t dummy_handle;
@@ -461,6 +462,50 @@ static void free_swap_page_owner(void *spo)
kfree(spo);
}
+static int store_swap_page_owner(struct swap_page_owner *spo, swp_entry_t entry)
+{
+ /* lookup the swap entry.val from the page */
+ void *ret = xa_store(&swap_page_owners, entry.val, spo, GFP_KERNEL);
+
+ if (WARN(xa_is_err(ret),
+ "page_owner: swap: swp_entry not stored (%lu)\n", entry.val)) {
+ free_swap_page_owner(spo);
+ return xa_err(ret);
+ } else if (ret) {
+ /* Entry is being replaced, free the old entry */
+ free_swap_page_owner(ret);
+ }
+
+ return 0;
+}
+
+static void *load_swap_page_owner(swp_entry_t entry)
+{
+ void *spo = xa_load(&swap_page_owners, entry.val);
+
+ if (WARN(!spo,
+ "page_owner: swap: swp_entry not loaded (%lu)\n", entry.val))
+ return NULL;
+
+ return spo;
+}
+
+static void erase_swap_page_owner(swp_entry_t entry, bool lock)
+{
+ void *spo;
+
+ if (lock)
+ spo = xa_erase(&swap_page_owners, entry.val);
+ else
+ spo = __xa_erase(&swap_page_owners, entry.val);
+
+ if (WARN(!spo,
+ "page_owner: swap: swp_entry not erased (%lu)\n", entry.val))
+ return;
+
+ free_swap_page_owner(spo);
+}
+
static void copy_to_swap_page_owner(struct swap_page_owner *spo,
struct page_owner *page_owner)
{
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 5/9] mm/page_owner: add swap hooks
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (3 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 4/9] mm/page_owner: add 'xarray swap_page_owners' " Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 6/9] mm/page_owner: report '(swapped)' pages in debugfs file 'page_owner' Mauricio Faria de Oliveira
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add the swap hooks 'prepare_to_swap()' (before swap-out and page-free),
'swap_restore()' (after page-alloc and swap-in), and 'swap_invalidate()'
for page and area.
The first two hooks are the core of the new functionality. They store
the (initial) allocation stack trace at swap-out and load it at swap-in,
in order to 'maintain' the allocation stack trace over swap-out/swap-in.
The refcounts for the initial allocation and the allocation at swap-in
are adjusted/fixed-up (incremented and decremented, respectively), as
the initial allocation is decremented at swap-out (page free) later, and
the swap-in allocation is incremented at swap-in (page alloc) earlier.
This is based on the swap hooks implementation for memory tags on arm64
('arch/arm64/mm/mteswap.c'; thanks!)
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
include/linux/page_owner.h | 49 +++++++++++++++
mm/page_owner.c | 120 +++++++++++++++++++++++++++++++++++++
2 files changed, 169 insertions(+)
diff --git a/include/linux/page_owner.h b/include/linux/page_owner.h
index 3328357f6dba..cd95aacceba7 100644
--- a/include/linux/page_owner.h
+++ b/include/linux/page_owner.h
@@ -75,4 +75,53 @@ static inline void dump_page_owner(const struct page *page)
{
}
#endif /* CONFIG_PAGE_OWNER */
+
+#ifdef CONFIG_SWAP_PAGE_OWNER
+extern struct static_key_false swap_page_owner_inited;
+
+extern int __page_owner_prepare_to_swap(struct folio *folio);
+extern void __page_owner_swap_restore(swp_entry_t entry, struct folio *folio);
+extern void __page_owner_swap_invalidate_page(int type, pgoff_t offset);
+extern void __page_owner_swap_invalidate_area(int type);
+
+static inline int page_owner_prepare_to_swap(struct folio *folio)
+{
+ if (static_branch_unlikely(&swap_page_owner_inited))
+ return __page_owner_prepare_to_swap(folio);
+
+ return 0;
+}
+
+static inline void page_owner_swap_restore(swp_entry_t entry, struct folio *folio)
+{
+ if (static_branch_unlikely(&swap_page_owner_inited))
+ return __page_owner_swap_restore(entry, folio);
+}
+
+static inline void page_owner_swap_invalidate_page(int type, pgoff_t offset)
+{
+ if (static_branch_unlikely(&swap_page_owner_inited))
+ return __page_owner_swap_invalidate_page(type, offset);
+}
+
+static inline void page_owner_swap_invalidate_area(int type)
+{
+ if (static_branch_unlikely(&swap_page_owner_inited))
+ return __page_owner_swap_invalidate_area(type);
+}
+#else
+static inline int page_owner_prepare_to_swap(struct folio *folio)
+{
+ return 0;
+}
+static inline void page_owner_swap_restore(swp_entry_t entry, struct folio *folio)
+{
+}
+static inline void page_owner_swap_invalidate_page(int type, pgoff_t offset)
+{
+}
+static inline void page_owner_swap_invalidate_area(int type)
+{
+}
+#endif /* CONFIG_SWAP_PAGE_OWNER */
#endif /* __LINUX_PAGE_OWNER_H */
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 5cd7de1f8023..d256f58deca4 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -525,6 +525,126 @@ static void copy_from_swap_page_owner(struct page_owner *page_owner,
page_owner->tgid = spo->tgid;
strscpy(page_owner->comm, spo->comm, sizeof(page_owner->comm));
}
+
+/* Store the initial stack information from page_owner to xarray. */
+int __page_owner_prepare_to_swap(struct folio *folio)
+{
+ struct page_ext_iter iter;
+ struct page_ext *page_ext;
+ struct page_owner *page_owner;
+ struct swap_page_owner *spo;
+ depot_stack_handle_t handle = 0;
+ swp_entry_t entry;
+ long i = 0, nr_pages = folio_nr_pages(folio);
+ int err;
+
+ rcu_read_lock();
+ for_each_page_ext(&folio->page, nr_pages, page_ext, iter) {
+ spo = alloc_swap_page_owner();
+ if (!spo) {
+ err = -ENOMEM;
+ goto out_locked;
+ }
+
+ page_owner = get_page_owner(page_ext);
+ copy_to_swap_page_owner(spo, page_owner);
+ entry = page_swap_entry(folio_page(folio, i));
+ err = store_swap_page_owner(spo, entry);
+ if (err)
+ goto out_locked;
+
+ if (!handle)
+ handle = page_owner->handle;
+ i++;
+ }
+ rcu_read_unlock();
+
+ /*
+ * Fix-up: increment refcount of the initial allocation.
+ * It will be decremented by page-free at swap-out.
+ */
+ inc_stack_record_count(handle, GFP_KERNEL, nr_pages);
+
+ return 0;
+
+out_locked:
+ for_each_page_ext(&folio->page, nr_pages, page_ext, iter) {
+ if (!i--)
+ break;
+
+ entry = page_swap_entry(folio_page(folio, i));
+ erase_swap_page_owner(entry, true);
+
+ page_owner = get_page_owner(page_ext);
+
+ }
+ rcu_read_unlock();
+ return err;
+}
+
+/* Load the initial stack information from xarray to page_owner. */
+void __page_owner_swap_restore(swp_entry_t entry, struct folio *folio)
+{
+ struct page_ext_iter iter;
+ struct page_ext *page_ext;
+ struct page_owner *page_owner;
+ struct swap_page_owner *spo;
+ depot_stack_handle_t handle = 0;
+ long i = 0, nr_pages = folio_nr_pages(folio);
+
+ rcu_read_lock();
+ for_each_page_ext(&folio->page, nr_pages, page_ext, iter) {
+ spo = (struct swap_page_owner *) load_swap_page_owner(entry);
+ if (!spo) {
+ rcu_read_unlock();
+ return;
+ }
+
+ page_owner = get_page_owner(page_ext);
+ copy_from_swap_page_owner(page_owner, spo);
+
+ if (!handle)
+ handle = page_owner->handle;
+ i++;
+ entry.val++;
+ }
+ rcu_read_unlock();
+
+ /*
+ * Fix-up: decrement refcount of the swap-in allocation.
+ * It was incremented by the page-alloc at swap-in.
+ * (early_handle: see __reset_page_owner().)
+ *
+ * FIXME(mfo): 'dec_stack_record_count: refcount went to 0 ...'
+ * with stack_depot oops is hit occasionaly on tests or shutdown.
+ */
+ if (handle != early_handle)
+ dec_stack_record_count(handle, nr_pages);
+}
+
+void __page_owner_swap_invalidate_page(int type, pgoff_t offset)
+{
+ swp_entry_t entry = swp_entry(type, offset);
+
+ erase_swap_page_owner(entry, true);
+}
+
+void __page_owner_swap_invalidate_area(int type)
+{
+ swp_entry_t first_entry = swp_entry(type, 0);
+ swp_entry_t last_entry = swp_entry(type + 1, 0);
+ swp_entry_t entry;
+ void *spo;
+
+ XA_STATE(xa_state, &swap_page_owners, first_entry.val);
+
+ xa_lock(&swap_page_owners);
+ xas_for_each(&xa_state, spo, last_entry.val - 1) {
+ entry.val = xa_state.xa_index;
+ erase_swap_page_owner(entry, false);
+ }
+ xa_unlock(&swap_page_owners);
+}
#endif
void pagetypeinfo_showmixedcount_print(struct seq_file *m,
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 6/9] mm/page_owner: report '(swapped)' pages in debugfs file 'page_owner'
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (4 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 5/9] mm/page_owner: add swap hooks Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 7/9] mm/page_owner: add debugfs file 'swap_page_owner' Mauricio Faria de Oliveira
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Report whether a page has been swapped in /sys/kernel/debug/page_owner.
This is helpful for stats, verifying the functionality, and debugging.
Add the 'swapped' field to 'struct page_owner' to track whether the page
has been swapped-in (and had allocation stack and some fields overridden).
Clear it via __set_page_owner() (page allocation), __reset_page_owner()
(page free), and init_pages_in_zone() (init). Set it in the swap_restore()
swap hook (swap-in).
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/page_owner.c | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index d256f58deca4..589aa1d7b9b6 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -34,6 +34,9 @@ struct page_owner {
pid_t tgid;
pid_t free_pid;
pid_t free_tgid;
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ bool swapped;
+#endif
};
struct stack {
@@ -275,7 +278,8 @@ static inline void __update_page_owner_handle(struct page *page,
unsigned short order,
gfp_t gfp_mask,
short last_migrate_reason, u64 ts_nsec,
- pid_t pid, pid_t tgid, char *comm)
+ pid_t pid, pid_t tgid, char *comm,
+ bool __maybe_unused swapped)
{
struct page_ext_iter iter;
struct page_ext *page_ext;
@@ -293,6 +297,9 @@ static inline void __update_page_owner_handle(struct page *page,
page_owner->ts_nsec = ts_nsec;
strscpy(page_owner->comm, comm,
sizeof(page_owner->comm));
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ page_owner->swapped = swapped;
+#endif
__set_bit(PAGE_EXT_OWNER, &page_ext->flags);
__set_bit(PAGE_EXT_OWNER_ALLOCATED, &page_ext->flags);
}
@@ -316,6 +323,9 @@ static inline void __update_page_owner_free_handle(struct page *page,
if (handle) {
__clear_bit(PAGE_EXT_OWNER_ALLOCATED, &page_ext->flags);
page_owner->free_handle = handle;
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ page_owner->swapped = false;
+#endif
}
page_owner->free_ts_nsec = free_ts_nsec;
page_owner->free_pid = current->pid;
@@ -370,7 +380,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order,
handle = save_stack(gfp_mask);
__update_page_owner_handle(page, handle, order, gfp_mask, -1,
ts_nsec, current->pid, current->tgid,
- current->comm);
+ current->comm, false);
inc_stack_record_count(handle, gfp_mask, 1 << order);
}
@@ -428,7 +438,13 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old)
old_page_owner->order, old_page_owner->gfp_mask,
old_page_owner->last_migrate_reason,
old_page_owner->ts_nsec, old_page_owner->pid,
- old_page_owner->tgid, old_page_owner->comm);
+ old_page_owner->tgid, old_page_owner->comm,
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ old_page_owner->swapped
+#else
+ false
+#endif
+ );
/*
* Do not proactively clear PAGE_EXT_OWNER{_ALLOCATED} bits as the folio
* will be freed after migration. Keep them until then as they may be
@@ -602,6 +618,7 @@ void __page_owner_swap_restore(swp_entry_t entry, struct folio *folio)
page_owner = get_page_owner(page_ext);
copy_from_swap_page_owner(page_owner, spo);
+ page_owner->swapped = true;
if (!handle)
handle = page_owner->handle;
@@ -783,12 +800,20 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
return -ENOMEM;
ret = scnprintf(kbuf, count,
- "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
+ "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
page_owner->tgid, page_owner->comm,
page_owner->ts_nsec);
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ if (static_branch_unlikely(&swap_page_owner_inited) &&
+ page_owner->swapped) {
+ ret += scnprintf(kbuf + ret, count - ret, " (swapped)");
+ }
+#endif
+ ret += scnprintf(kbuf + ret, count - ret, "\n");
+
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
@@ -1052,7 +1077,8 @@ 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,
- current->tgid, current->comm);
+ current->tgid, current->comm,
+ false);
count++;
ext_put_continue:
page_ext_put(page_ext);
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 7/9] mm/page_owner: add debugfs file 'swap_page_owner'
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (5 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 6/9] mm/page_owner: report '(swapped)' pages in debugfs file 'page_owner' Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 8/9] mm: call arch-specific swap hooks from generic swap hooks Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 9/9] mm: call page_owner " Mauricio Faria de Oliveira
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add debugfs file /sys/kernel/debug/swap_page_owner to report the allocation
stack traces for pages in swap space (i.e., dump the XArray).
Now, it is possible to list the allocation stack traces both for pages in
memory and swap space, via /sys/kernel/debug/{page_owner,swap_page_owner}.
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
mm/page_owner.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 589aa1d7b9b6..e014eb7caf56 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -1103,6 +1103,77 @@ static const struct file_operations page_owner_fops = {
.llseek = lseek_page_owner,
};
+#ifdef CONFIG_SWAP_PAGE_OWNER
+static ssize_t print_swap_page_owner(char __user *buf, size_t count,
+ swp_entry_t entry,
+ struct swap_page_owner *spo)
+{
+ int ret;
+ char *kbuf;
+
+ count = min_t(size_t, count, PAGE_SIZE);
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = scnprintf(kbuf, count,
+ "Page allocated via pid %d, tgid %d (%s), ts %llu ns\n",
+ spo->pid, spo->tgid, spo->comm, spo->ts_nsec);
+
+ ret += scnprintf(kbuf + ret, count - ret,
+ "SWP entry 0x%lx\n", entry.val);
+
+ ret += stack_depot_snprint(spo->handle, kbuf + ret, count - ret, 0);
+ if (ret >= count)
+ goto err;
+
+ ret += snprintf(kbuf + ret, count - ret, "\n");
+ if (ret >= count)
+ goto err;
+
+ if (copy_to_user(buf, kbuf, ret))
+ ret = -EFAULT;
+
+ kfree(kbuf);
+ return ret;
+
+err:
+ kfree(kbuf);
+ return -ENOMEM;
+}
+
+static ssize_t read_swap_page_owner(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ swp_entry_t entry = { .val = *ppos };
+ void *spo;
+ XA_STATE(xa_state, &swap_page_owners, entry.val);
+
+ if (!static_branch_unlikely(&swap_page_owner_inited))
+ return -EINVAL;
+
+ xa_lock(&swap_page_owners);
+ xas_for_each(&xa_state, spo, ULONG_MAX) {
+
+ /* Resume after current index. */
+ entry.val = xa_state.xa_index;
+ *ppos = entry.val + 1;
+
+ xa_unlock(&swap_page_owners);
+ return print_swap_page_owner(buf, count, entry,
+ (struct swap_page_owner *) spo);
+ }
+ xa_unlock(&swap_page_owners);
+
+ return 0;
+}
+
+static const struct file_operations swap_page_owner_fops = {
+ .read = read_swap_page_owner,
+ .llseek = lseek_page_owner,
+};
+#endif
+
static void *stack_start(struct seq_file *m, loff_t *ppos)
{
struct stack *stack;
@@ -1247,6 +1318,10 @@ static int __init pageowner_init(void)
&page_owner_stack_fops);
debugfs_create_file("count_threshold", 0600, dir, NULL,
&page_owner_threshold_fops);
+#ifdef CONFIG_SWAP_PAGE_OWNER
+ debugfs_create_file("swap_page_owner", 0400, NULL, NULL,
+ &swap_page_owner_fops);
+#endif
return 0;
}
late_initcall(pageowner_init)
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 8/9] mm: call arch-specific swap hooks from generic swap hooks
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (6 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 7/9] mm/page_owner: add debugfs file 'swap_page_owner' Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
2025-12-05 23:17 ` [PATCH RFC 9/9] mm: call page_owner " Mauricio Faria de Oliveira
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Add generic swap hooks that just call the arch-specific ones (e.g., arm64).
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
include/linux/pgtable.h | 20 ++++++++++++++++++++
mm/memory.c | 2 +-
mm/page_io.c | 2 +-
mm/shmem.c | 2 +-
mm/swapfile.c | 6 +++---
5 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
index 652f287c1ef6..23d30afe439e 100644
--- a/include/linux/pgtable.h
+++ b/include/linux/pgtable.h
@@ -1220,6 +1220,26 @@ static inline void arch_swap_restore(swp_entry_t entry, struct folio *folio)
}
#endif
+static inline int hook_prepare_to_swap(struct folio *folio)
+{
+ return arch_prepare_to_swap(folio);
+}
+
+static inline void hook_swap_invalidate_page(int type, pgoff_t offset)
+{
+ arch_swap_invalidate_page(type, offset);
+}
+
+static inline void hook_swap_invalidate_area(int type)
+{
+ arch_swap_invalidate_area(type);
+}
+
+static inline void hook_swap_restore(swp_entry_t entry, struct folio *folio)
+{
+ arch_swap_restore(entry, folio);
+}
+
#ifndef __HAVE_ARCH_MOVE_PTE
#define move_pte(pte, old_addr, new_addr) (pte)
#endif
diff --git a/mm/memory.c b/mm/memory.c
index 2a55edc48a65..6a5a5bd3eeed 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4945,7 +4945,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
* when reading from swap. This metadata may be indexed by swap entry
* so this must be called before swap_free().
*/
- arch_swap_restore(folio_swap(entry, folio), folio);
+ hook_swap_restore(folio_swap(entry, folio), folio);
/*
* Remove the swap entry and conditionally try to free up the swapcache.
diff --git a/mm/page_io.c b/mm/page_io.c
index 3c342db77ce3..b62c53a86a59 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -248,7 +248,7 @@ int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug)
* Arch code may have to preserve more data than just the page
* contents, e.g. memory tags.
*/
- ret = arch_prepare_to_swap(folio);
+ ret = hook_prepare_to_swap(folio);
if (ret) {
folio_mark_dirty(folio);
goto out_unlock;
diff --git a/mm/shmem.c b/mm/shmem.c
index 3f194c9842a8..ef53c14156fb 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2395,7 +2395,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
* Some architectures may have to restore extra metadata to the
* folio after reading from swap.
*/
- arch_swap_restore(folio_swap(swap, folio), folio);
+ hook_swap_restore(folio_swap(swap, folio), folio);
if (shmem_should_replace_folio(folio, gfp)) {
error = shmem_replace_folio(&folio, gfp, info, index, vma);
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 46d2008e4b99..e156f5cb6e9f 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1270,7 +1270,7 @@ static void swap_range_free(struct swap_info_struct *si, unsigned long offset,
else
swap_slot_free_notify = NULL;
while (offset <= end) {
- arch_swap_invalidate_page(si->type, offset);
+ hook_swap_invalidate_page(si->type, offset);
if (swap_slot_free_notify)
swap_slot_free_notify(si->bdev, offset);
offset++;
@@ -2196,7 +2196,7 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,
* when reading from swap. This metadata may be indexed by swap entry
* so this must be called before swap_free().
*/
- arch_swap_restore(folio_swap(entry, folio), folio);
+ hook_swap_restore(folio_swap(entry, folio), folio);
dec_mm_counter(vma->vm_mm, MM_SWAPENTS);
inc_mm_counter(vma->vm_mm, MM_ANONPAGES);
@@ -2931,7 +2931,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
p->cluster_info = NULL;
spin_unlock(&p->lock);
spin_unlock(&swap_lock);
- arch_swap_invalidate_area(p->type);
+ hook_swap_invalidate_area(p->type);
zswap_swapoff(p->type);
mutex_unlock(&swapon_mutex);
kfree(p->global_cluster);
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 9/9] mm: call page_owner swap hooks from generic swap hooks
2025-12-05 23:17 [PATCH RFC 0/9] mm/page_owner: add support for pages in swap space Mauricio Faria de Oliveira
` (7 preceding siblings ...)
2025-12-05 23:17 ` [PATCH RFC 8/9] mm: call arch-specific swap hooks from generic swap hooks Mauricio Faria de Oliveira
@ 2025-12-05 23:17 ` Mauricio Faria de Oliveira
8 siblings, 0 replies; 10+ messages in thread
From: Mauricio Faria de Oliveira @ 2025-12-05 23:17 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Lorenzo Stoakes, Michal Hocko, Vlastimil Babka, Oscar Salvador,
linux-mm, linux-kernel, kernel-dev
Finally, call the page_owner swap hooks from the generic swap hooks.
This plugs the new functionality into the kernel.
Add a missing include in 'page_owner.h' to fix build errors in files
that include 'pgtable.h' (thus now 'page_owner.h') without an earlier
include that provides 'pg_data_t':
In file included from .../include/linux/pgtable.h:19,
from .../arch/x86/boot/startup/map_kernel.c:7:
.../include/linux/page_owner.h:20:41: error: unknown type name ‘pg_data_t’
Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
---
include/linux/page_owner.h | 1 +
include/linux/pgtable.h | 12 +++++++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/include/linux/page_owner.h b/include/linux/page_owner.h
index cd95aacceba7..d7029d1e41de 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/mmzone.h>
#ifdef CONFIG_PAGE_OWNER
extern struct static_key_false page_owner_inited;
diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
index 23d30afe439e..ae397f5884d5 100644
--- a/include/linux/pgtable.h
+++ b/include/linux/pgtable.h
@@ -16,6 +16,7 @@
#include <linux/errno.h>
#include <asm-generic/pgtable_uffd.h>
#include <linux/page_table_check.h>
+#include <linux/page_owner.h>
#if 5 - defined(__PAGETABLE_P4D_FOLDED) - defined(__PAGETABLE_PUD_FOLDED) - \
defined(__PAGETABLE_PMD_FOLDED) != CONFIG_PGTABLE_LEVELS
@@ -1222,22 +1223,31 @@ static inline void arch_swap_restore(swp_entry_t entry, struct folio *folio)
static inline int hook_prepare_to_swap(struct folio *folio)
{
- return arch_prepare_to_swap(folio);
+ int ret;
+
+ ret = arch_prepare_to_swap(folio);
+ if (ret)
+ return ret;
+
+ return page_owner_prepare_to_swap(folio);
}
static inline void hook_swap_invalidate_page(int type, pgoff_t offset)
{
arch_swap_invalidate_page(type, offset);
+ page_owner_swap_invalidate_page(type, offset);
}
static inline void hook_swap_invalidate_area(int type)
{
arch_swap_invalidate_area(type);
+ page_owner_swap_invalidate_area(type);
}
static inline void hook_swap_restore(swp_entry_t entry, struct folio *folio)
{
arch_swap_restore(entry, folio);
+ page_owner_swap_restore(entry, folio);
}
#ifndef __HAVE_ARCH_MOVE_PTE
--
2.51.0
^ permalink raw reply [flat|nested] 10+ messages in thread