[PATCH v3 06/14] mm, swap: add xswap grow trigger on cluster allocation
From: Baoquan He
Date: Wed Sep 16 2026 - 06:25:36 EST
When cluster_alloc_swap_entry() fails to find a free cluster and
the xswap device still has room to grow, expand the mapped range
by XSWAP_GROW_CLUSTERS clusters.
Since xswap is always SWP_SOLIDSTATE, global_cluster_lock is never
held on this path.
This makes the xswap cluster space grow transparently as swap usage
increases, without any userspace intervention.
Growing maps pages into the VM_SPARSE area, which can sleep. The caller
holds local_lock(&percpu_swap_cluster.lock) across the whole slow path,
so drop it around xswap_map_clusters() and take it again afterwards; it
only protects the per-cpu cluster cache, which this path does not touch.
Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
mm/swapfile.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index b0edf5421fc5..17e482059d32 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1273,6 +1273,47 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si,
if (found)
goto done;
}
+
+#ifdef CONFIG_XSWAP
+ /* For xswap: grow the cluster_info array, then retry. */
+ if (!found && (si->flags & SWP_XSWAP) &&
+ READ_ONCE(si->nr_clusters_mapped) < READ_ONCE(si->nr_clusters_max) &&
+ list_empty(&si->free_clusters)) {
+ unsigned long nr_new = min(READ_ONCE(si->nr_clusters_max) -
+ READ_ONCE(si->nr_clusters_mapped),
+ XSWAP_GROW_CLUSTERS);
+ unsigned long start = READ_ONCE(si->nr_clusters_mapped);
+ unsigned long i;
+ int ret;
+
+ /*
+ * Mapping pages can sleep. The lock only guards the per-cpu
+ * cluster cache, which this path does not touch.
+ */
+ local_unlock(&percpu_swap_cluster.lock);
+ ret = xswap_map_clusters(si, start, nr_new);
+ local_lock(&percpu_swap_cluster.lock);
+
+ if (!ret) {
+ for (i = start; i < start + nr_new; i++) {
+ struct swap_cluster_info *ci = &si->cluster_info[i];
+
+ /*
+ * A concurrent grower may have taken these already;
+ * only add the off-list ones.
+ */
+ spin_lock(&ci->lock);
+ if (ci->flags == CLUSTER_FLAG_NONE)
+ move_cluster(si, ci, &si->free_clusters,
+ CLUSTER_FLAG_FREE);
+ spin_unlock(&ci->lock);
+ }
+
+ found = alloc_swap_scan_list(si, &si->free_clusters,
+ folio, false);
+ }
+ }
+#endif
done:
if (!(si->flags & SWP_SOLIDSTATE))
spin_unlock(&si->global_cluster_lock);
--
2.54.0