* [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries
@ 2026-07-07 8:26 Baoquan He
2026-07-07 8:26 ` [RFC PATCH 01/10] mm: ghost swapfile support for zswap Baoquan He
` (11 more replies)
0 siblings, 12 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
This series introduces a virtual "ghost" swapfile that can be backed
by multiple physical swap devices, switching between them at runtime
using a new "Redirect" swap table entry type.
Motivation
----------
Physical swap devices have fixed, large sizes but a machine may only
need a fraction at any given time. Ghost swap decouples the logical
swap capacity from physical swap allocation: it presents a large
virtual swap device to userspace while lazily mapping slots to
physical devices only when data must leave zswap (writeback). This
enables overcommit of swap capacity without wasting physical disk
space, and allows the ghost device to grow dynamically at runtime.
Ghost swapfile characteristics
--------------------------------
Ghost swap decouples logical swap capacity from physical swap allocation.
Key properties:
- No backing store required. A ghost swap device can operate purely
as a zswap container — compressed pages stay in the zswap pool without
ever touching a physical swap device. No physical swap device needs
to be attached.
- Optional physical backing. One or more physical swap devices can be
swapon'd alongside the ghost device, acting as writeback targets when
the zswap pool needs to evict cold pages. The Redirect entry in the
ghost swap table links each evicted slot to its physical destination.
- Dynamic size growth. The ghost device can be extended at runtime via
sysfs, up to a current maximum of 1TB. This is implemented through
lazy vmalloc: a sparse 64MB virtual address space is reserved at
swapon time to cover the full 1TB range, but physical pages for
cluster_info[] structures are allocated only on demand as the device
grows. The cluster_info pointer never changes, so all existing
accessors (__swap_offset_to_cluster, cluster_index) work unchanged.
- sysfs control. Each ghost device exposes attributes under
/sys/kernel/mm/ghost_swap/ghost_<N>/:
path (RO) — backing device name
max (RW) — current max pages; writing a larger value triggers
swap_ghost_extend_max()
Design overview
---------------
A swap table slot can now hold a Redirect entry:
Redirect: |----- physical swp_entry_t -----|101|
This stores a plain swp_entry_t pointing to a slot on a real (physical)
swap device. The low 3-bit mark (0b101) distinguishes it from Pointer
(0b100), PFN (0b10), Shadow (0b1), and NULL (0) entries.
The lifecycle is:
1. swapon a ghost device — reserves sparse vmalloc area (64MB virtual)
and physically backs only the initial range. The device starts with
no physical backing store: pages swapped out go to zswap, and zswap
pool evictions go to any concurrently attached physical device.
2. zswap writeback from ghost: when the zswap pool needs to evict,
zswap_writeback_ghost() allocates a physical slot from any real
swap device, writes the data there, and plants a Redirect entry
in the ghost's swap table pointing to the physical slot.
3. Swap-in from ghost: swap_read_folio() detects the ghost device
(SWP_GHOST), resolves the Redirect to the physical swp_entry_t,
and forwards the read I/O to the physical device.
4. When the swap cache folio is removed, the Redirect entry is restored
from an xarray (redirect_xa) that persists the mapping, so
subsequent reads still find the physical backing.
5. When the physical slot is freed, the ghost slot is also freed,
cascading through swap_range_free().
Note:
------
Ghost swapfile is a proof of concept. Compared with the other patchset:
[RFC PATCH v2 0/7] mm, swap: Virtual Swap Space (Swap Table Edition),
it explores a different approach to implementing virtual swap on top of
the existing swap table infrastructure, with a simpler implementation.
It builds on Chris Li's earlier ghost swapfile work and is currently
tentatively named "ghost swapfile" — alternative naming suggestions are
welcome. Ghost swapfile setup can also be integrated through modifications
to the swapon API and the swapon command in util-linux, for example:
swapon --ghost ghost_1:2G -p 5.
Baoquan He (9):
mm/swap_table: add Redirect entry encoding for ghost swap backend
switching
mm/swap: add redirect_xa field and ghost redirect helper declarations
mm/swapfile: implement ghost redirect helpers and free-path cascade
mm/swap_state: restore Redirect entry when swap cache folio is removed
mm/zswap: implement ghost-to-physical writeback for backend switching
mm/page_io: forward ghost swap reads to physical device via Redirect
mm/swapfile: manage ghost cluster_info via lazy vmalloc
mm/swapfile: implement swap_ghost_extend_max() for dynamic growth
mm/swapfile: add sysfs interface for ghost swap extension
Chris Li (1):
mm: ghost swapfile support for zswap
include/linux/swap.h | 10 +
mm/page_io.c | 51 +++-
mm/swap.h | 16 +-
mm/swap_state.c | 28 +++
mm/swap_table.h | 38 +++
mm/swapfile.c | 579 ++++++++++++++++++++++++++++++++++++++++---
mm/zswap.c | 98 +++++++-
7 files changed, 772 insertions(+), 48 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 01/10] mm: ghost swapfile support for zswap
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 02/10] mm/swap_table: add Redirect entry encoding for ghost swap backend switching Baoquan He
` (10 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
From: Chris Li <chrisl@kernel.org>
The current zswap requires a backing swapfile. The swap slot used
by zswap is not able to be used by the swapfile. That waste swapfile
space.
The ghost swapfile is a swapfile that only contains the swapfile header
for zswap. The swapfile header indicate the size of the swapfile. There
is no swap data section in the ghost swapfile, therefore, no waste of
swapfile space. As such, any write to a ghost swapfile will fail. To
prevents accidental read or write of ghost swapfile, bdev of
swap_info_struct is set to NULL. Ghost swapfile will also set the SSD
flag because there is no rotation disk access when using zswap.
The zswap write back has been disabled if all swapfiles in the system
are ghost swap files.
Quote Chris's setup steps here:
---------------------------
touch swap.1G
truncate -s 1G swap.1G
mkswap swap.1G
dd if=swap.1G of=ghost.1G bs=4096 count=1
Then you will have one 4K size ghost.1G.
you should be able to do:
swapon ghost.1G
---------------------------
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
include/linux/swap.h | 2 ++
mm/page_io.c | 18 +++++++++++++++---
mm/swap.h | 2 +-
mm/swap_state.c | 7 +++++++
mm/swapfile.c | 41 +++++++++++++++++++++++++++++++++++++----
mm/zswap.c | 12 +++++++++---
6 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 3f31b6a56788..9829a31a541e 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -208,6 +208,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_GHOST = (1 << 14), /* not backed by anything */
/* add others here before... */
};
@@ -412,6 +413,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 1ce8201ed86e..96e776f9a503 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -295,8 +295,7 @@ int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug)
}
rcu_read_unlock();
- __swap_writepage(folio, swap_plug);
- return 0;
+ return __swap_writepage(folio, swap_plug);
out_unlock:
folio_unlock(folio);
return ret;
@@ -463,11 +462,18 @@ static void swap_writepage_bdev_async(struct folio *folio,
submit_bio(bio);
}
-void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug)
+int __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug)
{
struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
VM_BUG_ON_FOLIO(!folio_test_swapcache(folio), folio);
+
+ if (sis->flags & SWP_GHOST) {
+ /* Prevent the page from getting reclaimed. */
+ folio_set_dirty(folio);
+ return AOP_WRITEPAGE_ACTIVATE;
+ }
+
/*
* ->flags can be updated non-atomically,
* but that will never affect SWP_FS_OPS, so the data_race
@@ -484,6 +490,7 @@ void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug)
swap_writepage_bdev_sync(folio, sis);
else
swap_writepage_bdev_async(folio, sis);
+ return 0;
}
void swap_write_unplug(struct swap_iocb *sio)
@@ -689,6 +696,11 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry)
if (zswap_load(folio, zentry) != -ENOENT)
goto finish;
+ if (unlikely(sis->flags & SWP_GHOST)) {
+ folio_unlock(folio);
+ goto finish;
+ }
+
/* We have to read from slower devices. Increase zswap protection. */
zswap_folio_swapin(folio);
diff --git a/mm/swap.h b/mm/swap.h
index 84e13ba6e605..2d29e41d3323 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -285,7 +285,7 @@ static inline void swap_read_unplug(struct swap_iocb *plug)
}
void swap_write_unplug(struct swap_iocb *sio);
int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug);
-void __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug);
+int __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug);
/* linux/mm/swap_state.c */
extern struct address_space swap_space __read_mostly;
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 60d937c6b392..5fb893cc7fce 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -883,6 +883,13 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,
struct swap_iocb *splug = NULL;
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_GHOST)
+ goto skip;
+
mask = swapin_nr_pages(offset) - 1;
if (!mask)
goto skip;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 18925e462571..148ceb2d812b 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
@@ -1218,6 +1219,8 @@ static void del_from_avail_list(struct swap_info_struct *si, bool swapoff)
goto skip;
}
+ if (!(si->flags & SWP_GHOST))
+ atomic_sub(1, &nr_real_swapfiles);
plist_del(&si->avail_list, &swap_avail_head);
skip:
@@ -1260,6 +1263,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_GHOST))
+ atomic_add(1, &nr_real_swapfiles);
skip:
spin_unlock(&swap_avail_lock);
@@ -3006,6 +3011,11 @@ static int setup_swap_extents(struct swap_info_struct *sis,
struct inode *inode = mapping->host;
int ret;
+ if (sis->flags & SWP_GHOST) {
+ *span = 0;
+ return 0;
+ }
+
if (S_ISBLK(inode->i_mode)) {
ret = add_swap_extent(sis, 0, sis->max, 0);
*span = sis->pages;
@@ -3213,7 +3223,8 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
destroy_swap_extents(p, p->swap_file);
- if (!(p->flags & SWP_SOLIDSTATE))
+ if (!(p->flags & SWP_GHOST) &&
+ !(p->flags & SWP_SOLIDSTATE))
atomic_dec(&nr_rotate_swap);
mutex_lock(&swapon_mutex);
@@ -3323,6 +3334,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_GHOST)
+ return "ghost\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;
@@ -3342,8 +3366,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);
@@ -3521,6 +3544,7 @@ static unsigned long read_swap_header(struct swap_info_struct *si,
unsigned long maxpages;
unsigned long swapfilepages;
unsigned long last_page;
+ loff_t size;
if (memcmp("SWAPSPACE2", swap_header->magic.magic, 10)) {
pr_err("Unable to find swap-space signature\n");
@@ -3563,7 +3587,16 @@ static unsigned long read_swap_header(struct swap_info_struct *si,
if (!maxpages)
return 0;
- swapfilepages = i_size_read(inode) >> PAGE_SHIFT;
+
+ size = i_size_read(inode);
+ if (size == PAGE_SIZE) {
+ /* Ghost swapfile */
+ si->bdev = NULL;
+ si->flags |= SWP_GHOST | SWP_SOLIDSTATE;
+ return maxpages;
+ }
+
+ swapfilepages = size >> PAGE_SHIFT;
if (swapfilepages && maxpages > swapfilepages) {
pr_warn("Swap area shorter than signature indicates\n");
return 0;
diff --git a/mm/zswap.c b/mm/zswap.c
index f9762d0a596a..bdf8283fd880 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -992,7 +992,12 @@ 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;
+
+ if (si->flags & SWP_GHOST) {
+ put_swap_device(si);
+ return -EINVAL;
+ }
/*
* Verify the swap table Pointer entry still references this
@@ -1048,7 +1053,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
folio_set_reclaim(folio);
/* start writeback */
- __swap_writepage(folio, NULL);
+ ret = __swap_writepage(folio, NULL);
+ WARN_ON_ONCE(ret);
out:
if (ret) {
@@ -1538,7 +1544,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] 15+ messages in thread
* [RFC PATCH 02/10] mm/swap_table: add Redirect entry encoding for ghost swap backend switching
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 8:26 ` [RFC PATCH 01/10] mm: ghost swapfile support for zswap Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 03/10] mm/swap: add redirect_xa field and ghost redirect helper declarations Baoquan He
` (9 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Introduce SWP_TB_REDIRECT_MARK (0b101) as a new swap table entry type
that stores a physical swp_entry_t directly. This allows a ghost
swap slot to redirect to a physical swap slot after zswap writeback.
The swp_entry_t has its low 12 bits clear (page-aligned offset), so
the 3-bit mark is ORed directly into the value without shifting and
without losing any type or offset bits.
Add inline helpers:
- swp_tb_is_redirect(): type check
- swp_entry_to_redirect(): encode a swp_entry_t as a Redirect entry
- redirect_to_swp_entry(): decode a Redirect entry back to swp_entry_t
Update the swap table entry type documentation to include the new
Redirect type.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/swap_table.h | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/mm/swap_table.h b/mm/swap_table.h
index 6318bf8aa76b..5a971152cf23 100644
--- a/mm/swap_table.h
+++ b/mm/swap_table.h
@@ -29,6 +29,7 @@ struct swap_memcg_table {
* Shadow: |SWAP_COUNT|Z|---- SHADOW_VAL ---|1| - Swapped out slot
* PFN: |SWAP_COUNT|Z|------ PFN -------|10| - Cached slot
* Pointer: |----------- Pointer ----------|100| - Zswap compressed entry
+ * Redirect: |------- swp_entry_t.val ------|101| - Redirect to phys swap
* Bad: |------------- 1 -------------|1000| - Bad slot
*
* COUNT is `SWP_TB_COUNT_BITS` long, Z is the `SWP_TB_ZERO_FLAG` bit,
@@ -55,6 +56,11 @@ struct swap_memcg_table {
* all upper bits. `0b100` is used as the marker because zswap_entry
* pointers are at least 8-byte aligned, leaving the lower three bits free.
*
+ * - Redirect: Stores a physical swp_entry_t directly, ending with `0b101`.
+ * Used by ghost swapfiles to redirect a virtual slot to a physical swap
+ * slot after zswap writeback. swp_entry_t has bits 0-11 clear (page-aligned
+ * offset), so the 3-bit mark is ORed directly into the value without shifting.
+ *
* - Bad: Swap slot is reserved, protects swap header or holes on swap devices.
*/
@@ -83,6 +89,15 @@ struct swap_memcg_table {
/* Pointer: zswap_entry pointer stored directly, ends with 0b100 */
#define SWP_TB_POINTER_MARK 0b100UL
+/*
+ * Redirect: stores a physical swp_entry_t directly, ends with 0b101.
+ * Used by ghost swapfiles to redirect a virtual slot to a physical
+ * swap slot after writeback. swp_entry_t has its low 12 bits clear
+ * (page-aligned offset), so we can OR the 3-bit mark directly into
+ * the entry value without shifting and without losing type bits.
+ */
+#define SWP_TB_REDIRECT_MARK 0b101UL
+
/* Bad slot: ends with 0b1000 and rests of bits are all 1 */
#define SWP_TB_BAD ((~0UL) << 3)
@@ -176,6 +191,11 @@ static inline bool swp_tb_is_pointer(unsigned long swp_tb)
return (swp_tb & (BIT(3) - 1)) == SWP_TB_POINTER_MARK;
}
+static inline bool swp_tb_is_redirect(unsigned long swp_tb)
+{
+ return (swp_tb & (BIT(3) - 1)) == SWP_TB_REDIRECT_MARK;
+}
+
static inline bool swp_tb_is_countable(unsigned long swp_tb)
{
return (swp_tb_is_shadow(swp_tb) || swp_tb_is_folio(swp_tb) ||
@@ -215,6 +235,24 @@ static inline void *swp_tb_to_pointer(unsigned long swp_tb)
return (void *)(swp_tb & ~((unsigned long)(BIT(3) - 1)));
}
+/*
+ * Redirect encoding: swp_entry_t is stored directly, with the 3-bit
+ * mark ORed into the low bits. swp_entry_t.val has bits 0-11 clear
+ * (page-aligned offset), so no shifting is needed and no type bits
+ * are lost.
+ */
+static inline unsigned long swp_entry_to_redirect(swp_entry_t entry)
+{
+ VM_WARN_ON(entry.val & (BIT(3) - 1));
+ return entry.val | SWP_TB_REDIRECT_MARK;
+}
+
+static inline swp_entry_t redirect_to_swp_entry(unsigned long swp_tb)
+{
+ VM_WARN_ON(!swp_tb_is_redirect(swp_tb));
+ return (swp_entry_t){ .val = swp_tb & ~((unsigned long)(BIT(3) - 1)) };
+}
+
static inline unsigned char __swp_tb_get_count(unsigned long swp_tb)
{
VM_WARN_ON(!swp_tb_is_countable(swp_tb));
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 03/10] mm/swap: add redirect_xa field and ghost redirect helper declarations
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 8:26 ` [RFC PATCH 01/10] mm: ghost swapfile support for zswap Baoquan He
2026-07-07 8:26 ` [RFC PATCH 02/10] mm/swap_table: add Redirect entry encoding for ghost swap backend switching Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade Baoquan He
` (8 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Add a 'struct xarray redirect_xa' to swap_info_struct (guarded by
CONFIG_ZSWAP) to persist ghost_offset -> phys_swp_entry_t mappings
across swap-cache PFN cycles.
Declare the ghost redirect helper functions in mm/swap.h.
All are no-ops when CONFIG_ZSWAP=n.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
include/linux/swap.h | 3 +++
mm/swap.h | 14 ++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 9829a31a541e..a4d6851e0921 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -277,6 +277,9 @@ struct swap_info_struct {
struct work_struct reclaim_work; /* reclaim worker */
struct list_head discard_clusters; /* discard clusters list */
struct plist_node avail_list; /* entry in swap_avail_head */
+#ifdef CONFIG_ZSWAP
+ struct xarray redirect_xa; /* ghost offset â phys swp_entry_t */
+#endif
};
static inline swp_entry_t page_swap_entry(struct page *page)
diff --git a/mm/swap.h b/mm/swap.h
index 2d29e41d3323..bc51ee5dbf51 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -287,6 +287,20 @@ void swap_write_unplug(struct swap_iocb *sio);
int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug);
int __swap_writepage(struct folio *folio, struct swap_iocb **swap_plug);
+/* Ghost swapfile redirect helpers (mm/swapfile.c) */
+#ifdef CONFIG_ZSWAP
+swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si,
+ pgoff_t offset);
+swp_entry_t alloc_phys_swap_slot(void);
+void free_phys_swap_slot(swp_entry_t phys_entry);
+#else
+static inline swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si,
+ pgoff_t offset)
+{ return (swp_entry_t){}; }
+static inline swp_entry_t alloc_phys_swap_slot(void) { return (swp_entry_t){}; }
+static inline void free_phys_swap_slot(swp_entry_t phys_entry) {}
+#endif
+
/* linux/mm/swap_state.c */
extern struct address_space swap_space __read_mostly;
static inline struct address_space *swap_address_space(swp_entry_t entry)
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (2 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 03/10] mm/swap: add redirect_xa field and ghost redirect helper declarations Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 05/10] mm/swap_state: restore Redirect entry when swap cache folio is removed Baoquan He
` (7 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Add the core infrastructure for ghost swapfile backend switching:
- alloc_phys_swap_slot()
- free_phys_swap_slot()
- ghost_redirect_to_phys()
Initialize redirect_xa in read_swap_header() when a ghost swapfile
is detected (file size == PAGE_SIZE).
Handle Redirect entries in __swap_cluster_free_entries(): when a
ghost entry with a Redirect is freed (swap count reaches zero),
erase the xarray mapping and cascade the free to the physical
swap slot. Also skip zero-flag clearing for Redirect entries
(they carry no zero flag).
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/swapfile.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 105 insertions(+), 2 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 148ceb2d812b..c1686c2b2521 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include <linux/kernel_stat.h>
#include <linux/swap.h>
+#include <linux/xarray.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/namei.h>
@@ -1975,7 +1976,14 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
* means zswap has a compressed copy; the xarray still
* holds the entry and zswap_invalidate will free it.
*/
- if (swp_tb_is_pointer(old_tb)) {
+ if (swp_tb_is_redirect(old_tb)) {
+ swp_entry_t phys_entry;
+
+ phys_entry = redirect_to_swp_entry(old_tb);
+ xa_erase(&si->redirect_xa,
+ ci_off + cluster_offset(si, ci));
+ free_phys_swap_slot(phys_entry);
+ } else if (swp_tb_is_pointer(old_tb)) {
VM_WARN_ON(zswap_swp_tb_get_count(old_tb) > 1);
/*
* Free the zswap entry now under ci->lock while
@@ -1990,7 +1998,8 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
/* Resetting the slot to NULL also clears the inline flags. */
__swap_table_set(ci, ci_off, null_to_swp_tb());
- if (!SWAP_TABLE_HAS_ZEROFLAG && !swp_tb_is_pointer(old_tb))
+ if (!SWAP_TABLE_HAS_ZEROFLAG && !swp_tb_is_pointer(old_tb) &&
+ !swp_tb_is_redirect(old_tb))
__swap_table_clear_zero(ci, ci_off);
/*
@@ -3593,6 +3602,7 @@ static unsigned long read_swap_header(struct swap_info_struct *si,
/* Ghost swapfile */
si->bdev = NULL;
si->flags |= SWP_GHOST | SWP_SOLIDSTATE;
+ xa_init(&si->redirect_xa);
return maxpages;
}
@@ -4006,6 +4016,99 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
}
#endif
+#ifdef CONFIG_ZSWAP
+/*
+ * Ghost swapfile redirect: physical swap slot allocation for writeback.
+ *
+ * Allocates a single order-0 swap slot from any real (non-ghost, writable)
+ * swap device. Used by zswap writeback to obtain a physical destination
+ * for ghost-backed entries.
+ */
+swp_entry_t alloc_phys_swap_slot(void)
+{
+ struct swap_info_struct *si;
+ unsigned long offset;
+ swp_entry_t entry = {};
+
+ spin_lock(&swap_avail_lock);
+ plist_for_each_entry(si, &swap_avail_head, avail_list) {
+ /* Skip ghost or non-writable devices */
+ if (si->flags & SWP_GHOST)
+ continue;
+ if (!(si->flags & SWP_WRITEOK))
+ continue;
+ if (!get_swap_device_info(si))
+ continue;
+
+ /* Try to allocate one slot */
+ offset = cluster_alloc_swap_entry(si, NULL);
+ put_swap_device(si);
+ if (offset && offset != SWAP_ENTRY_INVALID) {
+ entry = swp_entry(si->type, offset);
+ break;
+ }
+ }
+ spin_unlock(&swap_avail_lock);
+
+ return entry;
+}
+
+void free_phys_swap_slot(swp_entry_t phys_entry)
+{
+ struct swap_info_struct *si;
+ unsigned long offset;
+ struct swap_cluster_info *ci;
+
+ si = get_swap_device(phys_entry);
+ if (!si)
+ return;
+ offset = swp_offset(phys_entry);
+ ci = swap_cluster_lock(si, offset);
+ if (ci) {
+ /*
+ * Clear the physical slot. The swap count should be 0 at
+ * this point (the ghost entry's count has been transferred
+ * or freed).
+ */
+ __swap_table_set(ci, offset % SWAPFILE_CLUSTER,
+ null_to_swp_tb());
+ swap_cluster_unlock(ci);
+ swap_range_free(si, offset, 1);
+ }
+ put_swap_device(si);
+}
+
+/*
+ * Resolve a ghost swap offset to its physical backing entry.
+ * Checks the swap table first (for Redirect entries), then falls
+ * back to the xarray (for entries that have a swap-cache folio).
+ */
+swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si, pgoff_t offset)
+{
+ struct swap_cluster_info *ci;
+ unsigned long swp_tb;
+ void *val;
+
+ /* Fast path: check the swap table for a Redirect entry */
+ ci = __swap_offset_to_cluster(si, offset);
+ spin_lock(&ci->lock);
+ swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);
+ if (swp_tb_is_redirect(swp_tb)) {
+ swp_entry_t phys = redirect_to_swp_entry(swp_tb);
+ spin_unlock(&ci->lock);
+ return phys;
+ }
+ spin_unlock(&ci->lock);
+
+ /* Slow path: look up the persistent xarray mapping */
+ val = xa_load(&si->redirect_xa, offset);
+ if (val && !xa_is_err(val))
+ return (swp_entry_t){ .val = xa_to_value(val) };
+
+ return (swp_entry_t){};
+}
+#endif /* CONFIG_ZSWAP */
+
static int __init swapfile_init(void)
{
swapfile_maximum_size = arch_max_swapfile_size();
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 05/10] mm/swap_state: restore Redirect entry when swap cache folio is removed
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (3 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 06/10] mm/zswap: implement ghost-to-physical writeback for backend switching Baoquan He
` (6 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
When a swap cache folio is removed from a ghost swap device,
__swap_cache_do_del_folio() normally restores a Shadow entry with
the preserved swap count. For ghost devices, the entry may have
originated from a Redirect (zswap writeback to a physical device).
Check the redirect_xa before restoring a Shadow: if a redirect
mapping exists, restore the Redirect entry instead. This ensures
future swapins are correctly forwarded to the physical device.
The redirect mapping is stored in the xarray during zswap writeback
(see zswap_writeback_ghost()) and persists across the swap-cache
PFN lifecycle.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/swap_state.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5fb893cc7fce..cb2ed2a18f9f 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -12,6 +12,7 @@
#include <linux/kernel_stat.h>
#include <linux/mempolicy.h>
#include <linux/swap.h>
+#include <linux/xarray.h>
#include <linux/leafops.h>
#include <linux/init.h>
#include <linux/pagemap.h>
@@ -329,6 +330,26 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
folio_swapped = true;
else
need_free = true;
+ /*
+ * For ghost swap devices with a redirect mapping,
+ * restore the Redirect entry so future swapins
+ * are forwarded to the physical device.
+ */
+ if (si->flags & SWP_GHOST) {
+ swp_entry_t phys_entry;
+ void *val;
+
+ val = xa_load(&si->redirect_xa,
+ (swp_offset(entry) -
+ swp_cluster_offset(entry)) +
+ ci_off);
+ if (val && !xa_is_err(val)) {
+ phys_entry.val = xa_to_value(val);
+ __swap_table_set(ci, ci_off,
+ swp_entry_to_redirect(phys_entry));
+ continue;
+ }
+ }
/* If shadow is NULL, we set an empty shadow. */
__swap_table_set(ci, ci_off, shadow_to_swp_tb(shadow,
__swp_tb_get_flags(old_tb)));
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 06/10] mm/zswap: implement ghost-to-physical writeback for backend switching
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (4 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 05/10] mm/swap_state: restore Redirect entry when swap cache folio is removed Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 07/10] mm/page_io: forward ghost swap reads to physical device via Redirect Baoquan He
` (5 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Add zswap_writeback_ghost() to handle zswap writeback when the
backing swap device is a ghost swapfile (SWP_GHOST). Previously,
writeback for ghost entries was rejected with -EINVAL.
The new writeback path:
1. Allocates a physical swap slot from any real swap device via
alloc_phys_swap_slot().
2. Creates a swap-cache folio attached to the ghost swap entry.
3. Decompresses the zswap entry into the folio.
4. Stores the ghost->physical mapping in the ghost's redirect_xa.
5. Temporarily redirects folio->swap to the physical entry and
calls __swap_writepage() to write to the physical device.
6. On success, the xarray mapping persists. When the swap-cache
folio is later removed, __swap_cache_do_del_folio() restores
the Redirect entry from the xarray.
This enables the backend switch from zswap (compressed in memory)
to physical swap (on disk) without changing the PTE — the PTE
continues to point to the ghost swap entry, and the swapin path
forwards the I/O via the Redirect entry.
Modify zswap_writeback_entry() to call zswap_writeback_ghost()
for ghost-backed entries instead of returning -EINVAL.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/zswap.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 91 insertions(+), 5 deletions(-)
diff --git a/mm/zswap.c b/mm/zswap.c
index bdf8283fd880..2137e8f14413 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -978,6 +978,90 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
* the swap cache, the compressed version stored by zswap can be
* freed.
*/
+/*
+ * Ghost swapfile writeback: allocate a physical swap slot, decompress
+ * the zswap entry into a swap-cache folio, write it to the physical
+ * device via __swap_writepage, and set up a Redirect entry on the
+ * ghost swap table so future swapins are forwarded to the physical
+ * device.
+ */
+static int zswap_writeback_ghost(struct zswap_entry *entry,
+ swp_entry_t swpentry,
+ struct swap_info_struct *ghost_si,
+ pgoff_t offset)
+{
+ swp_entry_t phys_entry;
+ struct folio *folio;
+ struct mempolicy *mpol;
+ swp_entry_t saved_entry;
+ int ret = 0;
+
+ /* Allocate a physical swap slot from a real device */
+ phys_entry = alloc_phys_swap_slot();
+ if (!phys_entry.val)
+ return -ENOMEM;
+
+ /* Create a swap-cache folio attached to the ghost swap entry */
+ mpol = get_task_policy(current);
+ folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
+ NO_INTERLEAVE_INDEX, NULL);
+ if (IS_ERR(folio)) {
+ ret = PTR_ERR(folio);
+ goto free_phys;
+ }
+
+ /* Decompress zswap data into the folio */
+ if (!zswap_decompress(entry, folio)) {
+ ret = -EIO;
+ goto del_folio;
+ }
+
+ count_vm_event(ZSWPWB);
+ if (entry->objcg)
+ count_objcg_events(entry->objcg, ZSWPWB, 1);
+
+ zswap_entry_free(entry);
+
+ folio_mark_uptodate(folio);
+ folio_set_reclaim(folio);
+
+ /*
+ * Store the redirect mapping in the xarray so it persists
+ * across swap-cache PFN cycles. The swap table currently
+ * holds a PFN (the swap cache folio); when the folio is
+ * eventually removed from swap cache, the Redirect entry
+ * will be restored from the xarray.
+ */
+ xa_store(&ghost_si->redirect_xa, offset,
+ xa_mk_value(phys_entry.val), GFP_ATOMIC);
+
+ /*
+ * Write the folio to the physical swap device. Temporarily
+ * redirect folio->swap to the physical entry so
+ * __swap_writepage uses the correct bdev and sector.
+ */
+ saved_entry = folio->swap;
+ folio->swap = phys_entry;
+ ret = __swap_writepage(folio, NULL);
+ folio->swap = saved_entry;
+
+ if (ret) {
+ xa_erase(&ghost_si->redirect_xa, offset);
+ goto del_folio;
+ }
+
+ folio_put(folio);
+ return 0;
+
+del_folio:
+ swap_cache_del_folio(folio);
+ folio_unlock(folio);
+ folio_put(folio);
+free_phys:
+ free_phys_swap_slot(phys_entry);
+ return ret;
+}
+
static int zswap_writeback_entry(struct zswap_entry *entry,
swp_entry_t swpentry)
{
@@ -994,11 +1078,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
if (!si)
return -ENOENT;
- if (si->flags & SWP_GHOST) {
- put_swap_device(si);
- return -EINVAL;
- }
-
/*
* Verify the swap table Pointer entry still references this
* zswap_entry. If the entry was invalidated or replaced by a
@@ -1015,6 +1094,13 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
}
spin_unlock(&ci->lock);
+ /* Ghost swapfile: write back to a physical swap device */
+ if (si->flags & SWP_GHOST) {
+ ret = zswap_writeback_ghost(entry, swpentry, si, offset);
+ put_swap_device(si);
+ return ret;
+ }
+
mpol = get_task_policy(current);
folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
NO_INTERLEAVE_INDEX, NULL);
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 07/10] mm/page_io: forward ghost swap reads to physical device via Redirect
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (5 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 06/10] mm/zswap: implement ghost-to-physical writeback for backend switching Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 08/10] mm/swapfile: manage ghost cluster_info via lazy vmalloc Baoquan He
` (4 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
When swap_read_folio() encounters a ghost swap entry (SWP_GHOST),
check if there is a Redirect mapping to a physical swap slot via
ghost_redirect_to_phys(). If a physical backing exists, forward
the I/O to that device:
1. Query the redirect_xa / swap table for the physical swp_entry_t.
2. Temporarily redirect folio->swap to the physical entry so the
existing swap_read_folio_bdev_{sync,async} / swap_read_folio_fs
path computes the correct sector from the physical offset.
3. Restore folio->swap to the original ghost entry after I/O.
This completes the backend-switching round trip:
- Swapout: PTE -> ghost entry -> zswap_store -> Pointer(zswap_entry)
- Writeback: Pointer -> decompress -> write to phys -> Redirect(phys)
- Swapin: PTE -> ghost entry -> Redirect -> read from phys device
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/page_io.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/mm/page_io.c b/mm/page_io.c
index 96e776f9a503..b87b46f05b2f 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -697,6 +697,39 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry)
goto finish;
if (unlikely(sis->flags & SWP_GHOST)) {
+ swp_entry_t phys_entry;
+ struct swap_info_struct *phys_si;
+
+ /*
+ * Ghost swap entry: check if there's a Redirect to a
+ * physical swap slot. If so, read from the physical
+ * device instead.
+ */
+ phys_entry = ghost_redirect_to_phys(sis, swp_offset(folio->swap));
+ if (phys_entry.val) {
+ phys_si = __swap_entry_to_info(phys_entry);
+ if (phys_si && phys_si->bdev) {
+ swp_entry_t saved = folio->swap;
+
+ /*
+ * Temporarily point the folio to the
+ * physical entry so the bdev read path
+ * computes the correct sector.
+ */
+ folio->swap = phys_entry;
+ zswap_folio_swapin(folio);
+ if (data_race(phys_si->flags & SWP_FS_OPS))
+ swap_read_folio_fs(folio, plug);
+ else if (phys_si->flags & SWP_SYNCHRONOUS_IO)
+ swap_read_folio_bdev_sync(folio,
+ phys_si);
+ else
+ swap_read_folio_bdev_async(folio,
+ phys_si);
+ folio->swap = saved;
+ goto finish;
+ }
+ }
folio_unlock(folio);
goto finish;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 08/10] mm/swapfile: manage ghost cluster_info via lazy vmalloc
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (6 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 07/10] mm/page_io: forward ghost swap reads to physical device via Redirect Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:26 ` [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Baoquan He
` (3 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Replace the kvzalloc cluster_info allocation for ghost swap with a sparse
vmalloc area (VM_SPARSE). At swapon time, reserve up to 64MB of virtual
address space (supporting ~1TB ghost swap) but only allocate physical
pages for the initial size. The cluster_info pointer never changes —
__swap_offset_to_cluster() and cluster_index() are unchanged.
Add cluster_vm and ghost_name fields to struct swap_info_struct.
In setup_swap_clusters_info(), allocate zeroed physical pages and map
them into the reserved VA via vm_area_map_pages(). Handle teardown in
both swapoff and the swapon error path.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
include/linux/swap.h | 4 +
mm/swapfile.c | 210 +++++++++++++++++++++++++++++++++++--------
2 files changed, 179 insertions(+), 35 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index a4d6851e0921..b87688b7d4ba 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -16,6 +16,8 @@
#include <uapi/linux/mempolicy.h>
#include <asm/page.h>
+struct vm_struct;
+
#define SWAP_FLAG_PREFER 0x8000 /* set if swap priority specified */
#define SWAP_FLAG_PRIO_MASK 0x7fff
#define SWAP_FLAG_DISCARD 0x10000 /* enable discard for swap */
@@ -279,6 +281,8 @@ struct swap_info_struct {
struct plist_node avail_list; /* entry in swap_avail_head */
#ifdef CONFIG_ZSWAP
struct xarray redirect_xa; /* ghost offset → phys swp_entry_t */
+ struct vm_struct *cluster_vm; /* ghost: sparse vm_area for cluster_info */
+ char *ghost_name; /* ghost: dentry name for sysfs */
#endif
};
diff --git a/mm/swapfile.c b/mm/swapfile.c
index c1686c2b2521..da085a75d7b5 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -56,6 +56,9 @@ static bool folio_swapcache_freeable(struct folio *folio);
static void move_cluster(struct swap_info_struct *si,
struct swap_cluster_info *ci, struct list_head *list,
enum swap_cluster_flags new_flags);
+#ifdef CONFIG_ZSWAP
+#define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024)
+#endif
/*
* Protects the swap_info array, and the SWP_USED flag. swap_info contains
@@ -3244,9 +3247,14 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
swap_file = p->swap_file;
p->swap_file = NULL;
maxpages = p->max;
- cluster_info = p->cluster_info;
- p->max = 0;
- p->cluster_info = NULL;
+ if (p->flags & SWP_GHOST) {
+ p->max = 0;
+ cluster_info = NULL;
+ } else {
+ cluster_info = p->cluster_info;
+ p->max = 0;
+ p->cluster_info = NULL;
+ }
spin_unlock(&p->lock);
spin_unlock(&swap_lock);
arch_swap_invalidate_area(p->type);
@@ -3254,7 +3262,31 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
mutex_unlock(&swapon_mutex);
kfree(p->global_cluster);
p->global_cluster = NULL;
- free_swap_cluster_info(cluster_info, maxpages);
+ if (p->flags & SWP_GHOST) {
+ unsigned long j, np = p->cluster_vm ?
+ p->cluster_vm->nr_pages : 0;
+ struct page **pglist = p->cluster_vm ?
+ (struct page **)p->cluster_vm->pages : NULL;
+
+ xa_destroy(&p->redirect_xa);
+ kfree(p->ghost_name);
+ p->ghost_name = NULL;
+ for (j = 0; j < np; j++)
+ __free_page(pglist[j]);
+ kvfree(pglist);
+ if (p->cluster_vm) {
+ vm_area_unmap_pages(p->cluster_vm,
+ (unsigned long)p->cluster_vm->addr,
+ (unsigned long)p->cluster_vm->addr +
+ np * PAGE_SIZE);
+ remove_vm_area(p->cluster_vm->addr);
+ kfree(p->cluster_vm);
+ p->cluster_vm = NULL;
+ }
+ p->cluster_info = NULL;
+ } else {
+ free_swap_cluster_info(cluster_info, maxpages);
+ }
inode = mapping->host;
@@ -3624,16 +3656,48 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
unsigned long maxpages)
{
unsigned long nr_clusters = DIV_ROUND_UP(maxpages, SWAPFILE_CLUSTER);
- struct swap_cluster_info *cluster_info;
+ struct swap_cluster_info *cluster_info = NULL;
int err = -ENOMEM;
unsigned long i;
- cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
- if (!cluster_info)
- goto err;
+ if (si->flags & SWP_GHOST) {
+ unsigned long bytes, nr_pages;
+ struct page **pg;
- for (i = 0; i < nr_clusters; i++)
- spin_lock_init(&cluster_info[i].lock);
+ bytes = ALIGN(nr_clusters * sizeof(struct swap_cluster_info),
+ PAGE_SIZE);
+ nr_pages = bytes / PAGE_SIZE;
+ pg = kvmalloc_array(nr_pages, sizeof(*pg), GFP_KERNEL);
+ if (!pg)
+ goto err;
+ for (i = 0; i < nr_pages; i++) {
+ pg[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!pg[i]) {
+ while (i-- > 0)
+ __free_page(pg[i]);
+ kvfree(pg);
+ goto err;
+ }
+ }
+ err = vm_area_map_pages(si->cluster_vm,
+ (unsigned long)si->cluster_vm->addr,
+ (unsigned long)si->cluster_vm->addr + bytes, pg);
+ if (err) {
+ for (i = 0; i < nr_pages; i++)
+ __free_page(pg[i]);
+ kvfree(pg);
+ goto err;
+ }
+ si->cluster_vm->pages = (void *)pg;
+ si->cluster_vm->nr_pages = nr_pages;
+ cluster_info = si->cluster_info;
+ } else {
+ cluster_info = kvzalloc_objs(*cluster_info, nr_clusters);
+ if (!cluster_info)
+ goto err;
+ for (i = 0; i < nr_clusters; i++)
+ spin_lock_init(&cluster_info[i].lock);
+ }
if (!(si->flags & SWP_SOLIDSTATE)) {
si->global_cluster = kmalloc_obj(*si->global_cluster);
@@ -3644,37 +3708,64 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
spin_lock_init(&si->global_cluster_lock);
}
- /*
- * Mark unusable pages (header page, bad pages, and the EOF part of
- * the last cluster) as unavailable. The clusters aren't marked free
- * yet, so no list operations are involved yet.
- */
- err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
- if (err)
- goto err;
- for (i = 0; i < swap_header->info.nr_badpages; i++) {
- unsigned int page_nr = swap_header->info.badpages[i];
-
- if (!page_nr || page_nr > swap_header->info.last_page) {
- pr_warn("Bad slot offset is out of border: %d (last_page: %d)\n",
- page_nr, swap_header->info.last_page);
- err = -EINVAL;
+ if (si->flags & SWP_GHOST) {
+ struct swap_cluster_info *ci;
+
+ ci = &cluster_info[0];
+ if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
+ err = -ENOMEM;
goto err;
}
- err = swap_cluster_setup_bad_slot(si, cluster_info, page_nr, false);
- if (err)
- goto err;
- }
- for (i = maxpages; i < round_up(maxpages, SWAPFILE_CLUSTER); i++) {
- err = swap_cluster_setup_bad_slot(si, cluster_info, i, true);
+ spin_lock(&ci->lock);
+ __swap_table_set(ci, 0, SWP_TB_BAD);
+ ci->count++;
+ spin_unlock(&ci->lock);
+ if (nr_clusters && (maxpages % SWAPFILE_CLUSTER)) {
+ unsigned long base = (maxpages - 1) /
+ SWAPFILE_CLUSTER * SWAPFILE_CLUSTER;
+ unsigned long idx = base / SWAPFILE_CLUSTER;
+
+ ci = &cluster_info[idx];
+ if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
+ err = -ENOMEM;
+ goto err;
+ }
+ spin_lock(&ci->lock);
+ for (i = maxpages % SWAPFILE_CLUSTER;
+ i < SWAPFILE_CLUSTER; i++)
+ __swap_table_set(ci, i, SWP_TB_BAD);
+ spin_unlock(&ci->lock);
+ }
+ } else {
+ err = swap_cluster_setup_bad_slot(si, cluster_info, 0, false);
if (err)
goto err;
+ for (i = 0; i < swap_header->info.nr_badpages; i++) {
+ unsigned int page_nr = swap_header->info.badpages[i];
+
+ if (!page_nr || page_nr > swap_header->info.last_page) {
+ pr_warn("Bad slot offset is out of border: "
+ "%d (last_page: %d)\n",
+ page_nr, swap_header->info.last_page);
+ err = -EINVAL;
+ goto err;
+ }
+ err = swap_cluster_setup_bad_slot(si, cluster_info,
+ page_nr, false);
+ if (err)
+ goto err;
+ }
+ for (i = maxpages; i < round_up(maxpages, SWAPFILE_CLUSTER); i++) {
+ err = swap_cluster_setup_bad_slot(si, cluster_info,
+ i, true);
+ if (err)
+ goto err;
+ }
}
INIT_LIST_HEAD(&si->free_clusters);
INIT_LIST_HEAD(&si->full_clusters);
INIT_LIST_HEAD(&si->discard_clusters);
-
for (i = 0; i < SWAP_NR_ORDERS; i++) {
INIT_LIST_HEAD(&si->nonfull_clusters[i]);
INIT_LIST_HEAD(&si->frag_clusters[i]);
@@ -3692,9 +3783,12 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
}
}
- si->cluster_info = cluster_info;
+ if (!(si->flags & SWP_GHOST))
+ si->cluster_info = cluster_info;
return 0;
err:
+ if (si->flags & SWP_GHOST)
+ return err;
free_swap_cluster_info(cluster_info, maxpages);
return err;
}
@@ -3790,6 +3884,28 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
si->max = maxpages;
si->pages = maxpages - 1;
+ if (si->flags & SWP_GHOST) {
+ unsigned long max_cl_bytes;
+
+ max_cl_bytes = swapfile_maximum_size / SWAPFILE_CLUSTER *
+ sizeof(struct swap_cluster_info);
+ max_cl_bytes = min(ALIGN(max_cl_bytes, PAGE_SIZE),
+ GHOST_MAX_CLUSTER_BYTES);
+ si->cluster_vm = get_vm_area_caller(max_cl_bytes, VM_SPARSE,
+ __builtin_return_address(0));
+ if (!si->cluster_vm) {
+ error = -ENOMEM;
+ goto bad_swap_unlock_inode;
+ }
+ si->cluster_info = si->cluster_vm->addr;
+ si->cluster_vm->nr_pages = 0;
+ si->cluster_vm->pages = NULL;
+ si->ghost_name = kstrdup(dentry->d_name.name, GFP_KERNEL);
+ if (!si->ghost_name) {
+ error = -ENOMEM;
+ goto bad_swap_unlock_inode;
+ }
+ }
nr_extents = setup_swap_extents(si, swap_file, &span);
if (nr_extents < 0) {
error = nr_extents;
@@ -3907,8 +4023,32 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
si->global_cluster = NULL;
inode = NULL;
destroy_swap_extents(si, swap_file);
- free_swap_cluster_info(si->cluster_info, si->max);
- si->cluster_info = NULL;
+ if (si->flags & SWP_GHOST) {
+ unsigned long j, np = si->cluster_vm ?
+ si->cluster_vm->nr_pages : 0;
+ struct page **pglist = si->cluster_vm ?
+ (struct page **)si->cluster_vm->pages : NULL;
+
+ xa_destroy(&si->redirect_xa);
+ kfree(si->ghost_name);
+ si->ghost_name = NULL;
+ for (j = 0; j < np; j++)
+ __free_page(pglist[j]);
+ kvfree(pglist);
+ if (si->cluster_vm) {
+ vm_area_unmap_pages(si->cluster_vm,
+ (unsigned long)si->cluster_vm->addr,
+ (unsigned long)si->cluster_vm->addr +
+ np * PAGE_SIZE);
+ remove_vm_area(si->cluster_vm->addr);
+ kfree(si->cluster_vm);
+ si->cluster_vm = NULL;
+ }
+ si->cluster_info = NULL;
+ } else {
+ free_swap_cluster_info(si->cluster_info, si->max);
+ si->cluster_info = NULL;
+ }
/*
* Clear the SWP_USED flag after all resources are freed so
* alloc_swap_info can reuse this si safely.
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (7 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 08/10] mm/swapfile: manage ghost cluster_info via lazy vmalloc Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 22:36 ` Nhat Pham
2026-07-07 8:26 ` [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension Baoquan He
` (2 subsequent siblings)
11 siblings, 1 reply; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Allocate additional physical pages and map them at the end of the
already-reserved sparse vmalloc area via vm_area_map_pages(). The flat
cluster_info pointer never moves — new clusters appear contiguously at
higher indices. New clusters are fully initialized (spinlocks, list
heads, trailing BAD mask) before si->max is published via WRITE_ONCE.
Page-pointer tracking in cluster_vm->pages is reallocated to accommodate
additional entries. Returns -E2BIG if the requested size exceeds the
reserved VA range. Extension is serialized by swapon_mutex.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
mm/swapfile.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index da085a75d7b5..8bf336678887 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -58,6 +58,8 @@ static void move_cluster(struct swap_info_struct *si,
enum swap_cluster_flags new_flags);
#ifdef CONFIG_ZSWAP
#define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024)
+static int swap_ghost_extend_max(struct swap_info_struct *si,
+ unsigned int new_maxpages);
#endif
/*
@@ -4247,6 +4249,114 @@ swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si, pgoff_t offset)
return (swp_entry_t){};
}
+
+/*
+ * swap_ghost_extend_max — Extend an active ghost swap device.
+ * @si: ghost device (SWP_GHOST set)
+ * @new: new max pages (> si->max)
+ *
+ * Allocates physical pages and maps them at the end of the reserved
+ * sparse vmalloc area. si->cluster_info never moves. New clusters
+ * are fully initialized before si->max is published via WRITE_ONCE.
+ *
+ * Returns 0, -ENOMEM, -EINVAL, or -E2BIG. Caller holds swapon_mutex.
+ */
+static int swap_ghost_extend_max(struct swap_info_struct *si,
+ unsigned int new)
+{
+ unsigned long old_max = si->max, old_nr, new_nr;
+ unsigned long old_bytes, new_bytes, nr_new_pg;
+ struct page **new_pglist;
+ unsigned long i;
+
+ if (!(si->flags & SWP_GHOST) ||
+ !(si->flags & (SWP_USED | SWP_WRITEOK)) ||
+ new <= si->max)
+ return -EINVAL;
+
+ old_nr = DIV_ROUND_UP(si->max, SWAPFILE_CLUSTER);
+ new_nr = DIV_ROUND_UP(new, SWAPFILE_CLUSTER);
+ old_bytes = ALIGN(old_nr * sizeof(struct swap_cluster_info), PAGE_SIZE);
+ new_bytes = ALIGN(new_nr * sizeof(struct swap_cluster_info), PAGE_SIZE);
+ nr_new_pg = (new_bytes - old_bytes) / PAGE_SIZE;
+
+ if (old_bytes + nr_new_pg * PAGE_SIZE > si->cluster_vm->size) {
+ pr_warn("ghost: extend to %u exceeds reserved VA (%lu bytes)\n",
+ new, si->cluster_vm->size);
+ return -E2BIG;
+ }
+
+ new_pglist = kvmalloc_array(si->cluster_vm->nr_pages + nr_new_pg,
+ sizeof(struct page *), GFP_KERNEL);
+ if (!new_pglist)
+ return -ENOMEM;
+ if (si->cluster_vm->pages) {
+ memcpy(new_pglist, si->cluster_vm->pages,
+ si->cluster_vm->nr_pages * sizeof(struct page *));
+ kvfree(si->cluster_vm->pages);
+ }
+ si->cluster_vm->pages = (void *)new_pglist;
+
+ for (i = 0; i < nr_new_pg; i++) {
+ struct page *page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page) {
+ while (i-- > 0)
+ __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
+ return -ENOMEM;
+ }
+ new_pglist[si->cluster_vm->nr_pages + i] = page;
+ }
+
+ if (vm_area_map_pages(si->cluster_vm,
+ (unsigned long)si->cluster_vm->addr + old_bytes,
+ (unsigned long)si->cluster_vm->addr + new_bytes,
+ &new_pglist[si->cluster_vm->nr_pages])) {
+ for (i = 0; i < nr_new_pg; i++)
+ __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
+ return -ENOMEM;
+ }
+ si->cluster_vm->nr_pages += nr_new_pg;
+
+ for (i = old_nr; i < new_nr; i++) {
+ struct swap_cluster_info *ci = &si->cluster_info[i];
+ spin_lock_init(&ci->lock);
+ INIT_LIST_HEAD(&ci->list);
+ }
+
+ if (new % SWAPFILE_CLUSTER) {
+ unsigned long base = (new - 1) / SWAPFILE_CLUSTER *
+ SWAPFILE_CLUSTER;
+ unsigned long idx = base / SWAPFILE_CLUSTER;
+ struct swap_cluster_info *ci = &si->cluster_info[idx];
+
+ if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
+ for (i = 0; i < nr_new_pg; i++)
+ __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
+ return -ENOMEM;
+ }
+ spin_lock(&ci->lock);
+ for (i = new % SWAPFILE_CLUSTER; i < SWAPFILE_CLUSTER; i++)
+ __swap_table_set(ci, i, SWP_TB_BAD);
+ spin_unlock(&ci->lock);
+ }
+
+ WRITE_ONCE(si->max, new);
+
+ for (i = old_nr; i < new_nr; i++) {
+ struct swap_cluster_info *ci = &si->cluster_info[i];
+ ci->flags = CLUSTER_FLAG_FREE;
+ spin_lock(&si->lock);
+ list_add_tail(&ci->list, &si->free_clusters);
+ spin_unlock(&si->lock);
+ }
+
+ si->pages += new - old_max;
+ atomic_long_add(new - old_max, &nr_swap_pages);
+ spin_lock(&swap_lock);
+ total_swap_pages += new - old_max;
+ spin_unlock(&swap_lock);
+ return 0;
+}
#endif /* CONFIG_ZSWAP */
static int __init swapfile_init(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (8 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Baoquan He
@ 2026-07-07 8:26 ` Baoquan He
2026-07-07 8:56 ` [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 21:23 ` Nhat Pham
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:26 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel, Baoquan He
Add per-device attributes under /sys/kernel/mm/ghost_swap/ghost_<N>/:
path (RO) — dentry basename
max (RW) — current max pages; write to trigger swap_ghost_extend_max()
The ghost_kobj field ties each ghost device to its sysfs directory,
created at swapon and removed at swapoff.
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
include/linux/swap.h | 1 +
mm/swapfile.c | 111 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 112 insertions(+)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index b87688b7d4ba..d9ae2a9e33cc 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -283,6 +283,7 @@ struct swap_info_struct {
struct xarray redirect_xa; /* ghost offset → phys swp_entry_t */
struct vm_struct *cluster_vm; /* ghost: sparse vm_area for cluster_info */
char *ghost_name; /* ghost: dentry name for sysfs */
+ struct kobject *ghost_kobj; /* ghost: sysfs per-device directory */
#endif
};
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 8bf336678887..ede10539156a 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -31,6 +31,8 @@
#include <linux/security.h>
#include <linux/backing-dev.h>
#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
#include <linux/capability.h>
#include <linux/syscalls.h>
#include <linux/memcontrol.h>
@@ -60,6 +62,8 @@ static void move_cluster(struct swap_info_struct *si,
#define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024)
static int swap_ghost_extend_max(struct swap_info_struct *si,
unsigned int new_maxpages);
+static int ghost_sysfs_register(struct swap_info_struct *si);
+static void ghost_sysfs_unregister(struct swap_info_struct *si);
#endif
/*
@@ -3250,6 +3254,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
p->swap_file = NULL;
maxpages = p->max;
if (p->flags & SWP_GHOST) {
+ ghost_sysfs_unregister(p);
p->max = 0;
cluster_info = NULL;
} else {
@@ -4010,6 +4015,13 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
(si->flags & SWP_AREA_DISCARD) ? "s" : "",
(si->flags & SWP_PAGE_DISCARD) ? "c" : "");
+ if (si->flags & SWP_GHOST) {
+ error = ghost_sysfs_register(si);
+ if (error)
+ pr_warn("swapon: ghost sysfs register failed: %d\n",
+ error);
+ }
+
mutex_unlock(&swapon_mutex);
atomic_inc(&proc_poll_event);
wake_up_interruptible(&proc_poll_wait);
@@ -4357,6 +4369,105 @@ static int swap_ghost_extend_max(struct swap_info_struct *si,
spin_unlock(&swap_lock);
return 0;
}
+
+/* ─── Ghost swap sysfs interface ─── */
+
+static struct kobject *ghost_swap_kobj;
+
+static struct swap_info_struct *ghost_kobj_to_si(struct kobject *kobj)
+{
+ int type;
+
+ for (type = 0; type < MAX_SWAPFILES; type++) {
+ struct swap_info_struct *si = READ_ONCE(swap_info[type]);
+
+ if (si && si->ghost_kobj == kobj && (si->flags & SWP_USED))
+ return si;
+ }
+ return NULL;
+}
+
+#define GHOST_ATTR_RO(_n) \
+ static ssize_t _n##_show(struct kobject *k, struct kobj_attribute *a, char *b)
+#define GHOST_ATTR_WO(_n) \
+ static ssize_t _n##_store(struct kobject *k, struct kobj_attribute *a, \
+ const char *b, size_t n)
+
+GHOST_ATTR_RO(path)
+{
+ struct swap_info_struct *si = ghost_kobj_to_si(k);
+
+ if (!si || !si->ghost_name)
+ return -ENODEV;
+ return sysfs_emit(b, "%s\n", si->ghost_name);
+}
+
+GHOST_ATTR_RO(max)
+{
+ struct swap_info_struct *si = ghost_kobj_to_si(k);
+
+ if (!si)
+ return -ENODEV;
+ return sysfs_emit(b, "%u\n", si->max);
+}
+
+GHOST_ATTR_WO(max)
+{
+ struct swap_info_struct *si = ghost_kobj_to_si(k);
+ unsigned long val;
+ int ret;
+
+ if (!si)
+ return -ENODEV;
+ ret = kstrtoul(b, 10, &val);
+ if (ret)
+ return ret;
+ if (mutex_lock_interruptible(&swapon_mutex))
+ return -ERESTARTSYS;
+ ret = swap_ghost_extend_max(si, (unsigned int)val);
+ mutex_unlock(&swapon_mutex);
+ return ret ? ret : n;
+}
+
+static struct kobj_attribute ghost_attr_path = __ATTR_RO(path);
+static struct kobj_attribute ghost_attr_max = __ATTR_RW(max);
+static struct attribute *ghost_attrs[] = {
+ &ghost_attr_path.attr,
+ &ghost_attr_max.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ghost);
+
+static int ghost_sysfs_register(struct swap_info_struct *si)
+{
+ char name[32];
+ int ret;
+
+ if (!ghost_swap_kobj) {
+ ghost_swap_kobj = kobject_create_and_add("ghost_swap", mm_kobj);
+ if (!ghost_swap_kobj)
+ return -ENOMEM;
+ }
+ snprintf(name, sizeof(name), "ghost_%d", si->type);
+ si->ghost_kobj = kobject_create_and_add(name, ghost_swap_kobj);
+ if (!si->ghost_kobj)
+ return -ENOMEM;
+ ret = sysfs_create_groups(si->ghost_kobj, ghost_groups);
+ if (ret) {
+ kobject_put(si->ghost_kobj);
+ si->ghost_kobj = NULL;
+ }
+ return ret;
+}
+
+static void ghost_sysfs_unregister(struct swap_info_struct *si)
+{
+ if (!si->ghost_kobj)
+ return;
+ sysfs_remove_groups(si->ghost_kobj, ghost_groups);
+ kobject_put(si->ghost_kobj);
+ si->ghost_kobj = NULL;
+}
#endif /* CONFIG_ZSWAP */
static int __init swapfile_init(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (9 preceding siblings ...)
2026-07-07 8:26 ` [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension Baoquan He
@ 2026-07-07 8:56 ` Baoquan He
2026-07-07 21:23 ` Nhat Pham
11 siblings, 0 replies; 15+ messages in thread
From: Baoquan He @ 2026-07-07 8:56 UTC (permalink / raw)
To: linux-mm
Cc: akpm, chrisl, kasong, shikemeng, nphamcs, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel
On 07/07/26 at 04:26pm, Baoquan He wrote:
> This series introduces a virtual "ghost" swapfile that can be backed
> by multiple physical swap devices, switching between them at runtime
> using a new "Redirect" swap table entry type.
Forgot mentioning this patchset is based on below patchset:
[RFC PATCH 0/7] mm: store zswap entries in swap table (Pointer entry)
https://lore.kernel.org/all/20260707073215.72183-1-baoquan.he@linux.dev/T/#u
and they sit on top of akpm/mm-unstale commit cfb8731f5396
("mm: fix CONFIG_STACK_GROWSUP typo in tools/testing/vma/include/dup.h")
>
> Motivation
> ----------
> Physical swap devices have fixed, large sizes but a machine may only
> need a fraction at any given time. Ghost swap decouples the logical
> swap capacity from physical swap allocation: it presents a large
> virtual swap device to userspace while lazily mapping slots to
> physical devices only when data must leave zswap (writeback). This
> enables overcommit of swap capacity without wasting physical disk
> space, and allows the ghost device to grow dynamically at runtime.
>
> Ghost swapfile characteristics
> --------------------------------
> Ghost swap decouples logical swap capacity from physical swap allocation.
> Key properties:
>
> - No backing store required. A ghost swap device can operate purely
> as a zswap container ??? compressed pages stay in the zswap pool without
> ever touching a physical swap device. No physical swap device needs
> to be attached.
>
> - Optional physical backing. One or more physical swap devices can be
> swapon'd alongside the ghost device, acting as writeback targets when
> the zswap pool needs to evict cold pages. The Redirect entry in the
> ghost swap table links each evicted slot to its physical destination.
>
> - Dynamic size growth. The ghost device can be extended at runtime via
> sysfs, up to a current maximum of 1TB. This is implemented through
> lazy vmalloc: a sparse 64MB virtual address space is reserved at
> swapon time to cover the full 1TB range, but physical pages for
> cluster_info[] structures are allocated only on demand as the device
> grows. The cluster_info pointer never changes, so all existing
> accessors (__swap_offset_to_cluster, cluster_index) work unchanged.
>
> - sysfs control. Each ghost device exposes attributes under
> /sys/kernel/mm/ghost_swap/ghost_<N>/:
> path (RO) ??? backing device name
> max (RW) ??? current max pages; writing a larger value triggers
> swap_ghost_extend_max()
>
> Design overview
> ---------------
> A swap table slot can now hold a Redirect entry:
>
> Redirect: |----- physical swp_entry_t -----|101|
>
> This stores a plain swp_entry_t pointing to a slot on a real (physical)
> swap device. The low 3-bit mark (0b101) distinguishes it from Pointer
> (0b100), PFN (0b10), Shadow (0b1), and NULL (0) entries.
>
> The lifecycle is:
>
> 1. swapon a ghost device ??? reserves sparse vmalloc area (64MB virtual)
> and physically backs only the initial range. The device starts with
> no physical backing store: pages swapped out go to zswap, and zswap
> pool evictions go to any concurrently attached physical device.
>
> 2. zswap writeback from ghost: when the zswap pool needs to evict,
> zswap_writeback_ghost() allocates a physical slot from any real
> swap device, writes the data there, and plants a Redirect entry
> in the ghost's swap table pointing to the physical slot.
>
> 3. Swap-in from ghost: swap_read_folio() detects the ghost device
> (SWP_GHOST), resolves the Redirect to the physical swp_entry_t,
> and forwards the read I/O to the physical device.
>
> 4. When the swap cache folio is removed, the Redirect entry is restored
> from an xarray (redirect_xa) that persists the mapping, so
> subsequent reads still find the physical backing.
>
> 5. When the physical slot is freed, the ghost slot is also freed,
> cascading through swap_range_free().
>
> Note:
> ------
> Ghost swapfile is a proof of concept. Compared with the other patchset:
> [RFC PATCH v2 0/7] mm, swap: Virtual Swap Space (Swap Table Edition),
> it explores a different approach to implementing virtual swap on top of
> the existing swap table infrastructure, with a simpler implementation.
> It builds on Chris Li's earlier ghost swapfile work and is currently
> tentatively named "ghost swapfile" ??? alternative naming suggestions are
> welcome. Ghost swapfile setup can also be integrated through modifications
> to the swapon API and the swapon command in util-linux, for example:
> swapon --ghost ghost_1:2G -p 5.
>
> Baoquan He (9):
> mm/swap_table: add Redirect entry encoding for ghost swap backend
> switching
> mm/swap: add redirect_xa field and ghost redirect helper declarations
> mm/swapfile: implement ghost redirect helpers and free-path cascade
> mm/swap_state: restore Redirect entry when swap cache folio is removed
> mm/zswap: implement ghost-to-physical writeback for backend switching
> mm/page_io: forward ghost swap reads to physical device via Redirect
> mm/swapfile: manage ghost cluster_info via lazy vmalloc
> mm/swapfile: implement swap_ghost_extend_max() for dynamic growth
> mm/swapfile: add sysfs interface for ghost swap extension
>
> Chris Li (1):
> mm: ghost swapfile support for zswap
>
> include/linux/swap.h | 10 +
> mm/page_io.c | 51 +++-
> mm/swap.h | 16 +-
> mm/swap_state.c | 28 +++
> mm/swap_table.h | 38 +++
> mm/swapfile.c | 579 ++++++++++++++++++++++++++++++++++++++++---
> mm/zswap.c | 98 +++++++-
> 7 files changed, 772 insertions(+), 48 deletions(-)
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
` (10 preceding siblings ...)
2026-07-07 8:56 ` [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
@ 2026-07-07 21:23 ` Nhat Pham
2026-07-07 21:25 ` Nhat Pham
11 siblings, 1 reply; 15+ messages in thread
From: Nhat Pham @ 2026-07-07 21:23 UTC (permalink / raw)
To: Baoquan He
Cc: linux-mm, akpm, chrisl, kasong, shikemeng, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel
On Tue, Jul 7, 2026 at 1:26 AM Baoquan He <baoquan.he@linux.dev> wrote:
>
> This series introduces a virtual "ghost" swapfile that can be backed
> by multiple physical swap devices, switching between them at runtime
> using a new "Redirect" swap table entry type.
>
> Motivation
> ----------
> Physical swap devices have fixed, large sizes but a machine may only
> need a fraction at any given time. Ghost swap decouples the logical
> swap capacity from physical swap allocation: it presents a large
> virtual swap device to userspace while lazily mapping slots to
> physical devices only when data must leave zswap (writeback). This
> enables overcommit of swap capacity without wasting physical disk
> space, and allows the ghost device to grow dynamically at runtime.
>
> Ghost swapfile characteristics
> --------------------------------
> Ghost swap decouples logical swap capacity from physical swap allocation.
> Key properties:
>
> - No backing store required. A ghost swap device can operate purely
> as a zswap container — compressed pages stay in the zswap pool without
> ever touching a physical swap device. No physical swap device needs
> to be attached.
>
> - Optional physical backing. One or more physical swap devices can be
> swapon'd alongside the ghost device, acting as writeback targets when
> the zswap pool needs to evict cold pages. The Redirect entry in the
> ghost swap table links each evicted slot to its physical destination.
>
> - Dynamic size growth. The ghost device can be extended at runtime via
We don't want to put additional operational burden on users. Besides,
expecting userspace to react to swap space being filled, especially
under memory pressure (at which point userspace processes might be
stalling already), is tricky.
This is especially bad when redirection entry still eats up space in
the upper level (ghost zswap) swapfile. You will run out of zswap
space really fast under concurrent zswap store and zswap writeback.
> sysfs, up to a current maximum of 1TB. This is implemented through
> lazy vmalloc: a sparse 64MB virtual address space is reserved at
> swapon time to cover the full 1TB range, but physical pages for
This range seems a bit small, especially from something essentially fixed.
> cluster_info[] structures are allocated only on demand as the device
> grows. The cluster_info pointer never changes, so all existing
> accessors (__swap_offset_to_cluster, cluster_index) work unchanged.
>
> - sysfs control. Each ghost device exposes attributes under
> /sys/kernel/mm/ghost_swap/ghost_<N>/:
> path (RO) — backing device name
> max (RW) — current max pages; writing a larger value triggers
> swap_ghost_extend_max()
>
> Design overview
> ---------------
> A swap table slot can now hold a Redirect entry:
>
> Redirect: |----- physical swp_entry_t -----|101|
>
> This stores a plain swp_entry_t pointing to a slot on a real (physical)
> swap device. The low 3-bit mark (0b101) distinguishes it from Pointer
> (0b100), PFN (0b10), Shadow (0b1), and NULL (0) entries.
>
> The lifecycle is:
>
> 1. swapon a ghost device — reserves sparse vmalloc area (64MB virtual)
> and physically backs only the initial range. The device starts with
> no physical backing store: pages swapped out go to zswap, and zswap
> pool evictions go to any concurrently attached physical device.
If you think this data structure is superior to the xarray for dynamic
swap address, it's a fairly simple change on top of what I had in the
virtual swap RFC, no?
It can be grafted on top of what I had in a subsequent patch, or
squashed into my first patch. If you have concerns with me
implementing your idea here, I'd take your code if it proves to be
superior.
>
> 2. zswap writeback from ghost: when the zswap pool needs to evict,
> zswap_writeback_ghost() allocates a physical slot from any real
> swap device, writes the data there, and plants a Redirect entry
> in the ghost's swap table pointing to the physical slot.
What if user swapoff the physical swapfile, whose slot is backing a
redirection entry of a ghost swapfile, rather than a PTE. Has this
been handled yet?
Similarly, say the physical swapfile is full, and you attempt to
reclaim a physical swap slot that is swap-cache-only (swap count = 0).
Say this physical swap slot was allocated from a previous zswap
writeback attempt, i.e it has a redirection entry in the ghost zswap
swapfile associated with it. Did we handle it somehow?
My apologies if I missed it somehow.
>
> 3. Swap-in from ghost: swap_read_folio() detects the ghost device
> (SWP_GHOST), resolves the Redirect to the physical swp_entry_t,
> and forwards the read I/O to the physical device.
>
> 4. When the swap cache folio is removed, the Redirect entry is restored
> from an xarray (redirect_xa) that persists the mapping, so
> subsequent reads still find the physical backing.
>
> 5. When the physical slot is freed, the ghost slot is also freed,
> cascading through swap_range_free().
I don't get how *any* of this is simpler from what I had. If anything,
it's more complicated. With a vswap device, you just allocate a
physical swap slot (which you also did), change the backend, submit
the IO (which you also did), and that's it. No need to juggle this
redirect_xa entry. What does this redirect_xa buy us?
Not to mention now it entagles generic swap logic into
backend-specific code, unnecessarily. And we have more state in the
state machine to consider: With a generic vswap device, vswap -> disk
swap directly is the same state as vswap -> written back zswap entry.
With this zswap ghost swapfile design, you have to treat the latter
separately, as there is a redirection entry involved, which means
concurrent swap operations interact with it.
So it's more complicated, and achieve less - this is specific to
zswap, whereas what I provide is something generic to swap
infrastructure, which can be extended for future backends. I would
understand if it resulted in less overhead (which was the argument for
my pre-swap-table vswap design), but this seems like it's just virtual
swap, with similar space overhead (technically a bit worse since you
added redirect_xa xarray to physical swap cluster too), and with more
complications?
>
> Note:
> ------
> Ghost swapfile is a proof of concept. Compared with the other patchset:
> [RFC PATCH v2 0/7] mm, swap: Virtual Swap Space (Swap Table Edition),
> it explores a different approach to implementing virtual swap on top of
> the existing swap table infrastructure, with a simpler implementation.
You neglected to mention that this "simpler" implementation lacks a
lot of design choices. Just on top of my head:
1. Certain optimizations for cases when vswap points to disk swap.
2. No reverse mapping, or at least some mechanisms to handle the case
where you have to go from physical swap slots to the upper layer.
3. Charging behavior difference, and deduplication of metadata when we
have multiple swap levels. (In fact, you seem to be double charging a
writtenback entry here towards the swap counter, not just duplicating
the metadata space)
4. Infrastructure to make sure allocation from this swap device and
allocation from the backing swap device does not invalidate each
other's per-cpu allocation cache. You seem to just go for the slow
path with alloc_phys_swap_slot() for zswap writeback?
It's fine to disagree with any or all of these design choices of mine
- I'm receptive to discussion. But it's misleading to say you have a
simpler design here, IMHO. My design choices resulted from multiple
thoughtful iterations (a lot of them could be found in my
pre-swap-table version of vswap) and real production pains.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries
2026-07-07 21:23 ` Nhat Pham
@ 2026-07-07 21:25 ` Nhat Pham
0 siblings, 0 replies; 15+ messages in thread
From: Nhat Pham @ 2026-07-07 21:25 UTC (permalink / raw)
To: Baoquan He
Cc: linux-mm, akpm, chrisl, kasong, shikemeng, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel
On Tue, Jul 7, 2026 at 2:23 PM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Tue, Jul 7, 2026 at 1:26 AM Baoquan He <baoquan.he@linux.dev> wrote:
>
> So it's more complicated, and achieve less - this is specific to
> zswap, whereas what I provide is something generic to swap
> infrastructure, which can be extended for future backends. I would
> understand if it resulted in less overhead (which was the argument for
> my pre-swap-table vswap design), but this seems like it's just virtual
> swap, with similar space overhead (technically a bit worse since you
> added redirect_xa xarray to physical swap cluster too), and with more
> complications?
Actually no - I just realized redirect_xa is global per-swapfile :)
It's not even cluster-partitioned.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth
2026-07-07 8:26 ` [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Baoquan He
@ 2026-07-07 22:36 ` Nhat Pham
0 siblings, 0 replies; 15+ messages in thread
From: Nhat Pham @ 2026-07-07 22:36 UTC (permalink / raw)
To: Baoquan He
Cc: linux-mm, akpm, chrisl, kasong, shikemeng, baohua, youngjun.park,
hannes, yosry, chengming.zhou, linux-kernel
On Tue, Jul 7, 2026 at 1:27 AM Baoquan He <baoquan.he@linux.dev> wrote:
>
> Allocate additional physical pages and map them at the end of the
> already-reserved sparse vmalloc area via vm_area_map_pages(). The flat
> cluster_info pointer never moves — new clusters appear contiguously at
> higher indices. New clusters are fully initialized (spinlocks, list
> heads, trailing BAD mask) before si->max is published via WRITE_ONCE.
>
> Page-pointer tracking in cluster_vm->pages is reallocated to accommodate
> additional entries. Returns -E2BIG if the requested size exceeds the
> reserved VA range. Extension is serialized by swapon_mutex.
>
> Signed-off-by: Baoquan He <baoquan.he@linux.dev>
> ---
> mm/swapfile.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 110 insertions(+)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index da085a75d7b5..8bf336678887 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -58,6 +58,8 @@ static void move_cluster(struct swap_info_struct *si,
> enum swap_cluster_flags new_flags);
> #ifdef CONFIG_ZSWAP
> #define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024)
> +static int swap_ghost_extend_max(struct swap_info_struct *si,
> + unsigned int new_maxpages);
> #endif
>
> /*
> @@ -4247,6 +4249,114 @@ swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si, pgoff_t offset)
>
> return (swp_entry_t){};
> }
> +
> +/*
> + * swap_ghost_extend_max — Extend an active ghost swap device.
> + * @si: ghost device (SWP_GHOST set)
> + * @new: new max pages (> si->max)
> + *
> + * Allocates physical pages and maps them at the end of the reserved
> + * sparse vmalloc area. si->cluster_info never moves. New clusters
> + * are fully initialized before si->max is published via WRITE_ONCE.
> + *
> + * Returns 0, -ENOMEM, -EINVAL, or -E2BIG. Caller holds swapon_mutex.
> + */
> +static int swap_ghost_extend_max(struct swap_info_struct *si,
> + unsigned int new)
> +{
> + unsigned long old_max = si->max, old_nr, new_nr;
> + unsigned long old_bytes, new_bytes, nr_new_pg;
> + struct page **new_pglist;
> + unsigned long i;
> +
> + if (!(si->flags & SWP_GHOST) ||
> + !(si->flags & (SWP_USED | SWP_WRITEOK)) ||
> + new <= si->max)
> + return -EINVAL;
> +
> + old_nr = DIV_ROUND_UP(si->max, SWAPFILE_CLUSTER);
> + new_nr = DIV_ROUND_UP(new, SWAPFILE_CLUSTER);
> + old_bytes = ALIGN(old_nr * sizeof(struct swap_cluster_info), PAGE_SIZE);
> + new_bytes = ALIGN(new_nr * sizeof(struct swap_cluster_info), PAGE_SIZE);
> + nr_new_pg = (new_bytes - old_bytes) / PAGE_SIZE;
> +
> + if (old_bytes + nr_new_pg * PAGE_SIZE > si->cluster_vm->size) {
> + pr_warn("ghost: extend to %u exceeds reserved VA (%lu bytes)\n",
> + new, si->cluster_vm->size);
> + return -E2BIG;
> + }
> +
> + new_pglist = kvmalloc_array(si->cluster_vm->nr_pages + nr_new_pg,
> + sizeof(struct page *), GFP_KERNEL);
> + if (!new_pglist)
> + return -ENOMEM;
> + if (si->cluster_vm->pages) {
> + memcpy(new_pglist, si->cluster_vm->pages,
> + si->cluster_vm->nr_pages * sizeof(struct page *));
> + kvfree(si->cluster_vm->pages);
> + }
> + si->cluster_vm->pages = (void *)new_pglist;
> +
> + for (i = 0; i < nr_new_pg; i++) {
> + struct page *page = alloc_page(GFP_KERNEL | __GFP_ZERO);
> + if (!page) {
> + while (i-- > 0)
> + __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
> + return -ENOMEM;
> + }
> + new_pglist[si->cluster_vm->nr_pages + i] = page;
> + }
> +
> + if (vm_area_map_pages(si->cluster_vm,
> + (unsigned long)si->cluster_vm->addr + old_bytes,
> + (unsigned long)si->cluster_vm->addr + new_bytes,
> + &new_pglist[si->cluster_vm->nr_pages])) {
> + for (i = 0; i < nr_new_pg; i++)
> + __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
> + return -ENOMEM;
> + }
> + si->cluster_vm->nr_pages += nr_new_pg;
> +
> + for (i = old_nr; i < new_nr; i++) {
> + struct swap_cluster_info *ci = &si->cluster_info[i];
> + spin_lock_init(&ci->lock);
> + INIT_LIST_HEAD(&ci->list);
> + }
> +
> + if (new % SWAPFILE_CLUSTER) {
> + unsigned long base = (new - 1) / SWAPFILE_CLUSTER *
> + SWAPFILE_CLUSTER;
> + unsigned long idx = base / SWAPFILE_CLUSTER;
> + struct swap_cluster_info *ci = &si->cluster_info[idx];
> +
> + if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) {
> + for (i = 0; i < nr_new_pg; i++)
> + __free_page(new_pglist[si->cluster_vm->nr_pages + i]);
> + return -ENOMEM;
> + }
> + spin_lock(&ci->lock);
> + for (i = new % SWAPFILE_CLUSTER; i < SWAPFILE_CLUSTER; i++)
> + __swap_table_set(ci, i, SWP_TB_BAD);
> + spin_unlock(&ci->lock);
> + }
> +
> + WRITE_ONCE(si->max, new);
> +
> + for (i = old_nr; i < new_nr; i++) {
> + struct swap_cluster_info *ci = &si->cluster_info[i];
> + ci->flags = CLUSTER_FLAG_FREE;
> + spin_lock(&si->lock);
> + list_add_tail(&ci->list, &si->free_clusters);
> + spin_unlock(&si->lock);
> + }
> +
> + si->pages += new - old_max;
> + atomic_long_add(new - old_max, &nr_swap_pages);
> + spin_lock(&swap_lock);
> + total_swap_pages += new - old_max;
> + spin_unlock(&swap_lock);
> + return 0;
> +}
> #endif /* CONFIG_ZSWAP */
Does this only grow? Say I have a huge but temporary spike in swap
usage, which triggers the extension of the the ghost swapfile address
space. Do these metadata space live forever?
If that's the case, might as well just swapon...?
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-07 22:37 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-07 8:26 [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 8:26 ` [RFC PATCH 01/10] mm: ghost swapfile support for zswap Baoquan He
2026-07-07 8:26 ` [RFC PATCH 02/10] mm/swap_table: add Redirect entry encoding for ghost swap backend switching Baoquan He
2026-07-07 8:26 ` [RFC PATCH 03/10] mm/swap: add redirect_xa field and ghost redirect helper declarations Baoquan He
2026-07-07 8:26 ` [RFC PATCH 04/10] mm/swapfile: implement ghost redirect helpers and free-path cascade Baoquan He
2026-07-07 8:26 ` [RFC PATCH 05/10] mm/swap_state: restore Redirect entry when swap cache folio is removed Baoquan He
2026-07-07 8:26 ` [RFC PATCH 06/10] mm/zswap: implement ghost-to-physical writeback for backend switching Baoquan He
2026-07-07 8:26 ` [RFC PATCH 07/10] mm/page_io: forward ghost swap reads to physical device via Redirect Baoquan He
2026-07-07 8:26 ` [RFC PATCH 08/10] mm/swapfile: manage ghost cluster_info via lazy vmalloc Baoquan He
2026-07-07 8:26 ` [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Baoquan He
2026-07-07 22:36 ` Nhat Pham
2026-07-07 8:26 ` [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension Baoquan He
2026-07-07 8:56 ` [RFC PATCH 00/10] mm/swap: ghost swapfile with backend switching via Redirect entries Baoquan He
2026-07-07 21:23 ` Nhat Pham
2026-07-07 21:25 ` Nhat Pham
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox