From: Baoquan He <baoquan.he@linux.dev>
To: linux-mm@kvack.org
Cc: akpm@linux-foundation.org, david@kernel.org, chrisl@kernel.org,
kasong@tencent.com, nphamcs@gmail.com, baohua@kernel.org,
youngjun.park@lge.com, corbet@lwn.net, hannes@cmpxchg.org,
chengming.zhou@linux.dev, kunwu.chan@gmail.com,
klarasmodin@gmail.com, linux-kernel@vger.kernel.org,
Baoquan He <hebaoquan@kylinos.cn>
Subject: [PATCH v5 06/19] mm, swap: add a sysfs interface to create xswap devices
Date: Mon, 9 Nov 2026 13:54:42 +0800 [thread overview]
Message-ID: <20261109055455.536925-7-baoquan.he@linux.dev> (raw)
In-Reply-To: <20261109055455.536925-1-baoquan.he@linux.dev>
From: Baoquan He <hebaoquan@kylinos.cn>
An xswap device has no backing file, so it cannot be created with
swapon. Add a sysfs interface to create one directly:
/sys/kernel/mm/xswap/create
Write an optional priority to this file. Write nothing to use the
default priority. By default the device gets the highest priority.
A new device gets si->max and nr_clusters_max equal to twice RAM.
The cluster_info array is a VM_SPARSE area and is mapped lazily. At
this point only the first XSWAP_GROW_CLUSTERS chunk is mapped; the
grow trigger comes in a later patch. The device is shown in
/proc/swaps as "xswap<N>".
Hibernation device discovery skips xswap devices because they have no
bdev to store a resume image.
Signed-off-by: Baoquan He <hebaoquan@kylinos.cn>
---
mm/swapfile.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 170 insertions(+), 5 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 83aaf00b75d3..616adbb298e7 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>
@@ -48,6 +50,15 @@
#include "swap_table.h"
#include "internal.h"
#include "swap.h"
+#define DEF_SWAP_PRIO -1
+
+/*
+ * The highest priority a real swap device can use: one ahead of xswap
+ * would take the page directly, and taking that slot is what xswap
+ * exists to avoid. At equal priority the list is FIFO, so a real device
+ * that asks for this priority too and is added first wins.
+ */
+#define DEF_XSWAP_PRIO SWAP_FLAG_PRIO_MASK
#ifdef CONFIG_XSWAP
/*
@@ -73,7 +84,67 @@ 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_mapped_end(pte_t *pte, unsigned long addr, void *data);
-#endif
+
+static int xswap_create(int prio);
+
+static ssize_t xswap_create_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int prio = DEF_XSWAP_PRIO;
+ const char *p;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* "[<prio>]" is optional; a lower one is allowed, not a higher. */
+ p = skip_spaces(buf);
+ if (*p && *p != '\n') {
+ err = kstrtoint(p, 10, &prio);
+ if (err)
+ return err;
+ }
+
+ err = xswap_create(prio);
+ 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");
+ kobject_put(xswap_kobj);
+ xswap_kobj = NULL;
+ }
+}
+#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);
@@ -101,7 +172,6 @@ atomic_t nr_real_swapfiles;
EXPORT_SYMBOL_GPL(nr_swap_pages);
/* protected with swap_lock. reading in vm_swap_full() doesn't need lock */
long total_swap_pages;
-#define DEF_SWAP_PRIO -1
unsigned long swapfile_maximum_size;
#ifdef CONFIG_MIGRATION
bool swap_migration_ad_supported;
@@ -2284,6 +2354,9 @@ static int __find_hibernation_swap_type(dev_t device, sector_t offset)
if (!(sis->flags & SWP_WRITEOK))
continue;
+ /* xswap has no bdev to match a resume device */
+ if (sis->flags & SWP_XSWAP)
+ continue;
if (device == sis->bdev->bd_dev) {
struct swap_extent *se = first_se(sis);
@@ -2471,6 +2544,8 @@ int find_first_swap(dev_t *device)
if (!(sis->flags & SWP_WRITEOK))
continue;
+ if (sis->flags & SWP_XSWAP)
+ continue;
*device = sis->bdev->bd_dev;
spin_unlock(&swap_lock);
return type;
@@ -3452,7 +3527,8 @@ 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) && (si->flags & SWP_WRITEOK)))
continue;
if (!--l)
return si;
@@ -3473,7 +3549,8 @@ 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) && (si->flags & SWP_WRITEOK)))
continue;
return si;
}
@@ -3515,7 +3592,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),
@@ -4026,6 +4110,8 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
return 0;
err_unmap:
+ /* swap_cluster_setup_bad_slot() allocates cluster 0's table. */
+ swap_cluster_free_table(&si->cluster_info[0]);
xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
err_free_vm:
free_vm_area(si->cluster_vm);
@@ -4108,6 +4194,83 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
return err;
}
+#ifdef CONFIG_XSWAP
+/* Create a file-less xswap device, sized at twice RAM. */
+static int xswap_create(int prio)
+{
+ struct swap_info_struct *si;
+ unsigned long ram, maxpages;
+ int error;
+
+ if (prio != DEF_XSWAP_PRIO &&
+ (prio < 0 || prio > SWAP_FLAG_PRIO_MASK))
+ 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 * 2, swapfile_maximum_size);
+ /* A cluster index is an unsigned int, so cap the cluster count. */
+ if (maxpages / SWAPFILE_CLUSTER > UINT_MAX)
+ maxpages = (unsigned long)UINT_MAX * SWAPFILE_CLUSTER;
+ /* Cluster-aligned, so no cluster holds a slot past si->max. */
+ if (maxpages > SWAPFILE_CLUSTER)
+ maxpages = rounddown(maxpages, SWAPFILE_CLUSTER);
+ if (maxpages < 2)
+ maxpages = 2;
+
+ si->bdev = NULL;
+ si->swap_file = NULL;
+ si->flags |= SWP_XSWAP | SWP_SOLIDSTATE;
+ si->max = maxpages;
+ si->pages = maxpages - 1;
+ /*
+ * No backing file: setup_swap_extents() is only reachable from the
+ * file-backed swapon() path, so set ops here. Only ops->flags is
+ * used, by may_enter_fs(); the IO methods are never called because
+ * swap_writeout()/swap_read_folio() short circuit xswap.
+ */
+ si->ops = &swap_bdev_ops;
+
+ error = setup_swap_clusters_info(si, NULL, maxpages);
+ if (error)
+ goto bad_swap;
+
+ error = zswap_swapon(si->type, si->max);
+ if (error)
+ goto bad_swap;
+
+ mutex_lock(&swapon_mutex);
+ si->prio = prio;
+ si->list.prio = -si->prio;
+ si->avail_list.prio = -si->prio;
+ enable_swap_info(si);
+ pr_info("xswap: adding extendable swap type %d (prio %d, %lu pages)\n",
+ si->type, prio, si->pages);
+ mutex_unlock(&swapon_mutex);
+ 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_XSWAP */
+
SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
{
struct swap_info_struct *si;
@@ -4454,6 +4617,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
next prev parent reply other threads:[~2026-11-09 5:56 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 5:54 [PATCH v5 00/19] mm, swap: extendable swap devices (xswap phase I) Baoquan He
2026-11-09 5:54 ` [PATCH v5 01/19] mm: xswap support for zswap Baoquan He
2026-11-09 5:54 ` [PATCH v5 02/19] mm, swap: refactor free_swap_cluster_info to take swap_info_struct Baoquan He
2026-11-09 5:54 ` [PATCH v5 03/19] mm, swap: add VM_SPARSE cluster-array helpers for xswap Baoquan He
2026-11-09 5:54 ` [PATCH v5 04/19] mm, swap: use a VM_SPARSE cluster array for xswap devices Baoquan He
2026-11-09 5:54 ` [PATCH v5 05/19] mm, swap: widen swap_info_struct max/pages to unsigned long Baoquan He
2026-11-09 5:54 ` Baoquan He [this message]
2026-11-09 5:54 ` [PATCH v5 07/19] mm, swap: refactor swapoff and add xswap_destroy Baoquan He
2026-11-09 5:54 ` [PATCH v5 08/19] mm, swap: require zswap for xswap devices Baoquan He
2026-11-09 5:54 ` [PATCH v5 09/19] mm, swap: use a stable id " Baoquan He
2026-11-09 5:54 ` [PATCH v5 10/19] mm, swap: let the command line set the xswap device size Baoquan He
2026-11-09 5:54 ` [PATCH v5 11/19] mm, swap: add CONFIG_XSWAP_DEFAULT_MAX Baoquan He
2026-11-09 5:54 ` [PATCH v5 12/19] mm, swap: let swapoff destroy xswap devices by name Baoquan He
2026-11-09 5:54 ` [PATCH v5 13/19] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
2026-11-09 5:54 ` [PATCH v5 14/19] mm, swap: add debugfs counters for xswap Baoquan He
2026-11-09 5:54 ` [PATCH v5 15/19] mm, swap: make xswap cluster_info accesses RCU-safe Baoquan He
2026-11-09 5:54 ` [PATCH v5 16/19] mm, swap: prepare xswap_unmap_clusters for range shrinking Baoquan He
2026-11-09 5:54 ` [PATCH v5 17/19] mm, swap: add xswap shrink trigger and defer it to a workqueue Baoquan He
2026-11-09 5:54 ` [PATCH v5 18/19] mm, swap: do not give an xswap slot to a cgroup that cannot zswap Baoquan He
2026-11-09 5:54 ` [PATCH v5 19/19] mm, swap: skip the overcommit probe when removing xswap Baoquan He
2026-11-09 6:15 ` [PATCH v5 00/19] mm, swap: extendable swap devices (xswap phase I) Baoquan He
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109055455.536925-7-baoquan.he@linux.dev \
--to=baoquan.he@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=chengming.zhou@linux.dev \
--cc=chrisl@kernel.org \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=hebaoquan@kylinos.cn \
--cc=kasong@tencent.com \
--cc=klarasmodin@gmail.com \
--cc=kunwu.chan@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=youngjun.park@lge.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®