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: 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