[PATCH v3 14/14] mm, swap: shrink xswap to the ceiling when it drops
From: Baoquan He
Date: Wed Sep 16 2026 - 06:54:07 EST
Make xswap_try_shrink() shrink down to the ceiling when the ceiling is
lowered below the mapped range, and trigger the shrink directly from a
limit write instead of waiting for the next free cluster.
Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
mm/swapfile.c | 59 ++++++++++++++++++++++++++++++++-------------------
1 file changed, 37 insertions(+), 22 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 0fcbaf1cf0e2..ab64bfc4e0f7 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -4190,8 +4190,9 @@ static void xswap_shrink_work_fn(struct work_struct *work)
*/
static void xswap_try_shrink(struct swap_info_struct *si)
{
+ unsigned long nr_mapped, nr_ceiling, nr_tail, nr_unmap;
+ unsigned long start_idx, i;
struct swap_cluster_info *ci;
- unsigned long nr_mapped, nr_tail, nr_unmap, start_idx, i;
if (!(si->flags & SWP_XSWAP))
return;
@@ -4203,35 +4204,45 @@ static void xswap_try_shrink(struct swap_info_struct *si)
goto out_unlock;
nr_mapped = READ_ONCE(si->nr_clusters_mapped);
+ nr_ceiling = READ_ONCE(si->nr_clusters);
+
if (nr_mapped <= 1) /* keep cluster 0 */
goto out_unlock;
/*
- * Reclaim on our own, but only once the mapped range is at most
- * half in use: growth is demand driven, so reclaiming on a smaller
- * dip would only map the same clusters again, and every unmap costs
- * an RCU grace period.
+ * A cap below the mapped range shrinks on its own. Otherwise wait
+ * until the range is at most half in use: growth follows demand, so
+ * shrinking above that would only map the same clusters again.
*/
- if (swap_usage_in_pages(si) * 2 > nr_mapped * SWAPFILE_CLUSTER)
+ if (nr_ceiling >= nr_mapped &&
+ swap_usage_in_pages(si) * 2 > nr_mapped * SWAPFILE_CLUSTER)
goto out_unlock;
- /*
- * Count the free clusters at the tail of the mapped range. Scanned,
- * not tracked: the count must be exact to size the unmap, and an
- * incremental count falls behind on out-of-order frees.
- */
- nr_tail = 0;
- while (nr_mapped - nr_tail > 1) {
- ci = &si->cluster_info[nr_mapped - nr_tail - 1];
- if (READ_ONCE(ci->count) ||
- READ_ONCE(ci->flags) != CLUSTER_FLAG_FREE)
- break;
- nr_tail++;
- }
- if (nr_tail < XSWAP_SHRINK_SLACK + XSWAP_SHRINK_MIN)
- goto out_unlock;
+ if (nr_ceiling < nr_mapped) {
+ /* Take the excess exactly; rounding could zero a small cap. */
+ nr_unmap = nr_mapped - nr_ceiling;
+ /*
+ * Keep cluster 0: it always holds the header slot, so it is
+ * never free and must not be unmapped.
+ */
+ nr_unmap = min(nr_unmap, nr_mapped - 1);
+ } else {
+ /* Count the free tail; scanned, not tracked. */
+ nr_tail = 0;
+ while (nr_mapped - nr_tail > 1) {
+ ci = &si->cluster_info[nr_mapped - nr_tail - 1];
+ if (READ_ONCE(ci->count) ||
+ READ_ONCE(ci->flags) != CLUSTER_FLAG_FREE)
+ break;
+ nr_tail++;
+ }
- nr_unmap = rounddown(nr_tail - XSWAP_SHRINK_SLACK, XSWAP_GROW_CLUSTERS);
+ if (nr_tail < XSWAP_SHRINK_SLACK + XSWAP_SHRINK_MIN)
+ goto out_unlock;
+
+ nr_unmap = rounddown(nr_tail - XSWAP_SHRINK_SLACK,
+ XSWAP_GROW_CLUSTERS);
+ }
if (!nr_unmap)
goto out_unlock;
start_idx = nr_mapped - nr_unmap;
@@ -4507,6 +4518,10 @@ static ssize_t xswap_limit_store(struct kobject *kobj,
spin_unlock(&swap_lock);
+ /* Enforce a lowered ceiling at once; raising needs no shrink. */
+ if (clusters < READ_ONCE(si->nr_clusters_mapped))
+ xswap_try_shrink(si);
+
return count;
}
--
2.54.0