From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-189.mta1.migadu.com (out-189.mta1.migadu.com [95.215.58.189]) (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 48A143264FD for ; Tue, 7 Jul 2026 08:27:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.189 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412867; cv=none; b=Hn2EvtPNZlJOEMyoHf732T5yFmU4PfkuOyUJcvEeQ8CZcdpwTxOIQLLsG9Q8anI4UUfDqeTHrVmLk4feSYrDrZOJdOxeLR/TezuBaV/uWRNmGHSlz3MNVXZzPheSaZLxex6Pyhal/juJYdEsDc4tRAahnyBZmF+2mzz8Sv6SwJM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412867; c=relaxed/simple; bh=1rB2aiexI1yoHrbQ/+qPdfItdHaHC26gQz5rozcD6pc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type:Content-type; b=oLMg2npGHcliQEihxcZ+ZvIeQ0jw2/SqnmSkaM398M1fZcwc5kXOoFs755Z2EwA96P+3KPMWJNFTRt3g2zMiWZds4aebjVkBqPeCkRP+eUEQ1SM1NgzxJo+rCPeh7vGvIrgdIfJ7A6Y3jPvkk+WseKAlhqwHCPC95vbnBDSMRsk= 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=R/5DCUXv; arc=none smtp.client-ip=95.215.58.189 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="R/5DCUXv" 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=1783412863; 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=yihnCEZ8cVWBNOzVl1JoQsUGqfa5cZb/NMsbpAPjbu4=; b=R/5DCUXvrIXSqgwmx1kRAI7I80LsohS9htiVnFyv3HzIxXkRNskpMmlWBJKmwwhxHmW0qV hpRmq8hUHxz0V0MeIi0fwNoalxmtVT4FOueDChy82KJbX4N0UxijVY6Do2vDWwtFsyyknT FFiGCDGQTqrGUbyEc8PpFZ9MaaH/KcU= From: Baoquan He To: linux-mm@kvack.org Cc: akpm@linux-foundation.org, chrisl@kernel.org, kasong@tencent.com, shikemeng@huaweicloud.com, nphamcs@gmail.com, baohua@kernel.org, youngjun.park@lge.com, hannes@cmpxchg.org, yosry@kernel.org, chengming.zhou@linux.dev, linux-kernel@vger.kernel.org, Baoquan He Subject: [RFC PATCH 09/10] mm/swapfile: implement swap_ghost_extend_max() for dynamic growth Date: Tue, 7 Jul 2026 16:26:11 +0800 Message-ID: <20260707082614.95030-10-baoquan.he@linux.dev> In-Reply-To: <20260707082614.95030-1-baoquan.he@linux.dev> References: <20260707082614.95030-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 Allocate additional physical pages and map them at the end of the already-reserved sparse vmalloc area via vm_area_map_pages(). The flat cluster_info pointer never moves — new clusters appear contiguously at higher indices. New clusters are fully initialized (spinlocks, list heads, trailing BAD mask) before si->max is published via WRITE_ONCE. Page-pointer tracking in cluster_vm->pages is reallocated to accommodate additional entries. Returns -E2BIG if the requested size exceeds the reserved VA range. Extension is serialized by swapon_mutex. Signed-off-by: Baoquan He --- mm/swapfile.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/mm/swapfile.c b/mm/swapfile.c index da085a75d7b5..8bf336678887 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -58,6 +58,8 @@ static void move_cluster(struct swap_info_struct *si, enum swap_cluster_flags new_flags); #ifdef CONFIG_ZSWAP #define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024) +static int swap_ghost_extend_max(struct swap_info_struct *si, + unsigned int new_maxpages); #endif /* @@ -4247,6 +4249,114 @@ swp_entry_t ghost_redirect_to_phys(struct swap_info_struct *si, pgoff_t offset) return (swp_entry_t){}; } + +/* + * swap_ghost_extend_max — Extend an active ghost swap device. + * @si: ghost device (SWP_GHOST set) + * @new: new max pages (> si->max) + * + * Allocates physical pages and maps them at the end of the reserved + * sparse vmalloc area. si->cluster_info never moves. New clusters + * are fully initialized before si->max is published via WRITE_ONCE. + * + * Returns 0, -ENOMEM, -EINVAL, or -E2BIG. Caller holds swapon_mutex. + */ +static int swap_ghost_extend_max(struct swap_info_struct *si, + unsigned int new) +{ + unsigned long old_max = si->max, old_nr, new_nr; + unsigned long old_bytes, new_bytes, nr_new_pg; + struct page **new_pglist; + unsigned long i; + + if (!(si->flags & SWP_GHOST) || + !(si->flags & (SWP_USED | SWP_WRITEOK)) || + new <= si->max) + return -EINVAL; + + old_nr = DIV_ROUND_UP(si->max, SWAPFILE_CLUSTER); + new_nr = DIV_ROUND_UP(new, SWAPFILE_CLUSTER); + old_bytes = ALIGN(old_nr * sizeof(struct swap_cluster_info), PAGE_SIZE); + new_bytes = ALIGN(new_nr * sizeof(struct swap_cluster_info), PAGE_SIZE); + nr_new_pg = (new_bytes - old_bytes) / PAGE_SIZE; + + if (old_bytes + nr_new_pg * PAGE_SIZE > si->cluster_vm->size) { + pr_warn("ghost: extend to %u exceeds reserved VA (%lu bytes)\n", + new, si->cluster_vm->size); + return -E2BIG; + } + + new_pglist = kvmalloc_array(si->cluster_vm->nr_pages + nr_new_pg, + sizeof(struct page *), GFP_KERNEL); + if (!new_pglist) + return -ENOMEM; + if (si->cluster_vm->pages) { + memcpy(new_pglist, si->cluster_vm->pages, + si->cluster_vm->nr_pages * sizeof(struct page *)); + kvfree(si->cluster_vm->pages); + } + si->cluster_vm->pages = (void *)new_pglist; + + for (i = 0; i < nr_new_pg; i++) { + struct page *page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) { + while (i-- > 0) + __free_page(new_pglist[si->cluster_vm->nr_pages + i]); + return -ENOMEM; + } + new_pglist[si->cluster_vm->nr_pages + i] = page; + } + + if (vm_area_map_pages(si->cluster_vm, + (unsigned long)si->cluster_vm->addr + old_bytes, + (unsigned long)si->cluster_vm->addr + new_bytes, + &new_pglist[si->cluster_vm->nr_pages])) { + for (i = 0; i < nr_new_pg; i++) + __free_page(new_pglist[si->cluster_vm->nr_pages + i]); + return -ENOMEM; + } + si->cluster_vm->nr_pages += nr_new_pg; + + for (i = old_nr; i < new_nr; i++) { + struct swap_cluster_info *ci = &si->cluster_info[i]; + spin_lock_init(&ci->lock); + INIT_LIST_HEAD(&ci->list); + } + + if (new % SWAPFILE_CLUSTER) { + unsigned long base = (new - 1) / SWAPFILE_CLUSTER * + SWAPFILE_CLUSTER; + unsigned long idx = base / SWAPFILE_CLUSTER; + struct swap_cluster_info *ci = &si->cluster_info[idx]; + + if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL)) { + for (i = 0; i < nr_new_pg; i++) + __free_page(new_pglist[si->cluster_vm->nr_pages + i]); + return -ENOMEM; + } + spin_lock(&ci->lock); + for (i = new % SWAPFILE_CLUSTER; i < SWAPFILE_CLUSTER; i++) + __swap_table_set(ci, i, SWP_TB_BAD); + spin_unlock(&ci->lock); + } + + WRITE_ONCE(si->max, new); + + for (i = old_nr; i < new_nr; i++) { + struct swap_cluster_info *ci = &si->cluster_info[i]; + ci->flags = CLUSTER_FLAG_FREE; + spin_lock(&si->lock); + list_add_tail(&ci->list, &si->free_clusters); + spin_unlock(&si->lock); + } + + si->pages += new - old_max; + atomic_long_add(new - old_max, &nr_swap_pages); + spin_lock(&swap_lock); + total_swap_pages += new - old_max; + spin_unlock(&swap_lock); + return 0; +} #endif /* CONFIG_ZSWAP */ static int __init swapfile_init(void) -- 2.54.0