Re: [PATCH v3 14/14] mm, swap: shrink xswap to the ceiling when it drops

From: Chris Li

Date: Mon Sep 28 2026 - 02:19:27 EST


On Wed, Sep 16, 2026 at 12:21 AM Baoquan He <hebaoquan@xxxxxxxxxx> wrote:
>
> 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.

Again, I suspect we can move the shrink into the later phase if needed
so I have less code to review.

Chris

>
> 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
>