From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-178.mta0.migadu.com (out-178.mta0.migadu.com [91.218.175.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 42AD53E5A2B for ; Wed, 5 Aug 2026 07:55:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.178 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785916523; cv=none; b=p34vaRVK4F/aqWvpAuVX92Ku/bXWm/vnZKsjLyTsO/5J/y5XSvJWmF0p+NVctGxZaD+tCGWiLdKu55jq2v+NHLfM6Sf+buDzKjpLfP33jUYTyv0/OWrGHt5EMddrlYFA46QBEV6QSCrbIP6JpRj5P2EBp0erP+thBUrwNiqyPVU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785916523; c=relaxed/simple; bh=8vxDYPJT7zxVP55sojXBKnEYKhnuZozdf0JkOyXR3EY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type:Content-type; b=jKx8Me5mhpszgAZc3kYZ9wdBNck04o/1wwYgbT8EqNp2FAGZW4Np5b6wwmKnjgvyudarEbEyOsoIlfFgEEtHDI3JmdPVTojCAXTiJn85N6lR2KnSChwrLd+buObT6/bzQ9VcixssSmnnAUVUHAFF8fhLkrklz3/6EI2BGZJeFVY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=PlSv69tq; arc=none smtp.client-ip=91.218.175.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="PlSv69tq" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1785916519; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-type:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=U88ICcPSN2njwbDn4B5fxEExcCgx6VDyaCcBrToBcqI=; b=PlSv69tqP76D2gH+DzpHn9NJAtmrWNmjrsjXO2gTTvQO8sSuDIk1Uv3fYkWE4mxeIfCDix ecWzoHmDJlrdX+zGS+47GIEeOxLMRuEWuV6UrA0qYE/sezgXnuhjRSlZ142pLut59SZ9+u EkiXDQpBJeKa0PfL3qo/aaNaCzKbiHI= From: Baoquan He 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 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 Message-ID: <20260805075336.3579395-9-baoquan.he@linux.dev> In-Reply-To: <20260805075336.3579395-1-baoquan.he@linux.dev> References: <20260805075336.3579395-1-baoquan.he@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-type: text/plain Content-Transfer-Encoding: 8bit X-Migadu-Flow: FLOW_OUT 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 --- 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