The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Baoquan He <baoquan.he@linux.dev>
To: linux-mm@kvack.org
Cc: akpm@linux-foundation.org, chrisl@kernel.org, kasong@tencent.com,
	shikemeng@huaweicloud.com, nphamcs@gmail.com, baohua@kernel.org,
	youngjun.park@lge.com, hannes@cmpxchg.org, yosry@kernel.org,
	chengming.zhou@linux.dev, linux-kernel@vger.kernel.org,
	Baoquan He <baoquan.he@linux.dev>
Subject: [RFC PATCH 08/10] mm/swapfile: manage ghost cluster_info via lazy vmalloc
Date: Tue,  7 Jul 2026 16:26:10 +0800	[thread overview]
Message-ID: <20260707082614.95030-9-baoquan.he@linux.dev> (raw)
In-Reply-To: <20260707082614.95030-1-baoquan.he@linux.dev>

Replace the kvzalloc cluster_info allocation for ghost swap with a sparse
vmalloc area (VM_SPARSE). At swapon time, reserve up to 64MB of virtual
address space (supporting ~1TB ghost swap) but only allocate physical
pages for the initial size. The cluster_info pointer never changes —
__swap_offset_to_cluster() and cluster_index() are unchanged.

Add cluster_vm and ghost_name fields to struct swap_info_struct.
In setup_swap_clusters_info(), allocate zeroed physical pages and map
them into the reserved VA via vm_area_map_pages(). Handle teardown in
both swapoff and the swapon error path.

Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
 include/linux/swap.h |   4 +
 mm/swapfile.c        | 210 +++++++++++++++++++++++++++++++++++--------
 2 files changed, 179 insertions(+), 35 deletions(-)

diff --git a/include/linux/swap.h b/include/linux/swap.h
index a4d6851e0921..b87688b7d4ba 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -16,6 +16,8 @@
 #include <uapi/linux/mempolicy.h>
 #include <asm/page.h>
 
+struct vm_struct;
+
 #define SWAP_FLAG_PREFER	0x8000	/* set if swap priority specified */
 #define SWAP_FLAG_PRIO_MASK	0x7fff
 #define SWAP_FLAG_DISCARD	0x10000 /* enable discard for swap */
@@ -279,6 +281,8 @@ struct swap_info_struct {
 	struct plist_node avail_list;   /* entry in swap_avail_head */
 #ifdef CONFIG_ZSWAP
 	struct xarray redirect_xa;	/* ghost offset → phys swp_entry_t */
+	struct vm_struct *cluster_vm;	/* ghost: sparse vm_area for cluster_info */
+	char *ghost_name;		/* ghost: dentry name for sysfs */
 #endif
 };
 
diff --git a/mm/swapfile.c b/mm/swapfile.c
index c1686c2b2521..da085a75d7b5 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -56,6 +56,9 @@ static bool folio_swapcache_freeable(struct folio *folio);
 static void move_cluster(struct swap_info_struct *si,
 			 struct swap_cluster_info *ci, struct list_head *list,
 			 enum swap_cluster_flags new_flags);
