[RFC PATCH 06/11] mm, swap: free backing pages in xswap_unmap_clusters
From: Baoquan He
Date: Mon Jul 27 2026 - 10:18:52 EST
vm_area_unmap_pages() only clears PTEs and frees intermediate page
table pages — it does not free the backing physical pages allocated
by xswap_map_clusters().
Fix this by walking the page table with apply_to_existing_page_range()
before the unmap to collect all struct pages in the range. After
vunmap_range() clears the PTEs, free the collected pages via
__free_page().
Use a simple xswap_page_data collector callback: for each present PTE,
collect pte_page() into a dynamically allocated array. The array is
freed after the pages are released.
Signed-off-by: Baoquan He <baoquan.he@xxxxxxxxx>
---
mm/swapfile.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 45 insertions(+), 6 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index e0f5fe64de2a..7613ce231d70 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3776,6 +3776,23 @@ static int xswap_map_clusters(struct swap_info_struct *si,
return -ENOMEM;
}
+struct xswap_page_data {
+ struct page **pages;
+ int nr;
+ int max;
+};
+
+static int xswap_collect_page(pte_t *pte, unsigned long addr, void *data)
+{
+ struct xswap_page_data *xpd = data;
+
+ if (!pte_present(*pte))
+ return 0;
+ if (xpd->nr < xpd->max)
+ xpd->pages[xpd->nr++] = pte_page(*pte);
+ return 0;
+}
+
static void xswap_unmap_clusters(struct swap_info_struct *si,
unsigned long start_idx, unsigned long nr)
{
@@ -3785,21 +3802,43 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
/*
* Round to page boundaries: start up (skip partial page that may
* contain clusters still in use before start_idx), end up so the
- * entire range is covered. vm_area_unmap_pages() operates on
- * whole pages.
+ * entire range is covered. vm_area_unmap_pages() and
+ * apply_to_existing_page_range() operate on whole pages.
*/
unsigned long vm_start = PAGE_ALIGN(start_addr);
unsigned long vm_end = PAGE_ALIGN(end_addr);
+ unsigned long size;
+ unsigned long npages;
+ struct xswap_page_data xpd;
+ int i;
if (vm_start >= vm_end)
goto out;
- vm_area_unmap_pages(si->cluster_vm, vm_start, vm_end);
+ size = vm_end - vm_start;
+ npages = size >> PAGE_SHIFT;
+
/*
- * vm_area_unmap_pages() only clears PTEs; it does not free the
- * physical pages. Walk the page table to find and free them.
+ * Walk the page table to collect physical pages before unmapping.
+ * vm_area_unmap_pages() only clears PTEs and frees intermediate
+ * page table pages — it does not free backing pages.
*/
- /* TODO: free backing pages via page table walk or tracking bitmap */
+ xpd.pages = kmalloc_array(npages, sizeof(*xpd.pages), GFP_KERNEL);
+ if (xpd.pages) {
+ xpd.nr = 0;
+ xpd.max = npages;
+ apply_to_existing_page_range(&init_mm, vm_start, size,
+ xswap_collect_page, &xpd);
+ }
+
+ vm_area_unmap_pages(si->cluster_vm, vm_start, vm_end);
+
+ /* Free the collected backing pages */
+ if (xpd.pages) {
+ for (i = 0; i < xpd.nr; i++)
+ __free_page(xpd.pages[i]);
+ kfree(xpd.pages);
+ }
/*
* Pairs with READ_ONCE() in shrink/grow paths.
*/
--
2.54.0