[PATCH v2 08/12] mm, swap: free backing pages in xswap_unmap_clusters

From: Baoquan He

Date: Sun Sep 13 2026 - 03:51:54 EST


vm_area_unmap_pages() clears PTEs and frees intermediate page table
pages. It does not free the backing physical pages that
xswap_map_clusters() allocated. Those pages were leaked on every
unmap.

Collect the backing pages before the unmap. Walk the page table with
apply_to_existing_page_range() and a small callback that stores
pte_page() of every present PTE into a dynamically allocated array.
After vm_area_unmap_pages() clears the PTEs, free the collected pages
with __free_page().

The array allocation runs under memalloc_noreclaim_save(). Reclaim
must not recurse into the caller, which holds xswap_lock. If the
allocation fails, return -ENOMEM without unmapping, so that no backing
page is leaked.

xswap_unmap_clusters() now returns an error code.

Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
mm/swapfile.c | 57 ++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 50 insertions(+), 7 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index abe3d0ee5aba..e10bb6d245fe 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -67,8 +67,8 @@

static int xswap_map_clusters(struct swap_info_struct *si,
unsigned long start_idx, unsigned long nr);
-static void xswap_unmap_clusters(struct swap_info_struct *si,
- unsigned long start_idx, unsigned long nr);
+static int xswap_unmap_clusters(struct swap_info_struct *si,
+ unsigned long start_idx, unsigned long nr);
static int xswap_mapped_end(pte_t *pte, unsigned long addr, void *data);
static void xswap_try_shrink(struct swap_info_struct *si);

@@ -3889,8 +3889,26 @@ static int xswap_map_clusters(struct swap_info_struct *si,
return -ENOMEM;
}

-static void xswap_unmap_clusters(struct swap_info_struct *si,
- unsigned long start_idx, unsigned long nr)
+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;
+ pte_t pteval = ptep_get(pte);
+
+ if (!pte_present(pteval))
+ return 0;
+ if (xpd->nr < xpd->max)
+ xpd->pages[xpd->nr++] = pte_page(pteval);
+ return 0;
+}
+
+static int xswap_unmap_clusters(struct swap_info_struct *si,
+ unsigned long start_idx, unsigned long nr)
{
unsigned long start_addr = (unsigned long)si->cluster_info +
(size_t)start_idx * sizeof(struct swap_cluster_info);
@@ -3898,13 +3916,18 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
/* Round to page boundaries for vm_area_unmap_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;
+ unsigned int noreclaim_flags;
+ int i;

mutex_lock(&si->xswap_lock);

if (vm_start >= vm_end) {
WRITE_ONCE(si->nr_clusters_mapped, start_idx);
mutex_unlock(&si->xswap_lock);
- return;
+ return 0;
}

/*
@@ -3916,13 +3939,33 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
flush_percpu_swap_cluster(si);
synchronize_rcu();

+ size = vm_end - vm_start;
+ npages = size >> PAGE_SHIFT;
+
+ noreclaim_flags = memalloc_noreclaim_save();
+ xpd.pages = kmalloc_array(npages, sizeof(*xpd.pages),
+ __GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL);
+ memalloc_noreclaim_restore(noreclaim_flags);
+ if (!xpd.pages) {
+ mutex_unlock(&si->xswap_lock);
+ return -ENOMEM;
+ }
+
+ 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);
- /* vm_area_unmap_pages() clears PTEs but does not free pages. */
- /* TODO: free backing pages via page table walk or tracking bitmap */
+
+ for (i = 0; i < xpd.nr; i++)
+ __free_page(xpd.pages[i]);
+ kfree(xpd.pages);

/* Pairs with READ_ONCE() in shrink/grow paths. */
WRITE_ONCE(si->nr_clusters_mapped, start_idx);
mutex_unlock(&si->xswap_lock);
+ return 0;
}

/* Track the end of the run of pages that is already mapped. */
--
2.54.0