+#ifdef CONFIG_ZSWAP
+#define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024)
+#endif
 
 /*
  * Protects the swap_info array, and the SWP_USED flag. swap_info contains
@@ -3244,9 +3247,14 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 	swap_file = p->swap_file;
 	p->swap_file = NULL;
 	maxpages = p->max;
-	cluster_info = p->cluster_info;
-	p->max = 0;
-	p->cluster_info = NULL;
+	if (p->flags & SWP_GHOST) {
+		p->max = 0;
+		cluster_info = NULL;
+	} else {
+		cluster_info = p->cluster_info;
+		p->max = 0;
+		p->cluster_info = NULL;
+	}
 	spin_unlock(&p->lock);
 	spin_unlock(&swap_lock);
 	arch_swap_invalidate_area(p->type);
@@ -3254,7 +3262,31 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 	mutex_unlock(&swapon_mutex);
 	kfree(p->global_cluster);
 	p->global_cluster = NULL;
-	free_swap_cluster_info(cluster_info, maxpages);
+	if (p->flags & SWP_GHOST) {
+		unsigned long j, np = p->cluster_vm ?
+			p->cluster_vm->nr_pages : 0;
+		struct page **pglist = p->cluster_vm ?
+			(struct page **)p->cluster_vm->pages : NULL;
+
+		xa_destroy(&p->redirect_xa);
+		kfree(p->ghost_name);
+		p->ghost_name = NULL;
+		for (j = 0; j < np; j++)
+			__free_page(pglist[j]);
+		kvfree(pglist);
+		if (p->cluster_vm) {
+			vm_area_unmap_pages(p->cluster_vm,
+					    (unsigned long)p->cluster_vm->addr,
+					    (unsigned long)p->cluster_vm->addr +
+					    np * PAGE_SIZE);
+			remove_vm_area(p->cluster_vm->addr);
+			kfree(p->cluster_vm);
+			p->cluster_vm = NULL;
+		}
+		p->cluster_info = NULL;
+	} else {
+		free_swap_cluster_info(cluster_info, maxpages);
+	}
 
 	inode = mapping->host;
 
@@ -3624,16 +3656,48 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
 				    unsigned long maxpages)
 {
 	unsigned long nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
-	struct swap_cluster_info *cluster_info;
+	struct swap_cluster_info *cluster_info = NULL;
 	int err = -ENOMEM;
 	unsigned long i;
 
-	cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
-	if (!cluster_info)
-		goto err;
+	if (si->flags & SWP_GHOST) {
+		unsigned long bytes, nr_pages;
+		struct page **pg;
 
-	for (i = 0; i < nr_clusters; i++)
-		spin_lock_init(&cluster_info[i].lock);
+		bytes = ALIGN(nr_clusters * sizeof(struct swap_cluster_info),
+			      PAGE_SIZE);
+		nr_pages = bytes / PAGE_SIZE;
+		pg = kvmalloc_array(nr_pages, sizeof(*pg), GFP_KERNEL);
+		if (!pg)
+			goto err;
+		for (i = 0; i < nr_pages; i++) {
+			pg[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+			if (!pg[i]) {
+				while (i-- > 0)
+					__free_page(pg[i]);
+				kvfree(pg);
+				goto err;
+			}
+		}
+		err = vm_area_map_pages(si->cluster_vm,
+					(unsigned long)si->cluster_vm->addr,
+					(unsigned long)si->cluster_vm->addr + bytes, pg);
+		if (err) {
+			for (i = 0; i < nr_pages; i++)
+				__free_page(pg[i]);
+			kvfree(pg);
+			goto err;
+		}
+		si->cluster_vm->pages = (void *)pg;
+		si->cluster_vm->nr_pages = nr_pages;
+		cluster_info = si->cluster_info;
+	} else {
+		cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
+		if (!cluster_info)
+			goto err;
+		for (i = 0; i < nr_clusters; i++)
+			spin_lock_init(&cluster_info[i].lock);
+	}
 
 	if (!(si->flags & SWP_SOLIDSTATE)) {
 		si->global_cluster = kmalloc_obj(*si->global_cluster);
@@ -3644,37 +3708,64 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
 		spin_lock_init(&si->global_cluster_lock);
 	}
 
-	/*
-	 * Mark unusable pages (header page, bad pages, and the EOF part of
-	 * the last cluster) as unavailable. The clusters aren't marked free
-	 * yet, so no list operations are involved yet.
-	 */
-	err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
-	if (err)
-		goto err;
-	for (i = 0; i < swap_header->info.nr_badpages; i++) {
-		unsigned int page_nr = swap_header->info.badpages[i];
-
-		if (!page_nr || page_nr > swap_header->info.last_page) {
-			pr_warn("Bad slot offset is out of border: %d (last_page: %d)\n",
-				page_nr, swap_header->info.last_page);
-			err = -EINVAL;
+	if (si->flags & SWP_GHOST) {
+		struct swap_cluster_info *ci;
+
+		ci = &cluster_info[0];
+		if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
+			err = -ENOMEM;
 			goto err;
 		}
-		err = swap_cluster_setup_bad_slot(si, cluster_info, page_nr, false);
-		if (err)
-			goto err;
-	}
-	for (i = maxpages; i < round_up(maxpages, SWAPFILE_CLUSTER); i++) {
-		err = swap_cluster_setup_bad_slot(si, cluster_info, i, true);
+		spin_lock(&ci->lock);
+		__swap_table_set(ci, 0, SWP_TB_BAD);
+		ci->count++;
+		spin_unlock(&ci->lock);
+		if (nr_clusters && (maxpages % SWAPFILE_CLUSTER)) {
+			unsigned long base = (maxpages - 1) /
+				SWAPFILE_CLUSTER * SWAPFILE_CLUSTER;
+			unsigned long idx = base / SWAPFILE_CLUSTER;
+
+			ci = &cluster_info[idx];
+			if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
+				err = -ENOMEM;
+				goto err;
+			}
+			spin_lock(&ci->lock);
+			for (i = maxpages % SWAPFILE_CLUSTER;
+			     i < SWAPFILE_CLUSTER; i++)
+				__swap_table_set(ci, i, SWP_TB_BAD);
+			spin_unlock(&ci->lock);
+		}
+	} else {
+		err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
 		if (err)
 			goto err;
+		for (i = 0; i < swap_header->info.nr_badpages; i++) {
+			unsigned int page_nr = swap_header->info.badpages[i];
+
+			if (!page_nr || page_nr > swap_header->info.last_page) {
+				pr_warn("Bad slot offset is out of border: "
+					"%d (last_page: %d)\n",
+					page_nr, swap_header->info.last_page);
+				err = -EINVAL;
+				goto err;
+			}
+			err = swap_cluster_setup_bad_slot(si, cluster_info,
+							  page_nr, false);
+			if (err)
+				goto err;
+		}
+		for (i = maxpages; i < round_up(maxpages, SWAPFILE_CLUSTER); i++) {
+			err = swap_cluster_setup_bad_slot(si, cluster_info,
+							  i, true);
+			if (err)
+				goto err;
+		}
 	}
 
 	INIT_LIST_HEAD(&si->free_clusters);
 	INIT_LIST_HEAD(&si->full_clusters);
 	INIT_LIST_HEAD(&si->discard_clusters);
