[PATCH v2 1/6] mm/page_owner: extract page_owner_next_eligible() from read loop

From: Zhen Ni

Date: Tue Oct 13 2026 - 12:29:02 EST


Move the page scanning logic out of read_page_owner() into a dedicated
helper, in preparation for using it in the page_owner bpf_iter target.

The helper advances a struct page_owner_scan cursor to the next page
eligible for page_owner output and returns true on a hit. On a hit it
takes a snapshot of the page_owner record inside the page_ext RCU
window.

No change to the output is intended. handle is read from the snapshot,
taken before the non-zero check, so the validated and reported handle
are the same value.

Signed-off-by: Zhen Ni <zhen.ni@xxxxxxxxxxxx>
---
Changes in v2:
- Route the zero-handle case through ext_put_continue.

v1: https://lore.kernel.org/linux-mm/20261009112308.240769-2-zhen.ni@xxxxxxxxxxxx/
---
mm/page_owner.c | 103 ++++++++++++++++++++++++++++++------------------
1 file changed, 65 insertions(+), 38 deletions(-)

diff --git a/mm/page_owner.c b/mm/page_owner.c
index fbbda7ba914b..56c75a669a6b 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -36,6 +36,16 @@ struct page_owner {
pid_t free_tgid;
};

+/*
+ * Cursor and per-page result of page_owner_next_eligible().
+ */
+struct page_owner_scan {
+ /* resume point */
+ unsigned long pfn;
+ struct page *page;
+ struct page_owner po_snap;
+};
+
struct stack {
struct stack_record *stack_record;
struct stack *next;
@@ -725,37 +735,24 @@ void __dump_page_owner(const struct page *page)
page_ext_put(page_ext);
}

-static ssize_t
-read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+/*
+ * Advance @scan to the next page eligible for page_owner output.
+ * On a hit, fill scan->pfn/page/po_snap and return true; the caller
+ * must advance scan->pfn past the hit before calling again to resume
+ * the scan.
+ */
+static bool page_owner_next_eligible(struct page_owner_scan *scan)
{
- unsigned long pfn;
- struct page *page;
- struct page_ext *page_ext;
- struct page_owner *page_owner;
- depot_stack_handle_t handle;
- struct page_owner_filter_state *state = file->private_data;
+ unsigned long pfn = scan->pfn;

- if (!static_branch_unlikely(&page_owner_inited))
- return -EINVAL;
-
- page = NULL;
- if (*ppos == 0)
- pfn = min_low_pfn;
- else
- pfn = *ppos;
/* Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area */
while (!pfn_valid(pfn) && (pfn & (MAX_ORDER_NR_PAGES - 1)) != 0)
pfn++;

- /* Find an allocated page */
for (; pfn < max_pfn; pfn++) {
- /*
- * This temporary page_owner is required so
- * that we can avoid the context switches while holding
- * the rcu lock and copying the page owner information to
- * user through copy_to_user() or GFP_KERNEL allocations.
- */
- struct page_owner page_owner_tmp;
+ struct page_owner *page_owner;
+ struct page_ext *page_ext;
+ struct page *page;

/*
* If the new page is in a new MAX_ORDER_NR_PAGES area,
@@ -798,37 +795,67 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
goto ext_put_continue;

/*
- * Access to page_ext->handle isn't synchronous so we should
- * be careful to access it.
+ * Take a snapshot of the page_owner record inside the RCU
+ * window, then validate it, so that print_page_owner()
+ * reads the checked handle from the same snapshot.
*/
- handle = READ_ONCE(page_owner->handle);
- if (!handle)
+ scan->pfn = pfn;
+ scan->page = page;
+ scan->po_snap = *page_owner;
+
+ if (!scan->po_snap.handle)
goto ext_put_continue;

+ page_ext_put(page_ext);
+ return true;
+
+ext_put_continue:
+ page_ext_put(page_ext);
+ cond_resched();
+ }
+
+ return false;
+}
+
+static ssize_t
+read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ struct page_owner_scan scan;
+ struct page_owner_filter_state *state = file->private_data;
+
+ if (!static_branch_unlikely(&page_owner_inited))
+ return -EINVAL;
+
+ if (*ppos == 0)
+ scan.pfn = min_low_pfn;
+ else
+ scan.pfn = *ppos;
+
+ /* Find an allocated page */
+ while (page_owner_next_eligible(&scan)) {
if (state->nid_filter_enabled) {
int nid;
- memdesc_flags_t page_flags = READ_ONCE(page->flags);
+ memdesc_flags_t page_flags =
+ READ_ONCE(scan.page->flags);

/*
* Bypass PF_POISONED_CHECK() in page_to_nid() to avoid
* VM_BUG_ON when accessing poisoned pages.
*/
if (page_flags.f == PAGE_POISON_PATTERN)
- goto ext_put_continue;
+ goto skip_continue;
nid = memdesc_nid(&page_flags);
if (!node_isset(nid, state->nid_filter))
- goto ext_put_continue;
+ goto skip_continue;
}

/* Record the next PFN to read in the file offset */
- *ppos = pfn + 1;
+ *ppos = scan.pfn + 1;

- page_owner_tmp = *page_owner;
- page_ext_put(page_ext);
- return print_page_owner(buf, count, pfn, page,
- &page_owner_tmp, handle, state);
-ext_put_continue:
- page_ext_put(page_ext);
+ return print_page_owner(buf, count, scan.pfn, scan.page,
+ &scan.po_snap, scan.po_snap.handle, state);
+skip_continue:
+ scan.pfn++;
cond_resched();
}

--
2.20.1