[PATCH v3 2/7] mm/page_owner: protect page access with mem-hotplug read lock
From: Zhen Ni
Date: Wed Oct 21 2026 - 07:26:57 EST
The page_owner scan walks PFNs locklessly and dereferences the resolved
struct page in the scan itself and in print_page_owner(). With
CONFIG_MEMORY_HOTREMOVE, a concurrent remove_memory() can tear down the
section's vmemmap, so a dereference after a stale validity check may
fault.
Take a mem-hotplug read lock across each page's check-then-deref. In the
scan, gate every PFN with pfn_to_online_page() under the reader and drop
it before cond_resched(), so hotplug can still interleave between pages;
in print_page_owner(), re-resolve the page under a reader covering just
the print of one record.
The reader is cheap: get_online_mems()/put_online_mems() are just
per-CPU counter increments/decrements when no hotplug is active, so
they contend only with an actual add/remove. Taking the lock per-page
bounds the writer's wait to a single page's check-and-deref, keeping
hot-remove stalls brief while all accesses stay protected.
This makes the traversal crash-free. A recycled-but-present page can
still yield stale data -- the lockless scan is best-effort and
unchanged by this patch.
Signed-off-by: Zhen Ni <zhen.ni@xxxxxxxxxxxx>
---
Changes in v3:
- New patch. Add the mem-hotplug protect.
---
mm/page_owner.c | 35 ++++++++++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 5 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 92a92cdf74ec..8e72a85c663c 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -762,13 +762,22 @@ static bool page_owner_next_eligible(struct page_owner_scan *scan)
continue;
}
- page = pfn_to_page(pfn);
+ /*
+ * Hold a mem-hotplug reader across the page derefs so the
+ * vmemmap isn't torn down mid-scan; released before
+ * cond_resched().
+ */
+ get_online_mems();
+ page = pfn_to_online_page(pfn);
+ if (!page)
+ goto put_online_continue;
+
if (skip_buddy_pages(&pfn, page))
- continue;
+ goto put_online_continue;
page_ext = page_ext_get(page);
if (unlikely(!page_ext))
- continue;
+ goto put_online_continue;
/*
* Some pages could be missed by concurrent allocation or free,
@@ -805,10 +814,13 @@ static bool page_owner_next_eligible(struct page_owner_scan *scan)
goto ext_put_continue;
page_ext_put(page_ext);
+ put_online_mems();
return true;
ext_put_continue:
page_ext_put(page_ext);
+put_online_continue:
+ put_online_mems();
cond_resched();
}
@@ -831,7 +843,17 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* Find an allocated page */
while (page_owner_next_eligible(&scan)) {
- struct page *page = pfn_to_page(scan.pfn);
+ struct page *page;
+ ssize_t ret;
+
+ /*
+ * The scan is lockless. Re-resolve the page under a mem-hotplug
+ * read-side lock so that we never access a hot-removed section.
+ */
+ get_online_mems();
+ page = pfn_to_online_page(scan.pfn);
+ if (!page)
+ goto skip_continue;
if (state->nid_filter_enabled) {
int nid;
@@ -851,10 +873,13 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* Record the next PFN to read in the file offset */
*ppos = scan.pfn + 1;
- return print_page_owner(buf, count, scan.pfn, page,
+ ret = print_page_owner(buf, count, scan.pfn, page,
&scan.po_snap, scan.po_snap.handle, state);
+ put_online_mems();
+ return ret;
skip_continue:
+ put_online_mems();
scan.pfn++;
cond_resched();
}
--
2.20.1