[RFC PATCH v2 06/10] mm, swap: free backing pages in xswap_unmap_clusters

Baoquan He <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.kernel.mm
Message-ID <[email protected]>
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 <[email protected]>
---
 mm/swapfile.c | 52 +++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 46 insertions(+), 6 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index 0d1d0c5e24c6..c43c8746378e 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3796,6 +3796,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)
 {
@@ -3805,11 +3822,15 @@ 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;
 
 	mutex_lock(&si->xswap_lock);
 
@@ -3818,12 +3839,31 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
 		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);
+	}
+
 	mutex_unlock(&si->xswap_lock);
 
 out:
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.