-
 	for (i = 0; i < SWAP_NR_ORDERS; i++) {
 		INIT_LIST_HEAD(&si->nonfull_clusters[i]);
 		INIT_LIST_HEAD(&si->frag_clusters[i]);
@@ -3692,9 +3783,12 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
 		}
 	}
 
-	si->cluster_info = cluster_info;
+	if (!(si->flags & SWP_GHOST))
+		si->cluster_info = cluster_info;
 	return 0;
 err:
+	if (si->flags & SWP_GHOST)
+		return err;
 	free_swap_cluster_info(cluster_info, maxpages);
 	return err;
 }
@@ -3790,6 +3884,28 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
 
 	si->max = maxpages;
 	si->pages = maxpages - 1;
+	if (si->flags & SWP_GHOST) {
+		unsigned long max_cl_bytes;
+
+		max_cl_bytes = swapfile_maximum_size / SWAPFILE_CLUSTER *
+			       sizeof(struct swap_cluster_info);
+		max_cl_bytes = min(ALIGN(max_cl_bytes, PAGE_SIZE),
+				   GHOST_MAX_CLUSTER_BYTES);
+		si->cluster_vm = get_vm_area_caller(max_cl_bytes, VM_SPARSE,
+					__builtin_return_address(0));
+		if (!si->cluster_vm) {
+			error = -ENOMEM;
+			goto bad_swap_unlock_inode;
+		}
+		si->cluster_info = si->cluster_vm->addr;
+		si->cluster_vm->nr_pages = 0;
+		si->cluster_vm->pages = NULL;
+		si->ghost_name = kstrdup(dentry->d_name.name, GFP_KERNEL);
+		if (!si->ghost_name) {
+			error = -ENOMEM;
+			goto bad_swap_unlock_inode;
+		}
+	}
 	nr_extents = setup_swap_extents(si, swap_file, &span);
 	if (nr_extents < 0) {
 		error = nr_extents;
@@ -3907,8 +4023,32 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
 	si->global_cluster = NULL;
 	inode = NULL;
 	destroy_swap_extents(si, swap_file);
-	free_swap_cluster_info(si->cluster_info, si->max);
-	si->cluster_info = NULL;
+	if (si->flags & SWP_GHOST) {
+		unsigned long j, np = si->cluster_vm ?
+			si->cluster_vm->nr_pages : 0;
+		struct page **pglist = si->cluster_vm ?
+			(struct page **)si->cluster_vm->pages : NULL;
+
+		xa_destroy(&si->redirect_xa);
+		kfree(si->ghost_name);
+		si->ghost_name = NULL;
+		for (j = 0; j < np; j++)
+			__free_page(pglist[j]);
+		kvfree(pglist);
+		if (si->cluster_vm) {
+			vm_area_unmap_pages(si->cluster_vm,
+					    (unsigned long)si->cluster_vm->addr,
+					    (unsigned long)si->cluster_vm->addr +
+					    np * PAGE_SIZE);
+			remove_vm_area(si->cluster_vm->addr);
+			kfree(si->cluster_vm);
+			si->cluster_vm = NULL;
+		}
+		si->cluster_info = NULL;
+	} else {
+		free_swap_cluster_info(si->cluster_info, si->max);
+		si->cluster_info = NULL;
+	}
 	/*
 	 * Clear the SWP_USED flag after all resources are freed so
 	 * alloc_swap_info can reuse this si safely.
-- 
2.54.0


  parent reply	other threads:[~2026-07-07  8:27 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-07  8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07  8:26 ` [RFC PATCH 01/10] mm: ghost swapfile support for zswap Baoquan He
2026-07-07  8:26 ` [RFC PATCH 02/10] mm/swap_table: add Redirect entry encoding for ghost swap backend switching Baoquan He
2026-07-07  8:26 ` [RFC PATCH 03/10] mm/swap: add redirect_xa field and ghost redirect helper declarations Baoquan He
2026-07-07  8:26 ` [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade Baoquan He
2026-07-07  8:26 ` [RFC PATCH 05/10] mm/swap_state: restore Redirect entry when swap cache folio is removed Baoquan He
2026-07-07  8:26 ` [RFC PATCH 06/10] mm/zswap: implement ghost-to-physical writeback for backend switching Baoquan He
2026-07-07  8:26 ` [RFC PATCH 07/10] mm/page_io: forward ghost swap reads to physical device via Redirect Baoquan He
2026-07-07  8:26 ` Baoquan He [this message]
2026-07-07  8:26 ` [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Baoquan He
2026-07-07 22:36   ` Nhat Pham
2026-07-07  8:26 ` [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension Baoquan He
2026-07-07  8:56 ` [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 21:23 ` Nhat Pham
2026-07-07 21:25   ` Nhat Pham

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260707082614.95030-9-baoquan.he@linux.dev \
    --to=baoquan.he@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=baohua@kernel.org \
    --cc=chengming.zhou@linux.dev \
    --cc=chrisl@kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=kasong@tencent.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=nphamcs@gmail.com \
    --cc=shikemeng@huaweicloud.com \
    --cc=yosry@kernel.org \
    --cc=youngjun.park@lge.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox