From: Baoquan He <baoquan.he@linux.dev>
To: linux-mm@kvack.org
Cc: chrisl@kernel.org, nphamcs@gmail.com, kasong@tencent.com,
baohua@kernel.org, youngjun.park@lge.com, hannes@cmpxchg.org,
yosry@kernel.org, david@kernel.org, shikemeng@huaweicloud.com,
chengming.zhou@linux.dev, linux-kernel@vger.kernel.org,
Baoquan He <baoquan.he@linux.dev>
Subject: [RFC PATCH v2 08/10] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap
Date: Wed, 5 Aug 2026 15:53:31 +0800 [thread overview]
Message-ID: <20260805075336.3579395-9-baoquan.he@linux.dev> (raw)
In-Reply-To: <20260805075336.3579395-1-baoquan.he@linux.dev>
Split the xswap cluster limit into two fields:
- nr_clusters_max: immutable hard limit set at swapon from swap header
- nr_clusters: current growth ceiling, adjustable at runtime (≤ nr_clusters_max)
The grow path already uses nr_clusters as the ceiling. Shrink now also
respects it: when nr_clusters drops below nr_clusters_mapped, shrinking
fires on free until the mapped count reaches the ceiling. When
nr_clusters == nr_clusters_max (default), shrink is effectively
disabled — all growth and no shrink.
At swapon, nr_clusters starts at nr_clusters_max (full size).
Signed-off-by: Baoquan He <baoquan.he@linux.dev>
---
include/linux/swap.h | 3 ++-
mm/swapfile.c | 31 ++++++++++++++++++++++---------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 72b28116ed0f..1159153459a1 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -250,7 +250,8 @@ struct swap_info_struct {
struct swap_cluster_info *cluster_info; /* cluster info. Only for SSD */
#ifdef CONFIG_XSWAP
struct vm_struct *cluster_vm; /* VM_SPARSE area for xswap dynamic cluster_info */
- unsigned long nr_clusters; /* total cluster count for xswap */
+ unsigned long nr_clusters_max;/* upper limit from swap header */
+ unsigned long nr_clusters; /* current growth ceiling (≤ nr_clusters_max) */
unsigned long nr_clusters_mapped; /* currently mapped cluster count */
unsigned long nr_free_tail; /* contiguous free clusters at tail */
struct mutex xswap_lock; /* serialize map/unmap operations */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 3f536495b8cf..3037f428f217 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -72,6 +72,7 @@ static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx)
static void xswap_update_free_tail(struct swap_info_struct *si,
unsigned long freed_idx);
static void xswap_try_shrink(struct swap_info_struct *si);
+
#endif
static void swap_range_alloc(struct swap_info_struct *si,
@@ -3151,6 +3152,7 @@ static void free_swap_cluster_info(struct swap_info_struct *si)
xswap_unmap_clusters(si, 0, si->nr_clusters_mapped);
free_vm_area(si->cluster_vm);
si->cluster_vm = NULL;
+ si->nr_clusters_max = 0;
si->nr_clusters = 0;
si->nr_clusters_mapped = 0;
return;
@@ -3972,20 +3974,31 @@ static void xswap_trim_free_tail(struct swap_info_struct *si, unsigned long idx)
*/
static void xswap_try_shrink(struct swap_info_struct *si)
{
- unsigned long start_idx, nr_unmap, i;
+ unsigned long nr_mapped, nr_ceiling, nr_tail, nr_unmap;
+ unsigned long start_idx, i;
struct swap_cluster_info *ci;
if (!(si->flags & SWP_XSWAP))
return;
- if (si->nr_free_tail < XSWAP_GROW_CLUSTERS)
+
+ nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_ceiling = READ_ONCE(si->nr_clusters);
+ nr_tail = READ_ONCE(si->nr_free_tail);
+
+ if (nr_mapped <= nr_ceiling)
+ return;
+ if (nr_tail < XSWAP_GROW_CLUSTERS)
return;
- nr_unmap = round_down(si->nr_free_tail, XSWAP_GROW_CLUSTERS);
- start_idx = si->nr_clusters_mapped - nr_unmap;
+ nr_unmap = min(round_down(nr_tail, XSWAP_GROW_CLUSTERS),
+ nr_mapped - nr_ceiling);
+ if (nr_unmap < XSWAP_GROW_CLUSTERS)
+ return;
+ start_idx = nr_mapped - nr_unmap;
/* Verify the tail clusters are still free before unmapping */
spin_lock(&si->lock);
- for (i = start_idx; i < si->nr_clusters_mapped; i++) {
+ for (i = start_idx; i < nr_mapped; i++) {
ci = &si->cluster_info[i];
if (ci->flags != CLUSTER_FLAG_FREE) {
nr_unmap = i - start_idx;
@@ -4000,7 +4013,7 @@ static void xswap_try_shrink(struct swap_info_struct *si)
return;
xswap_unmap_clusters(si, start_idx, nr_unmap);
- si->nr_free_tail -= nr_unmap;
+ WRITE_ONCE(si->nr_free_tail, nr_tail - nr_unmap);
}
#endif /* CONFIG_XSWAP */
@@ -4024,6 +4037,7 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
cluster_info = vm->addr;
si->cluster_vm = vm;
+ si->nr_clusters_max = nr_clusters;
si->nr_clusters = nr_clusters;
si->cluster_info = cluster_info;
@@ -4058,6 +4072,8 @@ static int setup_swap_clusters_info(struct swap_info_struct *si,
}
}
+ /* All mapped clusters except cluster 0 are free at the tail */
+ si->nr_free_tail = si->nr_clusters_mapped - 1;
mutex_init(&si->xswap_lock);
return 0;
@@ -4469,7 +4485,6 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
static int __init swapfile_init(void)
{
swapfile_maximum_size = arch_max_swapfile_size();
-
/*
* Once a cluster is freed, it's swap table content is read
* only, and all swap cache readers (swap_cache_*) verifies
@@ -4479,12 +4494,10 @@ static int __init swapfile_init(void)
swap_table_cachep = kmem_cache_create("swap_table",
sizeof(struct swap_table),
0, SLAB_PANIC | SLAB_TYPESAFE_BY_RCU, NULL);
-
#ifdef CONFIG_MIGRATION
if (swapfile_maximum_size >= (1UL << SWP_MIG_TOTAL_BITS))
swap_migration_ad_supported = true;
#endif /* CONFIG_MIGRATION */
-
return 0;
}
subsys_initcall(swapfile_init);
--
2.54.0
next prev parent reply other threads:[~2026-08-05 7:55 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 7:53 [RFC PATCH v2 00/10] mm, swap: dynamic cluster management for xswap devices Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 01/10] mm: xswap support for zswap Baoquan He
2026-08-05 14:17 ` Johannes Weiner
2026-08-07 9:11 ` Baoquan He
2026-08-10 1:49 ` Youngjun Park
2026-08-10 7:25 ` Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 02/10] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 03/10] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 04/10] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 05/10] mm, swap: add xswap_try_shrink and shrink trigger on cluster free Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 06/10] mm, swap: free backing pages in xswap_unmap_clusters Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 07/10] mm, swap: add nr_free_tail for O(1) xswap shrink detection Baoquan He
2026-08-05 7:53 ` Baoquan He [this message]
2026-08-05 7:53 ` [RFC PATCH v2 09/10] mm, swap: add debugfs knob for xswap per-device cluster limit Baoquan He
2026-08-05 7:53 ` [RFC PATCH v2 10/10] mm, swap: defer xswap shrink to workqueue to avoid lock recursion 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=20260805075336.3579395-9-baoquan.he@linux.dev \
--to=baoquan.he@linux.dev \
--cc=baohua@kernel.org \
--cc=chengming.zhou@linux.dev \
--cc=chrisl@kernel.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kasong@tencent.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=shikemeng@huaweicloud.com \
--cc=yosry@kernel.org \
--cc=youngjun.park@lge.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.