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
next prev parent reply other threads:[~2026-07-07 8:27 UTC|newest]
Thread overview: 22+ 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-09 14:52 ` Baoquan He
2026-07-10 1:11 ` Nhat Pham
2026-07-11 9:02 ` Chris Li
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
2026-07-09 11:38 ` Baoquan He
2026-07-13 0:02 ` Nhat Pham
2026-07-13 17:47 ` Chris Li
2026-07-11 8:49 ` Chris Li
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.