* [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap
@ 2026-08-13 10:48 Baoquan He
2026-08-13 10:48 ` [RFC v3 01/15] mm: zswap: return -ENOENT when the swap device is gone Baoquan He
` (14 more replies)
0 siblings, 15 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
xswap is an extendable swap device with no backing storage.
Swapped-out pages live only in zswap, so the device wastes no disk space
and its size is independent of any physical device.
xswap decouples PTE swap entries from physical backing storage. The
cluster_info array is backed by a sparse vmalloc (VM_SPARSE) area that is
grown and shrunk on demand:
- Grow: when cluster allocation runs out of free clusters and the device
is below its ceiling, more physical pages are mapped into the VM_SPARSE
area and their clusters are added to the free list.
- Shrink: when contiguous free clusters accumulate at the tail of the
mapped range (tracked in O(1) via nr_free_tail), they are unmapped and
the backing pages freed. Shrink is deferred to a workqueue to avoid
lock recursion.
A per-device ceiling (nr_clusters) bounds growth and is adjustable at
runtime via debugfs.
Interface:
/sys/kernel/mm/xswap/create write a percent of RAM (0 for
the default) to create a device
/sys/kernel/mm/xswap/destroy write a swap type to tear down
a device
/sys/kernel/debug/xswap/type<N>_cluster_limit
read/write the per-device
cluster ceiling
Since xswap has no backing, swapped-out pages are stored compressed in
zswap: physical writeout is skipped, and zswap writeback is disabled when
every swapfile in the system is an xswap device. xswap requires zswap, so
device creation is refused when zswap is unavailable.
Naming:
======
I'm going with "xswap" (the "x" for extendable/extension) rather than "vswap".
Chris suggested this name, and this aligns with the "VFS-like swap layers"
direction Chris Li described in the first swap abstraction LPC talk
(co-hosted with Yosry) — the swap ops and the xswap extension interfaces in
this series are moving toward exactly that. I don't have a strong preference
between xswap and vswap, so if reviewers object to the name, please comment.
Note:
=====
Most of the added lines come from the switch to a sysfs-based create/destroy
interface. v2 created an xswap device by handing swapon() a "header-only"
swap file (mkswap followed by dd of just the first 4K), and teardown reused
the normal swapoff path. v3 makes xswap truly file-less:
/sys/kernel/mm/xswap/{create,destroy} replace that file dance.
This patchset only build the base. On top of this, I believe Nhat's core code
of xswap writeback, rmap etc can be implemented simpler. E.g, we only need add
one field in struct swap_cluster_info to let xs_table point to physical swap
entry, or zswap entry etc. No need to introduce struct swap_cluster_info_dynamic.
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -57,6 +57,9 @@ struct swap_cluster_info {
u8 order;
atomic_long_t __rcu *table; /* Swap table entries, see mm/swap_table.h */
unsigned int *extend_table; /* For large swap count, protected by ci->lock */
+#ifdef CONFIG_XSWAP
+ unsigned long *xs_table;
+#endif
Testing:
========
1. enable zswap
# echo 1 > /sys/module/zswap/parameters/enabled
2. create xswap device
~# echo 0 > /sys/kernel/mm/xswap/create
~# swapon
NAME TYPE SIZE USED PRIO
xswap0 xswap 1.2G 0B -1
~# echo 80 > /sys/kernel/mm/xswap/create
~# swapon
NAME TYPE SIZE USED PRIO
xswap0 xswap 1.2G 0B -1
xswap1 xswap 3.1G 0B -1
3. tune the zswap size
~# cat /sys/kernel/debug/xswap/type0_cluster_limit
1179
~# cat /sys/kernel/debug/xswap/type1_cluster_limit
3145
~# echo 2048 > /sys/kernel/debug/xswap/type0_cluster_limit
~# echo 2048 > /sys/kernel/debug/xswap/type1_cluster_limit
~# swapon
NAME TYPE SIZE USED PRIO
xswap0 xswap 2G 377.9M -1
xswap1 xswap 2G 376.2M -1
4. add memory pressure
stress-ng --vm 1 --vm-bytes 4G --vm-keep --timeout 120s &
create/destroy and grow/shrink xswap device casually, all passed.
Changelog
=========
v2->v3:
- Replace the "header-only swap file + swapon" creation hack with a
proper file-less device created and destroyed via sysfs
(/sys/kernel/mm/xswap/{create,destroy}). This required the
__swapoff() refactor and the free_swap_cluster_info() signature
change (patches 4, 6, 14).
- Require zswap: refuse to create an xswap device when zswap is
unavailable (patch 15).
- Split the unrelated zswap -ENOENT fix out of the series into a
standalone patch (patch 1).
- Fix nr_free_tail over-counting on concurrent grow, shrink leaking
detached clusters on early bail-out, a re-init race on cluster
spinlocks in xswap_map_clusters(), the nr_clusters_mapped update
ordering, and swapoff accessing the shrinker-unmapped cluster tail.
- Minor cleanups (checkpatch, /proc/swaps alignment, commit messages).
v1->v2:
- Added __GFP_HIGH | __GFP_NOMEMALLOC to alloc_page() and kmalloc_array()
in the grow path, plus memalloc_noreclaim_save/restore() wrapping,
to prevent the grow path from consuming emergency memory reserves
or recursing into swap under PF_MEMALLOC. This is folded into patch 3.
This was pointed out by Nhat.
- Folded the mutex serialization fix into the cluster grow patch (patch
3). This is suggested by Nhat.
- Fixed coding style issues: corrected indentation of declarations in
xswap_unmap_clusters(), removed unnecessary block scope around the
err variable in xswap_map_clusters().
- Rebased onto mm-unstable
Baoquan He (14):
mm: zswap: return -ENOENT when the swap device is gone
mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct
mm, swap: refactor free_swap_cluster_info to take swap_info_struct
mm, swap: add xswap cluster grow via VM_SPARSE vmalloc
mm, swap: add sysfs create interface for xswap
mm, swap: add xswap grow trigger on cluster allocation
mm, swap: add xswap_try_shrink and shrink trigger on cluster free
mm, swap: free backing pages in xswap_unmap_clusters
mm, swap: add nr_free_tail for O(1) xswap shrink detection
mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap
mm, swap: add debugfs knob for xswap per-device cluster limit
mm, swap: defer xswap shrink to workqueue to avoid lock recursion
mm, swap: refactor swapoff + add xswap_destroy
mm, swap: require zswap for xswap devices
Chris Li (1):
mm: xswap support for zswap
include/linux/swap.h | 12 +
mm/Kconfig | 9 +
mm/page_io.c | 16 +
mm/swap_state.c | 7 +
mm/swapfile.c | 1012 ++++++++++++++++++++++++++++++++++++++----
mm/zswap.c | 9 +-
6 files changed, 979 insertions(+), 86 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [RFC v3 01/15] mm: zswap: return -ENOENT when the swap device is gone
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 02/15] mm: xswap support for zswap Baoquan He
` (13 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
zswap_writeback_entry() returns -EEXIST when get_swap_device() finds no
device. -EEXIST is the shrinker's "page already in swap cache" signal,
which makes zswap_shrinker_scan() stop shrinking entirely. A NULL
get_swap_device() instead means the device is being swapped off, so the
entry is simply stale.
Return -ENOENT so the shrinker skips the stale entry and keeps scanning.
Independent of xswap; affects all swap devices.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/zswap.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/zswap.c b/mm/zswap.c
index 9f777a48b106..0407a448dd98 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -998,7 +998,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
/* try to allocate swap cache folio */
si = get_swap_device(swpentry);
if (!si)
- return -EEXIST;
+ return -ENOENT;
mpol = get_task_policy(current);
folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 02/15] mm: xswap support for zswap
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
2026-08-13 10:48 ` [RFC v3 01/15] mm: zswap: return -ENOENT when the swap device is gone Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 03/15] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Baoquan He
` (12 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
From: Chris Li <chrisl@kernel.org>
Introduce extendable (virtual) swap device support — xswap.
An xswap device is a virtual swap device with no backing storage and
no swap data section, so it wastes no disk space. Any write to an
xswap device will fail; to prevent accidental read or write, bdev of
swap_info_struct is set to NULL. Xswap devices set the SSD flag
because there is no rotational disk access when using zswap. Creation
is via a sysfs interface added in a later patch.
Zswap writeback is disabled if all swapfiles in the system are
xswap devices (tracked via nr_real_swapfiles).
Co-developed-by: Baoquan He <hebaoquan@kylinos.cn>
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
Signed-off-by: Chris Li <chrisl@kernel.org>
---
include/linux/swap.h | 2 ++
mm/page_io.c | 16 ++++++++++++++++
mm/swap_state.c | 7 +++++++
mm/swapfile.c | 37 ++++++++++++++++++++++++++++++++++---
mm/zswap.c | 7 ++++++-
5 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8dd68733c955..12f1c63c15c1 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -207,6 +207,7 @@ enum {
SWP_STABLE_WRITES = (1 << 11), /* no overwrite PG_writeback pages */
SWP_SYNCHRONOUS_IO = (1 << 12), /* synchronous IO is efficient */
SWP_HIBERNATION = (1 << 13), /* pinned for hibernation */
+ SWP_XSWAP = (1 << 14), /* extendable swap device */
/* add others here before... */
};
@@ -358,6 +359,7 @@ void free_folio_and_swap_cache(struct folio *folio);
void free_pages_and_swap_cache(struct encoded_page **, int);
/* linux/mm/swapfile.c */
extern atomic_long_t nr_swap_pages;
+extern atomic_t nr_real_swapfiles;
extern long total_swap_pages;
extern atomic_t nr_rotate_swap;
diff --git a/mm/page_io.c b/mm/page_io.c
index e4fa7ffffe8b..1f3fa52131ab 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -247,6 +247,17 @@ int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio)
}
rcu_read_unlock();
+ /*
+ * ctx->sis is set by swap_add_folio() which is called from
+ * __swap_writepage() below. Since we must avoid the writepage
+ * path for xswap devices, use the swap_info from the folio's
+ * swap entry directly instead of going through ctx.
+ */
+ if (unlikely(__swap_entry_to_info(folio->swap)->flags & SWP_XSWAP)) {
+ folio_mark_dirty(folio);
+ return AOP_WRITEPAGE_ACTIVATE;
+ }
+
__swap_writepage(ctx, folio);
return 0;
out_unlock:
@@ -479,6 +490,11 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
if (zswap_load(folio) != -ENOENT)
goto finish;
+ if (unlikely(sis->flags & SWP_XSWAP)) {
+ folio_unlock(folio);
+ goto finish;
+ }
+
/* We have to read from slower devices. Increase zswap protection. */
zswap_folio_swapin(folio);
swap_add_folio(ctx, folio, READ);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5be825911e64..ebb2d2ac356f 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -829,6 +829,13 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,
struct blk_plug plug;
swp_entry_t ra_entry;
+ /*
+ * The entry may have been freed by another task. Avoid swap_info_get()
+ * which will print error message if the race happens.
+ */
+ if (si->flags & SWP_XSWAP)
+ goto skip;
+
mask = swapin_nr_pages(offset) - 1;
if (!mask)
goto skip;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 4d4e3e3059f6..22db5dae3639 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -66,6 +66,7 @@ static void move_cluster(struct swap_info_struct *si,
static DEFINE_SPINLOCK(swap_lock);
static unsigned int nr_swapfiles;
atomic_long_t nr_swap_pages;
+atomic_t nr_real_swapfiles;
/*
* Some modules use swappable objects and may try to swap them out under
* memory pressure (via the shrinker). Before doing so, they may wish to
@@ -1223,6 +1224,8 @@ static void del_from_avail_list(struct swap_info_struct *si, bool swapoff)
goto skip;
}
+ if (!(si->flags & SWP_XSWAP))
+ atomic_sub(1, &nr_real_swapfiles);
plist_del(&si->avail_list, &swap_avail_head);
skip:
@@ -1265,6 +1268,8 @@ static void add_to_avail_list(struct swap_info_struct *si, bool swapon)
}
plist_add(&si->avail_list, &swap_avail_head);
+ if (!(si->flags & SWP_XSWAP))
+ atomic_add(1, &nr_real_swapfiles);
skip:
spin_unlock(&swap_avail_lock);
@@ -2952,6 +2957,19 @@ static int setup_swap_extents(struct swap_info_struct *sis,
struct inode *inode = mapping->host;
int ret;
+ if (sis->flags & SWP_XSWAP) {
+ *span = 0;
+ /*
+ * xswap devices have no backing block device and
+ * physical writeout is skipped in swap_writeout(),
+ * but sis->ops must still be set so that callers
+ * like shrink_folio_list() can safely dereference
+ * ops->flags.
+ */
+ sis->ops = &swap_bdev_ops;
+ return 0;
+ }
+
ret = sio_pool_init();
if (ret)
return ret;
@@ -3160,7 +3178,8 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
destroy_swap_extents(p, p->swap_file);
- if (!(p->flags & SWP_SOLIDSTATE))
+ if (!(p->flags & SWP_XSWAP) &&
+ !(p->flags & SWP_SOLIDSTATE))
atomic_dec(&nr_rotate_swap);
mutex_lock(&swapon_mutex);
@@ -3270,6 +3289,19 @@ static void swap_stop(struct seq_file *swap, void *v)
mutex_unlock(&swapon_mutex);
}
+static const char *swap_type_str(struct swap_info_struct *si)
+{
+ struct file *file = si->swap_file;
+
+ if (si->flags & SWP_XSWAP)
+ return "xswap\t";
+
+ if (S_ISBLK(file_inode(file)->i_mode))
+ return "partition";
+
+ return "file\t";
+}
+
static int swap_show(struct seq_file *swap, void *v)
{
struct swap_info_struct *si = v;
@@ -3289,8 +3321,7 @@ static int swap_show(struct seq_file *swap, void *v)
len = seq_file_path(swap, file, " \t\n\\");
seq_printf(swap, "%*s%s\t%lu\t%s%lu\t%s%d\n",
len < 40 ? 40 - len : 1, " ",
- S_ISBLK(file_inode(file)->i_mode) ?
- "partition" : "file\t",
+ swap_type_str(si),
bytes, bytes < 10000000 ? "\t" : "",
inuse, inuse < 10000000 ? "\t" : "",
si->prio);
diff --git a/mm/zswap.c b/mm/zswap.c
index 0407a448dd98..53837674d86f 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1000,6 +1000,11 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
if (!si)
return -ENOENT;
+ if (si->flags & SWP_XSWAP) {
+ put_swap_device(si);
+ return -EINVAL;
+ }
+
mpol = get_task_policy(current);
folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
NO_INTERLEAVE_INDEX);
@@ -1545,7 +1550,7 @@ bool zswap_store(struct folio *folio)
zswap_pool_put(pool);
put_objcg:
obj_cgroup_put(objcg);
- if (!ret && zswap_pool_reached_full)
+ if (!ret && zswap_pool_reached_full && atomic_read(&nr_real_swapfiles))
queue_work(shrink_wq, &zswap_shrink_work);
check_old:
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 03/15] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
2026-08-13 10:48 ` [RFC v3 01/15] mm: zswap: return -ENOENT when the swap device is gone Baoquan He
2026-08-13 10:48 ` [RFC v3 02/15] mm: xswap support for zswap Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 04/15] mm, swap: refactor free_swap_cluster_info to take swap_info_struct Baoquan He
` (11 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Add CONFIG_XSWAP Kconfig option (depends on SWAP && 64BIT) for
extendable (virtual) swap device support.
Add three fields to struct swap_info_struct under CONFIG_XSWAP:
- cluster_vm: the VM_SPARSE vm_struct backing the cluster_info array
- nr_clusters: total number of clusters in the xswap address space
- nr_clusters_mapped: number of clusters currently mapped (lazy grow)
These fields enable lazy vmalloc-based dynamic cluster management.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 5 +++++
mm/Kconfig | 9 +++++++++
2 files changed, 14 insertions(+)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 12f1c63c15c1..7ffc62a3b2d7 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -248,6 +248,11 @@ struct swap_info_struct {
signed char type; /* strange name for an index */
unsigned int max; /* size of this swap device */
struct swap_cluster_info *cluster_info; /* cluster info. Only for SSD */
+#ifdef CONFIG_XSWAP
+ struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
+ unsigned long nr_clusters; /* total cluster count for xswap */
+ unsigned long nr_clusters_mapped; /* currently mapped cluster count */
+#endif
struct list_head free_clusters; /* free clusters list */
struct list_head full_clusters; /* full clusters list */
struct list_head nonfull_clusters[SWAP_NR_ORDERS];
diff --git a/mm/Kconfig b/mm/Kconfig
index 8a24c130d008..2b4007985c46 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -122,6 +122,15 @@ config ZSWAP_COMPRESSOR_DEFAULT
default "zstd" if ZSWAP_COMPRESSOR_DEFAULT_ZSTD
default ""
+config XSWAP
+ bool "Extendable (virtual) swap device"
+ depends on SWAP && 64BIT
+ help
+ Adds support for extendable swap devices (xswap) that decouple
+ PTE swap entries from physical backing storage. The cluster_info
+ array is backed by a sparse vmalloc area that grows and shrinks
+ on demand, avoiding static pre-allocation overhead.
+
config ZSMALLOC
tristate
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 04/15] mm, swap: refactor free_swap_cluster_info to take swap_info_struct
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (2 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 03/15] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
` (10 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Change free_swap_cluster_info() to accept struct swap_info_struct*
instead of (cluster_info, maxpages) directly. Extract cluster_info
and maxpages from si inside the function. Also clean up swapoff:
remove the snapshot locals (maxpages/cluster_info) and move the
p->max/p->cluster_info clearing after free_swap_cluster_info().
This is a preparatory refactoring — no functional change. The new
signature will allow the xswap path (added in the next patch) to
access si->flags and call xswap_unmap_clusters() from within
free_swap_cluster_info().
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 22db5dae3639..4ce30e9ecdf6 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3047,14 +3047,17 @@ static void wait_for_allocation(struct swap_info_struct *si)
}
}
-static void free_swap_cluster_info(struct swap_cluster_info *cluster_info,
- unsigned long maxpages)
+static void free_swap_cluster_info(struct swap_info_struct *si)
{
+ struct swap_cluster_info *cluster_info = si->cluster_info;
+ unsigned long maxpages = si->max;
struct swap_cluster_info *ci;
- int i, nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
+ int i, nr_clusters;
if (!cluster_info)
return;
+
+ nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
for (i = 0; i < nr_clusters; i++) {
ci = cluster_info + i;
/* Cluster with bad marks count will have a remaining table */
@@ -3093,11 +3096,9 @@ static void flush_percpu_swap_cluster(struct swap_info_struct *si)
SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
{
struct swap_info_struct *p = NULL;
- struct swap_cluster_info *cluster_info;
struct file *swap_file, *victim;
struct address_space *mapping;
struct inode *inode;
- unsigned int maxpages;
int err, found = 0;
if (!capable(CAP_SYS_ADMIN))
@@ -3189,10 +3190,6 @@ 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;
spin_unlock(&p->lock);
spin_unlock(&swap_lock);
arch_swap_invalidate_area(p->type);
@@ -3200,7 +3197,9 @@ 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);
+ free_swap_cluster_info(p);
+ p->max = 0;
+ p->cluster_info = NULL;
inode = mapping->host;
@@ -3567,6 +3566,8 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
if (!cluster_info)
goto err;
+ si->cluster_info = cluster_info;
+
for (i = 0; i < nr_clusters; i++)
spin_lock_init(&cluster_info[i].lock);
@@ -3630,7 +3631,7 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
si->cluster_info = cluster_info;
return 0;
err:
- free_swap_cluster_info(cluster_info, maxpages);
+ free_swap_cluster_info(si);
return err;
}
@@ -3849,7 +3850,7 @@ 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);
+ free_swap_cluster_info(si);
si->cluster_info = NULL;
/*
* Clear the SWP_USED flag after all resources are freed so
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (3 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 04/15] mm, swap: refactor free_swap_cluster_info to take swap_info_struct Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-14 23:09 ` Klara Modin
2026-08-13 10:48 ` [RFC v3 06/15] mm, swap: add sysfs create interface for xswap Baoquan He
` (9 subsequent siblings)
14 siblings, 1 reply; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Implement dynamic cluster_info array growth for xswap devices using a
VM_SPARSE vmalloc area:
1. xswap_map_clusters(): Allocate physical pages and map them into
the pre-reserved VM_SPARSE KVA region via vm_area_map_pages().
2. xswap_unmap_clusters(): Unmap pages from the VM_SPARSE area via
vm_area_unmap_pages() (used by the error/teardown paths, shrink
comes later).
3. setup_swap_clusters_info() xswap path: Use get_vm_area(VM_SPARSE)
for the cluster_info array, lazily mapping only the initial chunk.
4. free_swap_cluster_info() xswap path: Unmap all clusters and
free_vm_area(). Built on the refactoring in the previous patch.
5. wait_for_allocation() xswap guard: Skip shrinker-unmapped clusters
beyond nr_clusters_mapped.
The grow path avoids emergency reserves via __GFP_HIGH|__GFP_NOMEMALLOC
and wraps allocations with memalloc_noreclaim_save(). A per-device
mutex (xswap_lock) serializes concurrent map/unmap page table
modifications.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 257 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 256 insertions(+), 2 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 7ffc62a3b2d7..c824848c6cfc 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -252,6 +252,7 @@ struct swap_info_struct {
struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
unsigned long nr_clusters; /* total cluster count for xswap */
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
+ struct mutex xswap_lock; /* serialize map/unmap operations */
#endif
struct list_head free_clusters; /* free clusters list */
struct list_head full_clusters; /* full clusters list */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 4ce30e9ecdf6..178c3b798f8e 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -49,6 +49,25 @@
#include "internal.h"
#include "swap.h"
+#ifdef CONFIG_XSWAP
+/*
+ * xswap: dynamically grow the cluster_info array via a VM_SPARSE area.
+ *
+ * XSWAP_GROW_CLUSTERS is the number of clusters to map in one grow
+ * operation. It is set to the number of cluster_info structs that
+ * fit in a single page (at least 16), so that the vmalloc page table
+ * overhead is proportional to the number of clusters mapped.
+ */
+#define XSWAP_GROW_CLUSTERS \
+ max_t(unsigned long, PAGE_SIZE / sizeof(struct swap_cluster_info), 16)
+
+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_check_mapped(pte_t *pte, unsigned long addr, void *data);
+#endif
+
static void swap_range_alloc(struct swap_info_struct *si,
unsigned int nr_entries);
static bool folio_swapcache_freeable(struct folio *folio);
@@ -2708,15 +2727,27 @@ static unsigned int find_next_to_unuse(struct swap_info_struct *si,
unsigned int prev)
{
unsigned int i;
+ unsigned int end = si->max;
unsigned long swp_tb;
+#ifdef CONFIG_XSWAP
+ /* xswap may have shrunk and unmapped the cluster_info tail. */
+ if (si->flags & SWP_XSWAP) {
+ unsigned long mapped_end;
+
+ mapped_end = READ_ONCE(si->nr_clusters_mapped) * SWAPFILE_CLUSTER;
+ if (mapped_end < end)
+ end = mapped_end;
+ }
+#endif
+
/*
* No need for swap_lock here: we're just looking
* for whether an entry is in use, not modifying it; false
* hits are okay, and sys_swapoff() has already prevented new
* allocations from this area (while holding swap_lock).
*/
- for (i = prev + 1; i < si->max; i++) {
+ for (i = prev + 1; i < end; i++) {
swp_tb = swap_table_get(__swap_offset_to_cluster(si, i),
i % SWAPFILE_CLUSTER);
if (!swp_tb_is_null(swp_tb) && !swp_tb_is_bad(swp_tb))
@@ -2725,7 +2756,7 @@ static unsigned int find_next_to_unuse(struct swap_info_struct *si,
cond_resched();
}
- if (i == si->max)
+ if (i == end)
i = 0;
return i;
@@ -3041,6 +3072,13 @@ static void wait_for_allocation(struct swap_info_struct *si)
BUG_ON(si->flags & SWP_WRITEOK);
+#ifdef CONFIG_XSWAP
+ /* Skip shrinker-unmapped cluster tail. */
+ if (si->flags & SWP_XSWAP)
+ end = min(end, READ_ONCE(si->nr_clusters_mapped) *
+ SWAPFILE_CLUSTER);
+#endif
+
for (offset = 0; offset < end; offset += SWAPFILE_CLUSTER) {
ci = swap_cluster_lock(si, offset);
swap_cluster_unlock(ci);
@@ -3057,6 +3095,19 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
if (!cluster_info)
return;
+#ifdef CONFIG_XSWAP
+ if (si->flags & SWP_XSWAP) {
+ /* Unmap all mapped clusters and free the VM_SPARSE area */
+ if (si->nr_clusters_mapped > 0)
+ xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
+ free_vm_area(si->cluster_vm);
+ si->cluster_vm = NULL;
+ si->nr_clusters = 0;
+ si->nr_clusters_mapped = 0;
+ return;
+ }
+#endif
+
nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
for (i = 0; i < nr_clusters; i++) {
ci = cluster_info + i;
@@ -3553,6 +3604,150 @@ static unsigned long read_swap_header(struct swap_info_struct *si,
return maxpages;
}
+#ifdef CONFIG_XSWAP
+static int xswap_map_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);
+ unsigned long end_addr = start_addr + (size_t)nr * sizeof(struct swap_cluster_info);
+ /* Round to page boundaries for vm_area_map_pages(). */
+ unsigned long vm_start = PAGE_ALIGN(start_addr);
+ unsigned long vm_end = PAGE_ALIGN(end_addr);
+ unsigned int noreclaim_flags;
+ unsigned long npages;
+ struct page **pages;
+ unsigned long i;
+ int err;
+
+ mutex_lock(&si->xswap_lock);
+
+ if (vm_start >= vm_end) {
+ /* All requested clusters fall within already-mapped pages. */
+ for (i = start_idx; i < start_idx + nr; i++)
+ spin_lock_init(&si->cluster_info[i].lock);
+ WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
+ mutex_unlock(&si->xswap_lock);
+ return 0;
+ }
+
+ npages = (vm_end - vm_start) >> PAGE_SHIFT;
+
+ /* Prevent recursive reclaim during vmap page table allocation. */
+ noreclaim_flags = memalloc_noreclaim_save();
+
+ pages = kmalloc_array(npages, sizeof(*pages),
+ __GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL);
+ if (!pages) {
+ memalloc_noreclaim_restore(noreclaim_flags);
+ mutex_unlock(&si->xswap_lock);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < npages; i++) {
+ /* __GFP_ZERO: cluster_info pointer fields must start NULL. */
+ pages[i] = alloc_page(__GFP_HIGH | __GFP_NOMEMALLOC |
+ GFP_KERNEL | __GFP_ZERO);
+ if (!pages[i])
+ goto fail;
+ }
+
+ /* Detect racing grower that already mapped these pages. */
+ if (apply_to_existing_page_range(&init_mm, vm_start,
+ vm_end - vm_start,
+ xswap_check_mapped, NULL)) {
+ i = npages;
+ goto fail_nounmap;
+ }
+
+ err = vm_area_map_pages(si->cluster_vm, vm_start, vm_end, pages);
+ if (err) {
+ /* -EBUSY: defensive, the page was already mapped. */
+ if (err == -EBUSY) {
+ i = npages;
+ goto fail_nounmap;
+ }
+ i = npages;
+ goto fail;
+ }
+
+ kfree(pages);
+ memalloc_noreclaim_restore(noreclaim_flags);
+
+ /* Initialize spinlocks for newly mapped clusters */
+ for (i = start_idx; i < start_idx + nr; i++)
+ spin_lock_init(&si->cluster_info[i].lock);
+
+ /*
+ * Pairs with READ_ONCE() in shrink/grow paths.
+ */
+ WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
+ mutex_unlock(&si->xswap_lock);
+ return 0;
+
+fail_nounmap:
+ /*
+ * The concurrent grower already mapped the range, initialized the
+ * cluster spinlocks and advanced nr_clusters_mapped. It may still
+ * be holding those locks while adding clusters to the free list, so
+ * do not touch them here; just free our unused pages.
+ */
+ while (i > 0) {
+ i--;
+ if (pages[i])
+ __free_page(pages[i]);
+ }
+ kfree(pages);
+ memalloc_noreclaim_restore(noreclaim_flags);
+ mutex_unlock(&si->xswap_lock);
+ return 0;
+
+fail:
+ while (i > 0) {
+ i--;
+ if (pages[i])
+ __free_page(pages[i]);
+ }
+ memalloc_noreclaim_restore(noreclaim_flags);
+ kfree(pages);
+ mutex_unlock(&si->xswap_lock);
+ return -ENOMEM;
+}
+
+static void 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);
+ unsigned long end_addr = start_addr + (size_t)nr * sizeof(struct swap_cluster_info);
+ /* 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);
+
+ mutex_lock(&si->xswap_lock);
+
+ if (vm_start >= vm_end) {
+ WRITE_ONCE(si->nr_clusters_mapped, start_idx);
+ mutex_unlock(&si->xswap_lock);
+ return;
+ }
+
+ 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 */
+
+ /* Pairs with READ_ONCE() in shrink/grow paths. */
+ WRITE_ONCE(si->nr_clusters_mapped, start_idx);
+ mutex_unlock(&si->xswap_lock);
+}
+
+/* Return 1 at first present PTE to signal range is already mapped. */
+static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data)
+{
+ return 1;
+}
+#endif /* CONFIG_XSWAP */
+
static int setup_swap_clusters_info(struct swap_info_struct *si,
union swap_header *swap_header,
unsigned long maxpages)
@@ -3562,6 +3757,64 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
int err = -ENOMEM;
unsigned long i;
+#ifdef CONFIG_XSWAP
+ if (si->flags & SWP_XSWAP) {
+ unsigned long size = PAGE_ALIGN(nr_clusters * sizeof(*cluster_info));
+ struct vm_struct *vm;
+
+ vm = get_vm_area(size, VM_SPARSE);
+ if (!vm)
+ goto err;
+
+ cluster_info = vm->addr;
+ si->cluster_vm = vm;
+ si->nr_clusters = nr_clusters;
+ si->cluster_info = cluster_info;
+
+ /* Map the initial chunk (at least cluster 0) */
+ if (xswap_map_clusters(si, 0, min_t(unsigned long,
+ XSWAP_GROW_CLUSTERS, nr_clusters)))
+ goto err_free_vm;
+
+ /* xswap: only cluster 0 slot 0 is bad */
+ err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
+ if (err)
+ goto err_unmap;
+
+ 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]);
+ }
+
+ /* Mark mapped clusters: cluster 0 has 1 bad slot, rest free */
+ for (i = 0; i < si->nr_clusters_mapped; i++) {
+ struct swap_cluster_info *ci = &cluster_info[i];
+
+ if (i == 0) {
+ ci->flags = CLUSTER_FLAG_NONFULL;
+ list_add_tail(&ci->list, &si->nonfull_clusters[0]);
+ } else {
+ ci->flags = CLUSTER_FLAG_FREE;
+ list_add_tail(&ci->list, &si->free_clusters);
+ }
+ }
+
+ mutex_init(&si->xswap_lock);
+ return 0;
+
+err_unmap:
+ xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
+err_free_vm:
+ free_vm_area(si->cluster_vm);
+ si->cluster_vm = NULL;
+ si->cluster_info = NULL;
+ return err;
+ }
+#endif /* CONFIG_XSWAP */
+
cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
if (!cluster_info)
goto err;
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 06/15] mm, swap: add sysfs create interface for xswap
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (4 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 07/15] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
` (8 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
xswap devices have no backing storage, so there is no file to swapon.
Add a sysfs interface to create them directly:
/sys/kernel/mm/xswap/create write a percent (0 = use default) to
create a device
The initial cluster ceiling (nr_clusters) for a new device is set by
XSWAP_DEFAULT_CLUSTER_PERCENT of totalram_pages. The device appears in
/proc/swaps as "xswap<N>". Per-device runtime sizing is tuned via the
debugfs knob added later in this series.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 161 insertions(+), 4 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 178c3b798f8e..006241558128 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -16,6 +16,8 @@
#include <linux/kernel_stat.h>
#include <linux/swap.h>
#include <linux/vmalloc.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
#include <linux/pagemap.h>
#include <linux/namei.h>
#include <linux/shmem_fs.h>
@@ -60,13 +62,79 @@
*/
#define XSWAP_GROW_CLUSTERS \
max_t(unsigned long, PAGE_SIZE / sizeof(struct swap_cluster_info), 16)
+#define XSWAP_DEFAULT_CLUSTER_PERCENT 30
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_check_mapped(pte_t *pte, unsigned long addr, void *data);
-#endif
+
+#ifdef CONFIG_SYSFS
+static int xswap_create(int percent);
+/* /sys/kernel/mm/xswap/: create.
+ * Per-device runtime size is tuned via debugfs type<N>_cluster_limit.
+ */
+
+static ssize_t xswap_create_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long percent;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ err = kstrtoul(buf, 0, &percent);
+ if (err)
+ return err;
+
+ /* 0 means "use the default percent" */
+ if (percent == 0)
+ percent = XSWAP_DEFAULT_CLUSTER_PERCENT;
+
+ err = xswap_create(percent);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+static struct kobj_attribute xswap_create_attr = __ATTR(create, 0200, NULL,
+ xswap_create_store);
+
+static struct attribute *xswap_attrs[] = {
+ &xswap_create_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group xswap_attr_group = {
+ .attrs = xswap_attrs,
+};
+
+static struct kobject *xswap_kobj;
+
+static void xswap_sysfs_init(void)
+{
+ xswap_kobj = kobject_create_and_add("xswap", mm_kobj);
+ if (!xswap_kobj) {
+ pr_err("xswap: failed to create sysfs kobject\n");
+ return;
+ }
+ if (sysfs_create_group(xswap_kobj, &xswap_attr_group))
+ pr_err("xswap: failed to create sysfs group\n");
+}
+#else
+static inline void xswap_sysfs_init(void)
+{
+}
+#endif /* CONFIG_SYSFS */
+#else /* !CONFIG_XSWAP */
+static inline void xswap_sysfs_init(void)
+{
+}
+#endif /* CONFIG_XSWAP */
static void swap_range_alloc(struct swap_info_struct *si,
unsigned int nr_entries);
@@ -3305,7 +3373,7 @@ static void *swap_start(struct seq_file *swap, loff_t *pos)
return SEQ_START_TOKEN;
for (type = 0; (si = swap_type_to_info(type)); type++) {
- if (!(si->swap_file))
+ if (!(si->swap_file) && !(si->flags & SWP_XSWAP))
continue;
if (!--l)
return si;
@@ -3326,7 +3394,7 @@ static void *swap_next(struct seq_file *swap, void *v, loff_t *pos)
++(*pos);
for (; (si = swap_type_to_info(type)); type++) {
- if (!(si->swap_file))
+ if (!(si->swap_file) && !(si->flags & SWP_XSWAP))
continue;
return si;
}
@@ -3368,7 +3436,14 @@ static int swap_show(struct seq_file *swap, void *v)
inuse = K(swap_usage_in_pages(si));
file = si->swap_file;
- len = seq_file_path(swap, file, " \t\n\\");
+ if (file)
+ len = seq_file_path(swap, file, " \t\n\\");
+ else {
+ char name[16];
+
+ len = scnprintf(name, sizeof(name), "xswap%d", si->type);
+ seq_puts(swap, name);
+ }
seq_printf(swap, "%*s%s\t%lu\t%s%lu\t%s%d\n",
len < 40 ? 40 - len : 1, " ",
swap_type_str(si),
@@ -3888,6 +3963,86 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
return err;
}
+#ifdef CONFIG_XSWAP
+#ifdef CONFIG_SYSFS
+/* Create a file-less xswap device. si->max = full RAM; @percent sets the
+ * runtime nr_clusters ceiling.
+ */
+static int xswap_create(int percent)
+{
+ struct swap_info_struct *si;
+ unsigned long ram, maxpages, init_clusters;
+ int error;
+
+ if (percent < 1 || percent > 100)
+ return -EINVAL;
+
+ si = alloc_swap_info();
+ if (IS_ERR(si))
+ return PTR_ERR(si);
+
+ INIT_WORK(&si->discard_work, swap_discard_work);
+ INIT_WORK(&si->reclaim_work, swap_reclaim_work);
+
+ ram = totalram_pages();
+ maxpages = min_t(unsigned long, ram, swapfile_maximum_size);
+ if ((unsigned int)maxpages == 0)
+ maxpages = UINT_MAX;
+ if (maxpages < 2)
+ maxpages = 2;
+
+ si->bdev = NULL;
+ si->flags |= SWP_XSWAP | SWP_SOLIDSTATE;
+ si->max = maxpages;
+ si->pages = maxpages - 1;
+ /* no backing file: mirror the xswap branch of setup_swap_extents() */
+ si->ops = &swap_bdev_ops;
+
+ error = setup_swap_clusters_info(si, NULL, maxpages);
+ if (error)
+ goto bad_swap;
+
+ /* VM_SPARSE covers full RAM; runtime nr_clusters starts at percent. */
+ init_clusters = div_u64((u64)maxpages * percent, 100 * SWAPFILE_CLUSTER);
+ init_clusters = max_t(unsigned long, init_clusters, XSWAP_GROW_CLUSTERS);
+ if (init_clusters > si->nr_clusters)
+ init_clusters = si->nr_clusters;
+ si->nr_clusters = init_clusters;
+ si->pages = min_t(unsigned long, init_clusters * SWAPFILE_CLUSTER, si->max) - 1;
+
+ error = zswap_swapon(si->type, si->max);
+ if (error)
+ goto bad_swap;
+
+ mutex_lock(&swapon_mutex);
+ si->prio = DEF_SWAP_PRIO;
+ si->list.prio = -si->prio;
+ si->avail_list.prio = -si->prio;
+ /* si->swap_file stays NULL: this is a file-less device */
+ enable_swap_info(si);
+ mutex_unlock(&swapon_mutex);
+
+ pr_info("xswap: adding extendable swap type %d (%d%% of %lu pages = %u pages, max %lu)\n",
+ si->type, percent, ram, si->pages, maxpages);
+ atomic_inc(&proc_poll_event);
+ wake_up_interruptible(&proc_poll_wait);
+
+ return si->type;
+
+bad_swap:
+ kfree(si->global_cluster);
+ si->global_cluster = NULL;
+ destroy_swap_extents(si, NULL); /* safe: xswap never sets SWP_ACTIVATED */
+ free_swap_cluster_info(si);
+ si->cluster_info = NULL;
+ spin_lock(&swap_lock);
+ si->flags = 0;
+ spin_unlock(&swap_lock);
+ return error;
+}
+#endif /* CONFIG_SYSFS */
+#endif /* CONFIG_XSWAP */
+
SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
{
struct swap_info_struct *si;
@@ -4231,6 +4386,8 @@ static int __init swapfile_init(void)
swap_migration_ad_supported = true;
#endif /* CONFIG_MIGRATION */
+ xswap_sysfs_init();
+
return 0;
}
subsys_initcall(swapfile_init);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 07/15] mm, swap: add xswap grow trigger on cluster allocation
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (5 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 06/15] mm, swap: add sysfs create interface for xswap Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 08/15] mm, swap: add xswap_try_shrink and shrink trigger on cluster free Baoquan He
` (7 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
When cluster_alloc_swap_entry() fails to find a free cluster and
the xswap device still has room to grow, expand the mapped range
by XSWAP_GROW_CLUSTERS clusters.
Since xswap is always SWP_SOLIDSTATE, no locks need to be dropped
before calling xswap_map_clusters() — global_cluster_lock is never
held on this path.
The grow sequence:
1. Check nr_clusters_mapped < nr_clusters and free list empty
2. Call xswap_map_clusters() to allocate and map more physical pages
3. Add newly mapped clusters to si->free_clusters under si->lock
4. Retry allocation from the fresh free clusters
This makes the xswap cluster space grow transparently as swap usage
increases, without any userspace intervention.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 006241558128..324d873b5f01 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1273,6 +1273,51 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si,
if (found)
goto done;
}
+
+#ifdef CONFIG_XSWAP
+ /*
+ * For xswap: if no free cluster was found and more clusters
+ * can be mapped, grow the cluster_info array and retry.
+ */
+ if (!found && (si->flags & SWP_XSWAP) &&
+ READ_ONCE(si->nr_clusters_mapped) < READ_ONCE(si->nr_clusters) &&
+ list_empty(&si->free_clusters)) {
+ unsigned long nr_new = min(READ_ONCE(si->nr_clusters) -
+ READ_ONCE(si->nr_clusters_mapped),
+ XSWAP_GROW_CLUSTERS);
+ unsigned long start = READ_ONCE(si->nr_clusters_mapped);
+ unsigned long i;
+
+ if (!xswap_map_clusters(si, start, nr_new)) {
+ unsigned long added = 0;
+
+ for (i = start; i < start + nr_new; i++) {
+ struct swap_cluster_info *ci = &si->cluster_info[i];
+
+ /*
+ * A concurrent grower may have already added
+ * these clusters to the free list. Only add
+ * clusters that are still off-list (NONE).
+ * Lock ci->lock first: move_cluster() takes
+ * si->lock internally.
+ */
+ spin_lock(&ci->lock);
+ if (ci->flags == CLUSTER_FLAG_NONE) {
+ move_cluster(si, ci, &si->free_clusters,
+ CLUSTER_FLAG_FREE);
+ added++;
+ }
+ spin_unlock(&ci->lock);
+ }
+ WRITE_ONCE(si->nr_free_tail,
+ READ_ONCE(si->nr_free_tail) + added);
+
+ /* Retry allocation from the free list */
+ found = alloc_swap_scan_list(si, &si->free_clusters,
+ folio, false);
+ }
+ }
+#endif
done:
if (!(si->flags & SWP_SOLIDSTATE))
spin_unlock(&si->global_cluster_lock);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 08/15] mm, swap: add xswap_try_shrink and shrink trigger on cluster free
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (6 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 07/15] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 09/15] mm, swap: free backing pages in xswap_unmap_clusters Baoquan He
` (6 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Add xswap_try_shrink() — the shrink logic that scans backwards from
the tail to find contiguous free clusters, then unmaps full pages
when >= XSWAP_GROW_CLUSTERS free clusters accumulate.
Wire the trigger in __free_cluster(): after a cluster is released to
the free list, call xswap_try_shrink() to attempt tail shrinking.
Also update the XSWAP_GROW_CLUSTERS comment to reflect both grow
and shrink semantics.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 48 ++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 44 insertions(+), 4 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 324d873b5f01..a3ba3496ed18 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -53,11 +53,13 @@
#ifdef CONFIG_XSWAP
/*
- * xswap: dynamically grow the cluster_info array via a VM_SPARSE area.
+ * xswap: dynamically grow and shrink the cluster_info array via a
+ * VM_SPARSE area.
*
- * XSWAP_GROW_CLUSTERS is the number of clusters to map in one grow
- * operation. It is set to the number of cluster_info structs that
- * fit in a single page (at least 16), so that the vmalloc page table
+ * XSWAP_GROW_CLUSTERS is the number of clusters to map/unmap in one
+ * grow/shrink operation. It is set to the number of cluster_info
+ * structs that fit in a single page (at least 16), so that the vmalloc
+ * page table
* overhead is proportional to the number of clusters mapped.
*/
#define XSWAP_GROW_CLUSTERS \
@@ -69,6 +71,7 @@ static int xswap_map_clusters(struct swap_info_struct *si,
static void xswap_unmap_clusters(struct swap_info_struct *si,
unsigned long start_idx, unsigned long nr);
static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data);
+static void xswap_try_shrink(struct swap_info_struct *si);
#ifdef CONFIG_SYSFS
static int xswap_create(int percent);
@@ -697,6 +700,9 @@ static void __free_cluster(struct swap_info_struct *si, struct swap_cluster_info
swap_cluster_free_table(ci);
move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
ci->order = 0;
+#ifdef CONFIG_XSWAP
+ xswap_try_shrink(si);
+#endif
}
/*
@@ -3866,6 +3872,40 @@ static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data)
{
return 1;
}
+
+/*
+ * Try to shrink the cluster_info tail: unmap contiguous free clusters
+ * at the end of the mapped range.
+ */
+static void xswap_try_shrink(struct swap_info_struct *si)
+{
+ struct swap_cluster_info *ci;
+ unsigned long last, idx;
+
+ if (!(si->flags & SWP_XSWAP))
+ return;
+ if (READ_ONCE(si->nr_clusters_mapped) <= 1) /* keep cluster 0 */
+ return;
+
+ /* Find the last non-free cluster from the tail */
+ last = READ_ONCE(si->nr_clusters_mapped);
+ while (last > 1) {
+ idx = last - 1;
+ ci = &si->cluster_info[idx];
+ if (ci->count || ci->flags != CLUSTER_FLAG_FREE)
+ break;
+ last = idx;
+ }
+
+ if (last == si->nr_clusters_mapped)
+ return; /* nothing to shrink */
+
+ /* Only unmap if we can free at least one full page of clusters */
+ if (si->nr_clusters_mapped - last < XSWAP_GROW_CLUSTERS)
+ return;
+
+ xswap_unmap_clusters(si, last, si->nr_clusters_mapped - last);
+}
#endif /* CONFIG_XSWAP */
static int setup_swap_clusters_info(struct swap_info_struct *si,
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 09/15] mm, swap: free backing pages in xswap_unmap_clusters
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (7 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 08/15] mm, swap: add xswap_try_shrink and shrink trigger on cluster free Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 10/15] mm, swap: add nr_free_tail for O(1) xswap shrink detection Baoquan He
` (5 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
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 <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 41 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 39 insertions(+), 2 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index a3ba3496ed18..c954313bbf75 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3840,6 +3840,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)
{
@@ -3849,6 +3866,10 @@ 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;
+ int i;
mutex_lock(&si->xswap_lock);
@@ -3858,9 +3879,25 @@ static void xswap_unmap_clusters(struct swap_info_struct *si,
return;
}
+ size = vm_end - vm_start;
+ npages = size >> PAGE_SHIFT;
+
+ 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);
- /* vm_area_unmap_pages() clears PTEs but does not free pages. */
- /* TODO: free backing pages via page table walk or tracking bitmap */
+
+ /* Free the collected backing pages */
+ if (xpd.pages) {
+ 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);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 10/15] mm, swap: add nr_free_tail for O(1) xswap shrink detection
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (8 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 09/15] mm, swap: free backing pages in xswap_unmap_clusters Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 11/15] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap Baoquan He
` (4 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Track contiguous free clusters at the tail of the mapped range in
si->nr_free_tail, maintained across alloc/free/grow paths. This
eliminates the backwards scan on every shrink check.
Three paths maintain the counter:
1. xswap_update_free_tail(): called on cluster free. If the freed
cluster is adjacent to the existing tail boundary, increment and
extend backwards to include already-free clusters now connected.
2. xswap_trim_free_tail(): called on cluster allocation. If the
allocated cluster lies within the tail free region, truncate the
count to end just before it.
3. Grow path: nr_free_tail += added — only the clusters actually
added to the free list extend the tail; a concurrent grower that
lost the race added none.
xswap_try_shrink() simplifies to a threshold check:
if nr_free_tail >= XSWAP_GROW_CLUSTERS → unmap
Setup initializes nr_free_tail = nr_clusters_mapped - 1 (all but
cluster 0 are free at the tail).
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 128 +++++++++++++++++++++++++++++++++++++------
2 files changed, 112 insertions(+), 17 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index c824848c6cfc..e8e6ac5680ff 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -252,6 +252,7 @@ struct swap_info_struct {
struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
unsigned long nr_clusters; /* total cluster count for xswap */
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
+ unsigned long nr_free_tail; /* contiguous free clusters at tail */
struct mutex xswap_lock; /* serialize map/unmap operations */
#endif
struct list_head free_clusters; /* free clusters list */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index c954313bbf75..fa76b8c9bd2d 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -71,6 +71,9 @@ static int xswap_map_clusters(struct swap_info_struct *si,
static void xswap_unmap_clusters(struct swap_info_struct *si,
unsigned long start_idx, unsigned long nr);
static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data);
+static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx);
+static void xswap_update_free_tail(struct swap_info_struct *si,
+ unsigned long freed_idx);
static void xswap_try_shrink(struct swap_info_struct *si);
#ifdef CONFIG_SYSFS
@@ -701,6 +704,7 @@ static void __free_cluster(struct swap_info_struct *si, struct swap_cluster_info
move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
ci->order = 0;
#ifdef CONFIG_XSWAP
+ xswap_update_free_tail(si, ci - si->cluster_info);
xswap_try_shrink(si);
#endif
}
@@ -1049,6 +1053,9 @@ static bool __swap_cluster_alloc_entries(struct swap_info_struct *si,
if (cluster_is_empty(ci))
ci->order = order;
ci->count += nr_pages;
+#ifdef CONFIG_XSWAP
+ xswap_trim_free_tail(si, cluster_index(si, ci));
+#endif
swap_range_alloc(si, nr_pages);
return true;
@@ -3911,37 +3918,124 @@ static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data)
}
/*
- * Try to shrink the cluster_info tail: unmap contiguous free clusters
- * at the end of the mapped range.
+ * Maintain si->nr_free_tail, the number of contiguous free clusters at
+ * the tail of the mapped range. Called when a cluster at @freed_idx is
+ * freed. Provides O(1) shrink detection: if nr_free_tail is non-zero,
+ * the tail can be unmapped without scanning cluster_info[].
+ *
+ * Only increments when @freed_idx is the cluster immediately before the
+ * existing tail region. Then scans backwards for already-free clusters
+ * now connected to the tail, bounded by XSWAP_GROW_CLUSTERS at a time.
*/
-static void xswap_try_shrink(struct swap_info_struct *si)
+static void xswap_update_free_tail(struct swap_info_struct *si,
+ unsigned long freed_idx)
{
+ unsigned long nr_mapped, nr_tail, tid, i;
struct swap_cluster_info *ci;
- unsigned long last, idx;
if (!(si->flags & SWP_XSWAP))
return;
- if (READ_ONCE(si->nr_clusters_mapped) <= 1) /* keep cluster 0 */
+
+ nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_tail = READ_ONCE(si->nr_free_tail);
+
+ /* Protect against concurrent shrink that races past us */
+ if (nr_tail >= nr_mapped)
+ return;
+
+ tid = nr_mapped - nr_tail - 1;
+
+ /* Only the cluster immediately before the tail region counts */
+ if (freed_idx != tid)
return;
- /* Find the last non-free cluster from the tail */
- last = READ_ONCE(si->nr_clusters_mapped);
- while (last > 1) {
- idx = last - 1;
- ci = &si->cluster_info[idx];
- if (ci->count || ci->flags != CLUSTER_FLAG_FREE)
+ nr_tail++;
+ WRITE_ONCE(si->nr_free_tail, nr_tail);
+
+ /* Extend: include already-free clusters now connected to the tail */
+ for (i = 1; i < XSWAP_GROW_CLUSTERS; i++) {
+ nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_tail = READ_ONCE(si->nr_free_tail);
+ if (nr_tail >= nr_mapped - 1)
+ break; /* reached cluster 0 */
+ tid = nr_mapped - nr_tail - 1;
+ ci = &si->cluster_info[tid];
+
+ if (READ_ONCE(ci->count) ||
+ READ_ONCE(ci->flags) != CLUSTER_FLAG_FREE)
break;
- last = idx;
+ nr_tail++;
+ WRITE_ONCE(si->nr_free_tail, nr_tail);
}
+}
- if (last == si->nr_clusters_mapped)
- return; /* nothing to shrink */
+/*
+ * Trim si->nr_free_tail when a cluster in the tail region is allocated.
+ * @idx: index of the cluster being allocated.
+ */
+static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx)
+{
+ unsigned long nr_mapped, nr_tail, tail_start;
- /* Only unmap if we can free at least one full page of clusters */
- if (si->nr_clusters_mapped - last < XSWAP_GROW_CLUSTERS)
+ if (!(si->flags & SWP_XSWAP))
return;
- xswap_unmap_clusters(si, last, si->nr_clusters_mapped - last);
+ /*
+ * nr_clusters_mapped and nr_free_tail are read locklessly;
+ * concurrent updates may cause nr_free_tail to be trimmed
+ * slightly less than ideally, which is harmless.
+ */
+ nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_tail = READ_ONCE(si->nr_free_tail);
+ tail_start = nr_mapped - nr_tail;
+ if (idx >= tail_start)
+ WRITE_ONCE(si->nr_free_tail, nr_mapped - idx - 1);
+}
+
+/*
+ * Try to shrink the cluster_info tail. Uses si->nr_free_tail which
+ * is maintained incrementally during alloc/free — no scanning needed.
+ */
+static void xswap_try_shrink(struct swap_info_struct *si)
+{
+ unsigned long start_idx, nr_unmap, i;
+ struct swap_cluster_info *ci;
+
+ if (!(si->flags & SWP_XSWAP))
+ return;
+ if (si->nr_free_tail < XSWAP_GROW_CLUSTERS)
+ return;
+
+ nr_unmap = round_down(si->nr_free_tail, XSWAP_GROW_CLUSTERS);
+ start_idx = si->nr_clusters_mapped - nr_unmap;
+
+ /*
+ * Verify the tail clusters are still free before unmapping. Count the
+ * contiguous free run first, then detach it in a second pass once the
+ * whole run is confirmed long enough. Detaching while counting would
+ * leave a partial run off the free list if it proved too short to unmap.
+ */
+ spin_lock(&si->lock);
+ for (i = start_idx; i < si->nr_clusters_mapped; i++) {
+ ci = &si->cluster_info[i];
+ if (ci->flags != CLUSTER_FLAG_FREE)
+ break;
+ }
+ nr_unmap = i - start_idx;
+ if (nr_unmap < XSWAP_GROW_CLUSTERS) {
+ spin_unlock(&si->lock);
+ return;
+ }
+
+ for (i = start_idx; i < start_idx + nr_unmap; i++) {
+ ci = &si->cluster_info[i];
+ list_del_init(&ci->list);
+ ci->flags = CLUSTER_FLAG_NONE;
+ }
+ spin_unlock(&si->lock);
+
+ xswap_unmap_clusters(si, start_idx, nr_unmap);
+ si->nr_free_tail -= nr_unmap;
}
#endif /* CONFIG_XSWAP */
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 11/15] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (9 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 10/15] mm, swap: add nr_free_tail for O(1) xswap shrink detection Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 12/15] mm, swap: add debugfs knob for xswap per-device cluster limit Baoquan He
` (3 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Split the xswap cluster limit into two fields:
- nr_clusters_max: immutable hard limit set at device creation
- nr_clusters: current growth ceiling, adjustable at runtime (≤ nr_clusters_max)
The grow path already uses nr_clusters as the ceiling. Shrink now also
respects it: when nr_clusters drops below nr_clusters_mapped, shrinking
fires on free until the mapped count reaches the ceiling. When
nr_clusters == nr_clusters_max (default), shrink is effectively
disabled — all growth and no shrink.
At creation, nr_clusters starts at nr_clusters_max (full size).
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 3 ++-
mm/swapfile.c | 29 +++++++++++++++++++++--------
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index e8e6ac5680ff..93d7771f2427 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -250,7 +250,8 @@ struct swap_info_struct {
struct swap_cluster_info *cluster_info; /* cluster info. Only for SSD */
#ifdef CONFIG_XSWAP
struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
- unsigned long nr_clusters; /* total cluster count for xswap */
+ unsigned long nr_clusters_max;/* upper limit from swap header */
+ unsigned long nr_clusters; /* current growth ceiling (≤ nr_clusters_max) */
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
unsigned long nr_free_tail; /* contiguous free clusters at tail */
struct mutex xswap_lock; /* serialize map/unmap operations */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index fa76b8c9bd2d..be56114a6691 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3228,6 +3228,7 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
free_vm_area(si->cluster_vm);
si->cluster_vm = NULL;
+ si->nr_clusters_max = 0;
si->nr_clusters = 0;
si->nr_clusters_mapped = 0;
return;
@@ -3998,16 +3999,27 @@ static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx)
*/
static void xswap_try_shrink(struct swap_info_struct *si)
{
- unsigned long start_idx, nr_unmap, i;
+ unsigned long nr_mapped, nr_ceiling, nr_tail, nr_unmap;
+ unsigned long start_idx, i;
struct swap_cluster_info *ci;
if (!(si->flags & SWP_XSWAP))
return;
- if (si->nr_free_tail < XSWAP_GROW_CLUSTERS)
+
+ nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_ceiling = READ_ONCE(si->nr_clusters);
+ nr_tail = READ_ONCE(si->nr_free_tail);
+
+ if (nr_mapped <= nr_ceiling)
+ return;
+ if (nr_tail < XSWAP_GROW_CLUSTERS)
return;
- nr_unmap = round_down(si->nr_free_tail, XSWAP_GROW_CLUSTERS);
- start_idx = si->nr_clusters_mapped - nr_unmap;
+ nr_unmap = min(round_down(nr_tail, XSWAP_GROW_CLUSTERS),
+ nr_mapped - nr_ceiling);
+ if (nr_unmap < XSWAP_GROW_CLUSTERS)
+ return;
+ start_idx = nr_mapped - nr_unmap;
/*
* Verify the tail clusters are still free before unmapping. Count the
@@ -4016,7 +4028,7 @@ static void xswap_try_shrink(struct swap_info_struct *si)
* leave a partial run off the free list if it proved too short to unmap.
*/
spin_lock(&si->lock);
- for (i = start_idx; i < si->nr_clusters_mapped; i++) {
+ for (i = start_idx; i < nr_mapped; i++) {
ci = &si->cluster_info[i];
if (ci->flags != CLUSTER_FLAG_FREE)
break;
@@ -4035,7 +4047,7 @@ static void xswap_try_shrink(struct swap_info_struct *si)
spin_unlock(&si->lock);
xswap_unmap_clusters(si, start_idx, nr_unmap);
- si->nr_free_tail -= nr_unmap;
+ WRITE_ONCE(si->nr_free_tail, nr_tail - nr_unmap);
}
#endif /* CONFIG_XSWAP */
@@ -4059,6 +4071,7 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
cluster_info = vm->addr;
si->cluster_vm = vm;
+ si->nr_clusters_max = nr_clusters;
si->nr_clusters = nr_clusters;
si->cluster_info = cluster_info;
@@ -4093,6 +4106,8 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
}
}
+ /* All mapped clusters except cluster 0 are free at the tail */
+ si->nr_free_tail = si->nr_clusters_mapped - 1;
mutex_init(&si->xswap_lock);
return 0;
@@ -4586,7 +4601,6 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
static int __init swapfile_init(void)
{
swapfile_maximum_size = arch_max_swapfile_size();
-
/*
* Once a cluster is freed, it's swap table content is read
* only, and all swap cache readers (swap_cache_*) verifies
@@ -4596,7 +4610,6 @@ static int __init swapfile_init(void)
swap_table_cachep = kmem_cache_create("swap_table",
sizeof(struct swap_table),
0, SLAB_PANIC | SLAB_TYPESAFE_BY_RCU, NULL);
-
#ifdef CONFIG_MIGRATION
if (swapfile_maximum_size >= (1UL << SWP_MIG_TOTAL_BITS))
swap_migration_ad_supported = true;
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 12/15] mm, swap: add debugfs knob for xswap per-device cluster limit
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (10 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 11/15] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 13/15] mm, swap: defer xswap shrink to workqueue to avoid lock recursion Baoquan He
` (2 subsequent siblings)
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
Add a per-device debugfs file for runtime adjustment of xswap cluster
limit:
/sys/kernel/debug/xswap/type<N>_cluster_limit
Reading shows the current ceiling (in clusters); writing sets it
(clamped to [0, nr_clusters_max]). Setting below nr_clusters_mapped
triggers an immediate shrink check via xswap_try_shrink().
The debugfs entry is created on device creation and removed on
destruction.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 90 ++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 88 insertions(+), 3 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 93d7771f2427..4d8ca920d2f9 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -254,6 +254,7 @@ struct swap_info_struct {
unsigned long nr_clusters; /* current growth ceiling (⤠nr_clusters_max) */
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
unsigned long nr_free_tail; /* contiguous free clusters at tail */
+ struct dentry *debugfs_entry; /* debugfs: type<N>_max_clusters */
struct mutex xswap_lock; /* serialize map/unmap operations */
#endif
struct list_head free_clusters; /* free clusters list */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index be56114a6691..2755a549582c 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -50,6 +50,9 @@
#include "swap_table.h"
#include "internal.h"
#include "swap.h"
+#include <linux/debugfs.h>
+
+static DEFINE_SPINLOCK(swap_lock);
#ifdef CONFIG_XSWAP
/*
@@ -66,6 +69,8 @@
max_t(unsigned long, PAGE_SIZE / sizeof(struct swap_cluster_info), 16)
#define XSWAP_DEFAULT_CLUSTER_PERCENT 30
+static struct dentry *xswap_debugfs_root;
+
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,
@@ -76,6 +81,83 @@ static void xswap_update_free_tail(struct swap_info_struct *si,
unsigned long freed_idx);
static void xswap_try_shrink(struct swap_info_struct *si);
+/*
+ * debugfs read/write for per-device max cluster count.
+ */
+static ssize_t xswap_max_clusters_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct swap_info_struct *si = file->private_data;
+ char tmp[32];
+ int len;
+
+ len = snprintf(tmp, sizeof(tmp), "%lu\n", READ_ONCE(si->nr_clusters));
+ return simple_read_from_buffer(buf, count, ppos, tmp, len);
+}
+
+static ssize_t xswap_max_clusters_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct swap_info_struct *si = file->private_data;
+ unsigned long val, new_pages;
+ int err;
+
+ err = kstrtoul_from_user(buf, count, 0, &val);
+ if (err)
+ return err;
+
+ if (val > si->nr_clusters_max)
+ val = si->nr_clusters_max;
+
+ spin_lock(&si->lock);
+ si->nr_clusters = val;
+ spin_unlock(&si->lock);
+
+ new_pages = min_t(unsigned long, val * SWAPFILE_CLUSTER, si->max);
+ if (new_pages)
+ new_pages--;
+ if (new_pages != si->pages) {
+ long delta = (long)new_pages - (long)si->pages;
+
+ spin_lock(&swap_lock);
+ si->pages = new_pages;
+ atomic_long_add(delta, &nr_swap_pages);
+ total_swap_pages += delta;
+ spin_unlock(&swap_lock);
+ }
+
+ /* Lowering the ceiling may free tail clusters. */
+ xswap_try_shrink(si);
+
+ return count;
+}
+
+static const struct file_operations xswap_debugfs_fops = {
+ .read = xswap_max_clusters_read,
+ .write = xswap_max_clusters_write,
+ .open = simple_open,
+ .llseek = default_llseek,
+};
+
+static void xswap_debugfs_add(struct swap_info_struct *si)
+{
+ char name[32];
+
+ if (!xswap_debugfs_root)
+ return;
+
+ snprintf(name, sizeof(name), "type%d_cluster_limit", si->type);
+ si->debugfs_entry = debugfs_create_file(name, 0644, xswap_debugfs_root,
+ si, &xswap_debugfs_fops);
+}
+
+static void xswap_debugfs_del(struct swap_info_struct *si)
+{
+ debugfs_remove(si->debugfs_entry);
+ si->debugfs_entry = NULL;
+}
+
#ifdef CONFIG_SYSFS
static int xswap_create(int percent);
/* /sys/kernel/mm/xswap/: create.
@@ -156,7 +238,6 @@ static void move_cluster(struct swap_info_struct *si,
*
* Also protects swap_active_head total_swap_pages, and the SWP_WRITEOK flag.
*/
-static DEFINE_SPINLOCK(swap_lock);
static unsigned int nr_swapfiles;
atomic_long_t nr_swap_pages;
atomic_t nr_real_swapfiles;
@@ -3223,6 +3304,7 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
#ifdef CONFIG_XSWAP
if (si->flags & SWP_XSWAP) {
+ xswap_debugfs_del(si);
/* Unmap all mapped clusters and free the VM_SPARSE area */
if (si->nr_clusters_mapped > 0)
xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
@@ -4109,6 +4191,7 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
/* All mapped clusters except cluster 0 are free at the tail */
si->nr_free_tail = si->nr_clusters_mapped - 1;
mutex_init(&si->xswap_lock);
+ xswap_debugfs_add(si);
return 0;
err_unmap:
@@ -4236,8 +4319,8 @@ static int xswap_create(int percent)
/* VM_SPARSE covers full RAM; runtime nr_clusters starts at percent. */
init_clusters = div_u64((u64)maxpages * percent, 100 * SWAPFILE_CLUSTER);
init_clusters = max_t(unsigned long, init_clusters, XSWAP_GROW_CLUSTERS);
- if (init_clusters > si->nr_clusters)
- init_clusters = si->nr_clusters;
+ if (init_clusters > si->nr_clusters_max)
+ init_clusters = si->nr_clusters_max;
si->nr_clusters = init_clusters;
si->pages = min_t(unsigned long, init_clusters * SWAPFILE_CLUSTER, si->max) - 1;
@@ -4615,6 +4698,7 @@ static int __init swapfile_init(void)
swap_migration_ad_supported = true;
#endif /* CONFIG_MIGRATION */
+ xswap_debugfs_root = debugfs_create_dir("xswap", NULL);
xswap_sysfs_init();
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 13/15] mm, swap: defer xswap shrink to workqueue to avoid lock recursion
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (11 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 12/15] mm, swap: add debugfs knob for xswap per-device cluster limit Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 14/15] mm, swap: refactor swapoff + add xswap_destroy Baoquan He
2026-08-13 10:48 ` [RFC v3 15/15] mm, swap: require zswap for xswap devices Baoquan He
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
xswap_try_shrink() was called directly from __free_cluster() while
holding ci->lock. The shrink path calls xswap_unmap_clusters()
which unmaps vmalloc pages backing cluster_info, and on return
swap_cache_del_folio() tries swap_cluster_unlock(ci) on the now-
unmapped address — crashing on a not-present page.
Replace direct calls with schedule_work() so shrink runs in an
independent workqueue context where no cluster locks are held.
Use cancel_work_sync() during swapoff to ensure no pending shrink
work races with the VM area teardown.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 12 +++++++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 4d8ca920d2f9..d3a68bd45c82 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -255,6 +255,7 @@ struct swap_info_struct {
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
unsigned long nr_free_tail; /* contiguous free clusters at tail */
struct dentry *debugfs_entry; /* debugfs: type<N>_max_clusters */
+ struct work_struct xswap_shrink_work; /* deferred shrink trigger */
struct mutex xswap_lock; /* serialize map/unmap operations */
#endif
struct list_head free_clusters; /* free clusters list */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 2755a549582c..aa83a76773fe 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -786,7 +786,7 @@ static void __free_cluster(struct swap_info_struct *si, struct swap_cluster_info
ci->order = 0;
#ifdef CONFIG_XSWAP
xswap_update_free_tail(si, ci - si->cluster_info);
- xswap_try_shrink(si);
+ schedule_work(&si->xswap_shrink_work);
#endif
}
@@ -3305,6 +3305,7 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
#ifdef CONFIG_XSWAP
if (si->flags & SWP_XSWAP) {
xswap_debugfs_del(si);
+ cancel_work_sync(&si->xswap_shrink_work);
/* Unmap all mapped clusters and free the VM_SPARSE area */
if (si->nr_clusters_mapped > 0)
xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
@@ -4075,6 +4076,13 @@ static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx)
WRITE_ONCE(si->nr_free_tail, nr_mapped - idx - 1);
}
+static void xswap_shrink_work_fn(struct work_struct *work)
+{
+ struct swap_info_struct *si = container_of(work,
+ struct swap_info_struct, xswap_shrink_work);
+ xswap_try_shrink(si);
+}
+
/*
* Try to shrink the cluster_info tail. Uses si->nr_free_tail which
* is maintained incrementally during alloc/free — no scanning needed.
@@ -4190,7 +4198,9 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
/* All mapped clusters except cluster 0 are free at the tail */
si->nr_free_tail = si->nr_clusters_mapped - 1;
+
mutex_init(&si->xswap_lock);
+ INIT_WORK(&si->xswap_shrink_work, xswap_shrink_work_fn);
xswap_debugfs_add(si);
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 14/15] mm, swap: refactor swapoff + add xswap_destroy
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (12 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 13/15] mm, swap: defer xswap shrink to workqueue to avoid lock recursion Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 15/15] mm, swap: require zswap for xswap devices Baoquan He
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
1. Extract __swapoff() from sys_swapoff(): the core teardown logic
now lives in __swapoff(), shared by sys_swapoff() and the new
xswap_destroy(). swap_file operations are guarded with NULL check
so __swapoff() works for file-less devices too. sys_swapoff()
retains file-matching; a NULL guard on p->swap_file ensures xswap
devices are never matched by the file path.
2. Add xswap_destroy(int type): tears down a file-less xswap device
by its swap type. Validates SWP_XSWAP | SWP_WRITEOK, removes
from lists, delegates to __swapoff().
3. Add /sys/kernel/mm/xswap/destroy: write a swap type to tear down
that xswap device. Requires CAP_SYS_ADMIN.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 197 ++++++++++++++++++++++++++++++++++----------------
1 file changed, 136 insertions(+), 61 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index aa83a76773fe..61c307b6a432 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -160,6 +160,7 @@ static void xswap_debugfs_del(struct swap_info_struct *si)
#ifdef CONFIG_SYSFS
static int xswap_create(int percent);
+static int xswap_destroy(int type);
/* /sys/kernel/mm/xswap/: create.
* Per-device runtime size is tuned via debugfs type<N>_cluster_limit.
*/
@@ -192,8 +193,33 @@ static ssize_t xswap_create_store(struct kobject *kobj,
static struct kobj_attribute xswap_create_attr = __ATTR(create, 0200, NULL,
xswap_create_store);
+static ssize_t xswap_destroy_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long type;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ err = kstrtoul(buf, 0, &type);
+ if (err)
+ return err;
+
+ err = xswap_destroy(type);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static struct kobj_attribute xswap_destroy_attr = __ATTR(destroy, 0200, NULL,
+ xswap_destroy_store);
+
static struct attribute *xswap_attrs[] = {
&xswap_create_attr.attr,
+ &xswap_destroy_attr.attr,
NULL,
};
@@ -3354,61 +3380,13 @@ static void flush_percpu_swap_cluster(struct swap_info_struct *si)
}
-SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
+/* Common swap teardown after list removal; shared by sys_swapoff() and
+ * xswap_destroy().
+ */
+static int __swapoff(struct swap_info_struct *p)
{
- struct swap_info_struct *p = NULL;
- struct file *swap_file, *victim;
- struct address_space *mapping;
- struct inode *inode;
- int err, found = 0;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- BUG_ON(!current->mm);
-
- CLASS(filename, pathname)(specialfile);
- victim = file_open_name(pathname, O_RDWR|O_LARGEFILE, 0);
- if (IS_ERR(victim))
- return PTR_ERR(victim);
-
- mapping = victim->f_mapping;
- spin_lock(&swap_lock);
- plist_for_each_entry(p, &swap_active_head, list) {
- if (p->flags & SWP_WRITEOK) {
- if (p->swap_file->f_mapping == mapping) {
- found = 1;
- break;
- }
- }
- }
- if (!found) {
- err = -EINVAL;
- spin_unlock(&swap_lock);
- goto out_dput;
- }
-
- /* Refuse swapoff while the device is pinned for hibernation */
- if (p->flags & SWP_HIBERNATION) {
- err = -EBUSY;
- spin_unlock(&swap_lock);
- goto out_dput;
- }
-
- if (!security_vm_enough_memory_mm(current->mm, p->pages))
- vm_unacct_memory(p->pages);
- else {
- err = -ENOMEM;
- spin_unlock(&swap_lock);
- goto out_dput;
- }
- spin_lock(&p->lock);
- del_from_avail_list(p, true);
- plist_del(&p->list, &swap_active_head);
- atomic_long_sub(p->pages, &nr_swap_pages);
- total_swap_pages -= p->pages;
- spin_unlock(&p->lock);
- spin_unlock(&swap_lock);
+ struct file *swap_file = NULL;
+ int err;
wait_for_allocation(p);
@@ -3419,7 +3397,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
if (err) {
/* re-insert swap space back into swap_list */
reinsert_swap_info(p);
- goto out_dput;
+ return err;
}
/*
@@ -3462,12 +3440,14 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
p->max = 0;
p->cluster_info = NULL;
- inode = mapping->host;
+ if (swap_file) {
+ struct inode *inode = swap_file->f_mapping->host;
- inode_lock(inode);
- inode->i_flags &= ~S_SWAPFILE;
- inode_unlock(inode);
- filp_close(swap_file, NULL);
+ inode_lock(inode);
+ inode->i_flags &= ~S_SWAPFILE;
+ inode_unlock(inode);
+ filp_close(swap_file, NULL);
+ }
/*
* Clear the SWP_USED flag after all resources are freed so that swapon
@@ -3478,10 +3458,69 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
p->flags = 0;
spin_unlock(&swap_lock);
- err = 0;
atomic_inc(&proc_poll_event);
wake_up_interruptible(&proc_poll_wait);
+ return 0;
+}
+
+SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
+{
+ struct swap_info_struct *p = NULL;
+ struct file *victim;
+ struct address_space *mapping;
+ int err, found = 0;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ BUG_ON(!current->mm);
+
+ CLASS(filename, pathname)(specialfile);
+ victim = file_open_name(pathname, O_RDWR|O_LARGEFILE, 0);
+ if (IS_ERR(victim))
+ return PTR_ERR(victim);
+
+ mapping = victim->f_mapping;
+ spin_lock(&swap_lock);
+ plist_for_each_entry(p, &swap_active_head, list) {
+ if (p->flags & SWP_WRITEOK) {
+ if (p->swap_file && p->swap_file->f_mapping == mapping) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ err = -EINVAL;
+ spin_unlock(&swap_lock);
+ goto out_dput;
+ }
+
+ /* Refuse swapoff while the device is pinned for hibernation */
+ if (p->flags & SWP_HIBERNATION) {
+ err = -EBUSY;
+ spin_unlock(&swap_lock);
+ goto out_dput;
+ }
+
+ if (!security_vm_enough_memory_mm(current->mm, p->pages))
+ vm_unacct_memory(p->pages);
+ else {
+ err = -ENOMEM;
+ spin_unlock(&swap_lock);
+ goto out_dput;
+ }
+ spin_lock(&p->lock);
+ del_from_avail_list(p, true);
+ plist_del(&p->list, &swap_active_head);
+ atomic_long_sub(p->pages, &nr_swap_pages);
+ total_swap_pages -= p->pages;
+ spin_unlock(&p->lock);
+ spin_unlock(&swap_lock);
+
+ err = __swapoff(p);
+
out_dput:
filp_close(victim, NULL);
return err;
@@ -4364,6 +4403,42 @@ static int xswap_create(int percent)
spin_unlock(&swap_lock);
return error;
}
+
+/* Tear down a file-less xswap device by its swap type. */
+static int xswap_destroy(int type)
+{
+ struct swap_info_struct *p;
+
+ p = swap_type_to_info(type);
+ if (!p)
+ return -EINVAL;
+
+ spin_lock(&swap_lock);
+ if (!(p->flags & SWP_WRITEOK) || !(p->flags & SWP_XSWAP)) {
+ spin_unlock(&swap_lock);
+ return -EINVAL;
+ }
+ /* Refuse swapoff while the device is pinned for hibernation */
+ if (p->flags & SWP_HIBERNATION) {
+ spin_unlock(&swap_lock);
+ return -EBUSY;
+ }
+ if (!security_vm_enough_memory_mm(current->mm, p->pages))
+ vm_unacct_memory(p->pages);
+ else {
+ spin_unlock(&swap_lock);
+ return -ENOMEM;
+ }
+ spin_lock(&p->lock);
+ del_from_avail_list(p, true);
+ plist_del(&p->list, &swap_active_head);
+ atomic_long_sub(p->pages, &nr_swap_pages);
+ total_swap_pages -= p->pages;
+ spin_unlock(&p->lock);
+ spin_unlock(&swap_lock);
+
+ return __swapoff(p);
+}
#endif /* CONFIG_SYSFS */
#endif /* CONFIG_XSWAP */
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [RFC v3 15/15] mm, swap: require zswap for xswap devices
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
` (13 preceding siblings ...)
2026-08-13 10:48 ` [RFC v3 14/15] mm, swap: refactor swapoff + add xswap_destroy Baoquan He
@ 2026-08-13 10:48 ` Baoquan He
14 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-13 10:48 UTC (permalink / raw)
To: linux-mm
Cc: chrisl, nphamcs, kasong, baohua, youngjun.park, hannes, yosry,
shikemeng, chengming.zhou, baoquan.he, linux-kernel, Baoquan He
xswap has no backing storage: swapped-out pages live only in zswap.
Without zswap, swapout always bounces back, so the device would
consume swap entry space without ever freeing memory. Refuse to
create a device when zswap is unavailable.
Runtime disabling of zswap after creation is safe: existing entries
stay loadable (zswap_load() gates on zswap_never_enabled(), not the
runtime zswap_enabled flag) and new swapouts merely bounce back to
memory without freeing it.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 61c307b6a432..911bf3b8c863 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -4340,6 +4340,10 @@ static int xswap_create(int percent)
if (percent < 1 || percent > 100)
return -EINVAL;
+ /* xswap has no backing store, it relies on zswap. */
+ if (!zswap_is_enabled())
+ return -EOPNOTSUPP;
+
si = alloc_swap_info();
if (IS_ERR(si))
return PTR_ERR(si);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc
2026-08-13 10:48 ` [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
@ 2026-08-14 23:09 ` Klara Modin
2026-08-17 0:46 ` Baoquan He
0 siblings, 1 reply; 18+ messages in thread
From: Klara Modin @ 2026-08-14 23:09 UTC (permalink / raw)
To: Baoquan He
Cc: linux-mm, chrisl, nphamcs, kasong, baohua, youngjun.park, hannes,
yosry, shikemeng, chengming.zhou, baoquan.he, linux-kernel
Hi,
On 2026-08-13 18:48:44 +0800, Baoquan He wrote:
> Implement dynamic cluster_info array growth for xswap devices using a
> VM_SPARSE vmalloc area:
>
> 1. xswap_map_clusters(): Allocate physical pages and map them into
> the pre-reserved VM_SPARSE KVA region via vm_area_map_pages().
>
> 2. xswap_unmap_clusters(): Unmap pages from the VM_SPARSE area via
> vm_area_unmap_pages() (used by the error/teardown paths, shrink
> comes later).
>
> 3. setup_swap_clusters_info() xswap path: Use get_vm_area(VM_SPARSE)
> for the cluster_info array, lazily mapping only the initial chunk.
>
> 4. free_swap_cluster_info() xswap path: Unmap all clusters and
> free_vm_area(). Built on the refactoring in the previous patch.
>
> 5. wait_for_allocation() xswap guard: Skip shrinker-unmapped clusters
> beyond nr_clusters_mapped.
>
> The grow path avoids emergency reserves via __GFP_HIGH|__GFP_NOMEMALLOC
> and wraps allocations with memalloc_noreclaim_save(). A per-device
> mutex (xswap_lock) serializes concurrent map/unmap page table
> modifications.
>
> Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
> ---
> include/linux/swap.h | 1 +
> mm/swapfile.c | 257 ++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 256 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 7ffc62a3b2d7..c824848c6cfc 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -252,6 +252,7 @@ struct swap_info_struct {
> struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
> unsigned long nr_clusters; /* total cluster count for xswap */
> unsigned long nr_clusters_mapped; /* currently mapped cluster count */
> + struct mutex xswap_lock; /* serialize map/unmap operations */
> #endif
> struct list_head free_clusters; /* free clusters list */
> struct list_head full_clusters; /* full clusters list */
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 4ce30e9ecdf6..178c3b798f8e 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -49,6 +49,25 @@
> #include "internal.h"
> #include "swap.h"
>
> +#ifdef CONFIG_XSWAP
> +/*
> + * xswap: dynamically grow the cluster_info array via a VM_SPARSE area.
> + *
> + * XSWAP_GROW_CLUSTERS is the number of clusters to map in one grow
> + * operation. It is set to the number of cluster_info structs that
> + * fit in a single page (at least 16), so that the vmalloc page table
> + * overhead is proportional to the number of clusters mapped.
> + */
> +#define XSWAP_GROW_CLUSTERS \
> + max_t(unsigned long, PAGE_SIZE / sizeof(struct swap_cluster_info), 16)
> +
> +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_check_mapped(pte_t *pte, unsigned long addr, void *data);
> +#endif
> +
> static void swap_range_alloc(struct swap_info_struct *si,
> unsigned int nr_entries);
> static bool folio_swapcache_freeable(struct folio *folio);
> @@ -2708,15 +2727,27 @@ static unsigned int find_next_to_unuse(struct swap_info_struct *si,
> unsigned int prev)
> {
> unsigned int i;
> + unsigned int end = si->max;
> unsigned long swp_tb;
>
> +#ifdef CONFIG_XSWAP
> + /* xswap may have shrunk and unmapped the cluster_info tail. */
> + if (si->flags & SWP_XSWAP) {
> + unsigned long mapped_end;
> +
> + mapped_end = READ_ONCE(si->nr_clusters_mapped) * SWAPFILE_CLUSTER;
> + if (mapped_end < end)
> + end = mapped_end;
> + }
> +#endif
> +
> /*
> * No need for swap_lock here: we're just looking
> * for whether an entry is in use, not modifying it; false
> * hits are okay, and sys_swapoff() has already prevented new
> * allocations from this area (while holding swap_lock).
> */
> - for (i = prev + 1; i < si->max; i++) {
> + for (i = prev + 1; i < end; i++) {
> swp_tb = swap_table_get(__swap_offset_to_cluster(si, i),
> i % SWAPFILE_CLUSTER);
> if (!swp_tb_is_null(swp_tb) && !swp_tb_is_bad(swp_tb))
> @@ -2725,7 +2756,7 @@ static unsigned int find_next_to_unuse(struct swap_info_struct *si,
> cond_resched();
> }
>
> - if (i == si->max)
> + if (i == end)
> i = 0;
>
> return i;
> @@ -3041,6 +3072,13 @@ static void wait_for_allocation(struct swap_info_struct *si)
>
> BUG_ON(si->flags & SWP_WRITEOK);
>
> +#ifdef CONFIG_XSWAP
> + /* Skip shrinker-unmapped cluster tail. */
> + if (si->flags & SWP_XSWAP)
> + end = min(end, READ_ONCE(si->nr_clusters_mapped) *
> + SWAPFILE_CLUSTER);
> +#endif
> +
> for (offset = 0; offset < end; offset += SWAPFILE_CLUSTER) {
> ci = swap_cluster_lock(si, offset);
> swap_cluster_unlock(ci);
> @@ -3057,6 +3095,19 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
> if (!cluster_info)
> return;
>
> +#ifdef CONFIG_XSWAP
> + if (si->flags & SWP_XSWAP) {
> + /* Unmap all mapped clusters and free the VM_SPARSE area */
> + if (si->nr_clusters_mapped > 0)
> + xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
> + free_vm_area(si->cluster_vm);
> + si->cluster_vm = NULL;
> + si->nr_clusters = 0;
> + si->nr_clusters_mapped = 0;
> + return;
> + }
> +#endif
> +
> nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
> for (i = 0; i < nr_clusters; i++) {
> ci = cluster_info + i;
> @@ -3553,6 +3604,150 @@ static unsigned long read_swap_header(struct swap_info_struct *si,
> return maxpages;
> }
>
> +#ifdef CONFIG_XSWAP
> +static int xswap_map_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);
> + unsigned long end_addr = start_addr + (size_t)nr * sizeof(struct swap_cluster_info);
> + /* Round to page boundaries for vm_area_map_pages(). */
> + unsigned long vm_start = PAGE_ALIGN(start_addr);
> + unsigned long vm_end = PAGE_ALIGN(end_addr);
> + unsigned int noreclaim_flags;
> + unsigned long npages;
> + struct page **pages;
> + unsigned long i;
> + int err;
> +
> + mutex_lock(&si->xswap_lock);
> +
> + if (vm_start >= vm_end) {
> + /* All requested clusters fall within already-mapped pages. */
> + for (i = start_idx; i < start_idx + nr; i++)
> + spin_lock_init(&si->cluster_info[i].lock);
> + WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
> + mutex_unlock(&si->xswap_lock);
> + return 0;
> + }
> +
> + npages = (vm_end - vm_start) >> PAGE_SHIFT;
> +
> + /* Prevent recursive reclaim during vmap page table allocation. */
> + noreclaim_flags = memalloc_noreclaim_save();
> +
> + pages = kmalloc_array(npages, sizeof(*pages),
> + __GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL);
> + if (!pages) {
> + memalloc_noreclaim_restore(noreclaim_flags);
> + mutex_unlock(&si->xswap_lock);
> + return -ENOMEM;
> + }
> +
> + for (i = 0; i < npages; i++) {
> + /* __GFP_ZERO: cluster_info pointer fields must start NULL. */
> + pages[i] = alloc_page(__GFP_HIGH | __GFP_NOMEMALLOC |
> + GFP_KERNEL | __GFP_ZERO);
> + if (!pages[i])
> + goto fail;
> + }
> +
> + /* Detect racing grower that already mapped these pages. */
> + if (apply_to_existing_page_range(&init_mm, vm_start,
> + vm_end - vm_start,
> + xswap_check_mapped, NULL)) {
> + i = npages;
> + goto fail_nounmap;
> + }
> +
> + err = vm_area_map_pages(si->cluster_vm, vm_start, vm_end, pages);
> + if (err) {
> + /* -EBUSY: defensive, the page was already mapped. */
> + if (err == -EBUSY) {
> + i = npages;
> + goto fail_nounmap;
> + }
> + i = npages;
> + goto fail;
> + }
> +
> + kfree(pages);
> + memalloc_noreclaim_restore(noreclaim_flags);
> +
> + /* Initialize spinlocks for newly mapped clusters */
> + for (i = start_idx; i < start_idx + nr; i++)
> + spin_lock_init(&si->cluster_info[i].lock);
> +
> + /*
> + * Pairs with READ_ONCE() in shrink/grow paths.
> + */
> + WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
> + mutex_unlock(&si->xswap_lock);
> + return 0;
> +
> +fail_nounmap:
> + /*
> + * The concurrent grower already mapped the range, initialized the
> + * cluster spinlocks and advanced nr_clusters_mapped. It may still
> + * be holding those locks while adding clusters to the free list, so
> + * do not touch them here; just free our unused pages.
> + */
> + while (i > 0) {
> + i--;
> + if (pages[i])
> + __free_page(pages[i]);
> + }
> + kfree(pages);
> + memalloc_noreclaim_restore(noreclaim_flags);
> + mutex_unlock(&si->xswap_lock);
> + return 0;
> +
> +fail:
> + while (i > 0) {
> + i--;
> + if (pages[i])
> + __free_page(pages[i]);
> + }
> + memalloc_noreclaim_restore(noreclaim_flags);
> + kfree(pages);
> + mutex_unlock(&si->xswap_lock);
> + return -ENOMEM;
> +}
> +
> +static void 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);
> + unsigned long end_addr = start_addr + (size_t)nr * sizeof(struct swap_cluster_info);
> + /* 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);
> +
> + mutex_lock(&si->xswap_lock);
> +
> + if (vm_start >= vm_end) {
> + WRITE_ONCE(si->nr_clusters_mapped, start_idx);
> + mutex_unlock(&si->xswap_lock);
> + return;
> + }
> +
> + 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 */
> +
> + /* Pairs with READ_ONCE() in shrink/grow paths. */
> + WRITE_ONCE(si->nr_clusters_mapped, start_idx);
> + mutex_unlock(&si->xswap_lock);
> +}
> +
> +/* Return 1 at first present PTE to signal range is already mapped. */
> +static int xswap_check_mapped(pte_t *pte, unsigned long addr, void *data)
> +{
> + return 1;
> +}
> +#endif /* CONFIG_XSWAP */
> +
> static int setup_swap_clusters_info(struct swap_info_struct *si,
> union swap_header *swap_header,
> unsigned long maxpages)
> @@ -3562,6 +3757,64 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
> int err = -ENOMEM;
> unsigned long i;
>
> +#ifdef CONFIG_XSWAP
> + if (si->flags & SWP_XSWAP) {
> + unsigned long size = PAGE_ALIGN(nr_clusters * sizeof(*cluster_info));
> + struct vm_struct *vm;
> +
> + vm = get_vm_area(size, VM_SPARSE);
> + if (!vm)
> + goto err;
> +
> + cluster_info = vm->addr;
> + si->cluster_vm = vm;
> + si->nr_clusters = nr_clusters;
> + si->cluster_info = cluster_info;
Should probably initialise the mutex here instead since
xswap_map_clusters() uses it?
> +
> + /* Map the initial chunk (at least cluster 0) */
> + if (xswap_map_clusters(si, 0, min_t(unsigned long,
> + XSWAP_GROW_CLUSTERS, nr_clusters)))
> + goto err_free_vm;
> +
> + /* xswap: only cluster 0 slot 0 is bad */
> + err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
> + if (err)
> + goto err_unmap;
> +
> + 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]);
> + }
> +
> + /* Mark mapped clusters: cluster 0 has 1 bad slot, rest free */
> + for (i = 0; i < si->nr_clusters_mapped; i++) {
> + struct swap_cluster_info *ci = &cluster_info[i];
> +
> + if (i == 0) {
> + ci->flags = CLUSTER_FLAG_NONFULL;
> + list_add_tail(&ci->list, &si->nonfull_clusters[0]);
> + } else {
> + ci->flags = CLUSTER_FLAG_FREE;
> + list_add_tail(&ci->list, &si->free_clusters);
> + }
> + }
> +
> + mutex_init(&si->xswap_lock);
> + return 0;
> +
> +err_unmap:
> + xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
> +err_free_vm:
> + free_vm_area(si->cluster_vm);
> + si->cluster_vm = NULL;
> + si->cluster_info = NULL;
> + return err;
> + }
> +#endif /* CONFIG_XSWAP */
> +
> cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
> if (!cluster_info)
> goto err;
> --
> 2.54.0
>
Regards,
Klara Modin
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc
2026-08-14 23:09 ` Klara Modin
@ 2026-08-17 0:46 ` Baoquan He
0 siblings, 0 replies; 18+ messages in thread
From: Baoquan He @ 2026-08-17 0:46 UTC (permalink / raw)
To: Klara Modin
Cc: Baoquan He, linux-mm, chrisl, nphamcs, kasong, baohua,
youngjun.park, hannes, yosry, shikemeng, chengming.zhou,
linux-kernel
On 08/15/26 at 01:09am, Klara Modin wrote:
> Hi,
>
> On 2026-08-13 18:48:44 +0800, Baoquan He wrote:
> > Implement dynamic cluster_info array growth for xswap devices using a
> > VM_SPARSE vmalloc area:
> >
......snip....
> > @@ -3562,6 +3757,64 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
> > int err = -ENOMEM;
> > unsigned long i;
> >
> > +#ifdef CONFIG_XSWAP
> > + if (si->flags & SWP_XSWAP) {
> > + unsigned long size = PAGE_ALIGN(nr_clusters * sizeof(*cluster_info));
> > + struct vm_struct *vm;
> > +
> > + vm = get_vm_area(size, VM_SPARSE);
> > + if (!vm)
> > + goto err;
> > +
> > + cluster_info = vm->addr;
> > + si->cluster_vm = vm;
> > + si->nr_clusters = nr_clusters;
> > + si->cluster_info = cluster_info;
>
> Should probably initialise the mutex here instead since
> xswap_map_clusters() uses it?
Many thanks for reviewing, and you are absolutely right. A real bug is
caught, I will wrap up the fix in the next version as you suggested.
Thanks
Baoquan
>
> > +
> > + /* Map the initial chunk (at least cluster 0) */
> > + if (xswap_map_clusters(si, 0, min_t(unsigned long,
> > + XSWAP_GROW_CLUSTERS, nr_clusters)))
> > + goto err_free_vm;
>
> > +
> > + /* xswap: only cluster 0 slot 0 is bad */
> > + err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
> > + if (err)
> > + goto err_unmap;
> > +
> > + 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]);
> > + }
> > +
> > + /* Mark mapped clusters: cluster 0 has 1 bad slot, rest free */
> > + for (i = 0; i < si->nr_clusters_mapped; i++) {
> > + struct swap_cluster_info *ci = &cluster_info[i];
> > +
> > + if (i == 0) {
> > + ci->flags = CLUSTER_FLAG_NONFULL;
> > + list_add_tail(&ci->list, &si->nonfull_clusters[0]);
> > + } else {
> > + ci->flags = CLUSTER_FLAG_FREE;
> > + list_add_tail(&ci->list, &si->free_clusters);
> > + }
> > + }
> > +
> > + mutex_init(&si->xswap_lock);
> > + return 0;
> > +
> > +err_unmap:
> > + xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
> > +err_free_vm:
> > + free_vm_area(si->cluster_vm);
> > + si->cluster_vm = NULL;
> > + si->cluster_info = NULL;
> > + return err;
> > + }
> > +#endif /* CONFIG_XSWAP */
> > +
> > cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
> > if (!cluster_info)
> > goto err;
> > --
> > 2.54.0
> >
>
> Regards,
> Klara Modin
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-08-17 0:47 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 10:48 [RFC v3 00/15] xswap: extendable (virtual) swap device backed by zswap Baoquan He
2026-08-13 10:48 ` [RFC v3 01/15] mm: zswap: return -ENOENT when the swap device is gone Baoquan He
2026-08-13 10:48 ` [RFC v3 02/15] mm: xswap support for zswap Baoquan He
2026-08-13 10:48 ` [RFC v3 03/15] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Baoquan He
2026-08-13 10:48 ` [RFC v3 04/15] mm, swap: refactor free_swap_cluster_info to take swap_info_struct Baoquan He
2026-08-13 10:48 ` [RFC v3 05/15] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
2026-08-14 23:09 ` Klara Modin
2026-08-17 0:46 ` Baoquan He
2026-08-13 10:48 ` [RFC v3 06/15] mm, swap: add sysfs create interface for xswap Baoquan He
2026-08-13 10:48 ` [RFC v3 07/15] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
2026-08-13 10:48 ` [RFC v3 08/15] mm, swap: add xswap_try_shrink and shrink trigger on cluster free Baoquan He
2026-08-13 10:48 ` [RFC v3 09/15] mm, swap: free backing pages in xswap_unmap_clusters Baoquan He
2026-08-13 10:48 ` [RFC v3 10/15] mm, swap: add nr_free_tail for O(1) xswap shrink detection Baoquan He
2026-08-13 10:48 ` [RFC v3 11/15] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap Baoquan He
2026-08-13 10:48 ` [RFC v3 12/15] mm, swap: add debugfs knob for xswap per-device cluster limit Baoquan He
2026-08-13 10:48 ` [RFC v3 13/15] mm, swap: defer xswap shrink to workqueue to avoid lock recursion Baoquan He
2026-08-13 10:48 ` [RFC v3 14/15] mm, swap: refactor swapoff + add xswap_destroy Baoquan He
2026-08-13 10:48 ` [RFC v3 15/15] mm, swap: require zswap for xswap devices Baoquan He
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox