From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 C076B56E04A; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088155; cv=none; b=Kf3vBSqIsMf6gMpGlYrDDCev/gQBdR+3ncH95mjDiFNaa8yGPFYuw++2PwSN2U2cImMk24Z7MOcZLgplkSjwwG/AdUhiEXwh4R2CUozXOIJByxSVEoofh0QnCYKMJIBwF8D4SSnr6PHh7myoNgmtk5laPdmERsmFriXWp0z0X1s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088155; c=relaxed/simple; bh=s1/G/exW7I9C8J1Zs0aDe5W6D8qQyUMskv8ZL4vemXA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aUTX3Y4MKcgB4WiM6SUjg/ekg1PrbSBDiMc0Z9o8EF5hq+wRNBXOoClI02Gumotw348lSIa/jA2n3m9W8P09kFoMuuaaKcThRskQj9D+hVeW9f0630r/pTvl2EYVCBE3D+yadrFaUltAI2P7RJPkdzkOi0elvW7KfgnJlRc7mL0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RPK2LnRz; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="RPK2LnRz" Received: by smtp.kernel.org (Postfix) with ESMTPS id 33C02C2BCF4; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=s1/G/exW7I9C8J1Zs0aDe5W6D8qQyUMskv8ZL4vemXA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=RPK2LnRzKNHqSDcsJs62+H7Q0GFWNOsx4JRA5AmCvcjFb9mCN1tkcbwxPRFKroi9p KGY8XuB6hWGj5c9Xxt9uynobCYDCrZwJikOK70ttOznJ8vQXEmVYQunMSBcJP43lQA F25p4YOqjIq1XnDiPGkfwSUeT74ET2WMa9QyClHBhoz+XkiVCynG2YdnncuugWxtSC 5RX0iJtwOKgRsF8YasB+pLKYj+pQ74Gdgs34vE7TKeJ+PmSiQiisIYIvi72pM/4JH1 /trL0f7DgVUYFNxKvAZ3rSYWh1M279PLNYXXyPmwJSYWB7dD1jrxwjFmf7xfmuCrJE qnrHxXwpLaiZw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 106D4CC6B38; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:50 +0800 Subject: [PATCH v4 01/17] mm/memcontrol: allow update of LRU statistic without holding LRU lock 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-1-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=1134; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=WaY0YQ32toU3iWe0KThIrrS8OVU12NSfLZ8Lil6VesU=; b=V+guvWhaQbqmzqqbc8zI+LRwI/lXcAEz81sU0OsErjMX8doqUKgRBPVmBCb7/aGiM2I6H04mV ADgUv8gHB7QBgzvvNxLatOtSNL3y1wTHQ7nwuvaL7DBiAQGloBrYEdi X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song To enable moving file pages in folio_mark_accessed directly and lazily for MGLRU, allow updating the LRU statistic atomically without holding a lock. It may cause temporary counter underflow, which should be fine as we still follow final consistency of the counter, and it only serves as a estimated metrics, a little inaccuracy has no visible effect. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- include/linux/mm_inline.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index ab69b9930893..597f013c8e04 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -47,11 +47,10 @@ static __always_inline void __update_lru_size(struct lr= uvec *lruvec, { struct pglist_data *pgdat =3D lruvec_pgdat(lruvec); =20 - lockdep_assert_held(&lruvec->lru_lock); WARN_ON_ONCE(nr_pages !=3D (int)nr_pages); =20 mod_lruvec_state(lruvec, NR_LRU_BASE + lru, nr_pages); - __mod_zone_page_state(&pgdat->node_zones[zid], + mod_zone_page_state(&pgdat->node_zones[zid], NR_ZONE_LRU_BASE + lru, nr_pages); } =20 --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 00D8656E061; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=GKsDh8Lq1nQxWxWLaF4QFGOjCPm5YDuuer1C2EDZs7csfwAGubQaKYmu3tpunG8PzIi/wWNWaQucDra30d3141KEluI3sU405SX83uSxthipr8NXit83k02rC3FC2SU+pkoI4fsYxGjuTzC6etzDPHkS/S1YdEqUJNZrxiPo7os= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=AM2uStnUk0NEZ/O+itnfOOVfuh8FaYObL72VxiLWM2g=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=XFHzIIbpYtTOf3hqzGxlszxwKF8xJ1b8ofLKqk+Q3HXa1Y7UxD45YbeIlityjV2BklTrRMiuUXZBt4WzW8RD3X57SdhVkk6xBUkIVbhy5yClFeOV/4uVm1HVl/2bAOeYr9z5x5kPv8M6djsKk0Xd8Rx4RS1cJJu5U2cEmr0FTa8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=cqJ2FaDl; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="cqJ2FaDl" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4390BC2BCF5; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=AM2uStnUk0NEZ/O+itnfOOVfuh8FaYObL72VxiLWM2g=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=cqJ2FaDlOWJdmIDBul6wap+f+Iugq1YeiaMmM4fm0iXIZ5PTWKKSILnUkR3DLSeJ+ d6y3G4cDyxfg4q12eTzdJHyX31ysfc4HQL5UMKnI6WsTeGrhVGq6Lau8NWsJsno2bW mCvGZ3glHK+7IDPoCmLWxgZX624LAtzzL3z6grbeYogBR4FirMcmwGSkZZ+dJ2zoe1 wx7bxALT9ppqmNzcuOE07Pd7OAaDDDDNzJW3XtanlrYUvFNOj70U6cnALQf0d1CMIA bM4jHXw4WwnVfbBlle86zAmXCXEA/YYOQZeEvQ6luQkEL1e2zX1nuXiWlEYA4dOnTh JUC5SUhTwbF0g== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 227EACCA466; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:51 +0800 Subject: [PATCH v4 02/17] mm/mglru: make generation page counters atomic 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-2-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=5705; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=0WiOWnqNr4HX6xukU5Z7iG/633RjVbvVUBflEj8zKv0=; b=GzIQnMCGGFBJ8rRg+psg68PBeye+76F28QDmanvToZ/WFpaDmG5dUrzTfJvb8a4MA/FisZGJH oLRudWip/H5CxKK6A0GNSogsnT6xzOKiGpswm4d6g0s+I1LjILlAP9T X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song No feature change, convert them to atomic so we can update them without holding the LRU lock. There is no risk of overflow. The reader always compares and uses zero instead if the counter values are negative. It follows final consistency. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- include/linux/mm_inline.h | 6 ++---- include/linux/mmzone.h | 2 +- mm/vmscan.c | 27 ++++++++++++--------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index 597f013c8e04..f52f02e8e5be 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -265,11 +265,9 @@ static inline void lru_gen_update_size(struct lruvec *= lruvec, struct folio *foli VM_WARN_ON_ONCE(old_gen =3D=3D -1 && new_gen =3D=3D -1); =20 if (old_gen >=3D 0) - WRITE_ONCE(lrugen->nr_pages[old_gen][type][zone], - lrugen->nr_pages[old_gen][type][zone] - delta); + atomic_long_sub(delta, &lrugen->nr_pages[old_gen][type][zone]); if (new_gen >=3D 0) - WRITE_ONCE(lrugen->nr_pages[new_gen][type][zone], - lrugen->nr_pages[new_gen][type][zone] + delta); + atomic_long_add(delta, &lrugen->nr_pages[new_gen][type][zone]); =20 /* addition */ if (old_gen < 0) { diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 3b96d6c7123b..09ce82fa841a 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -554,7 +554,7 @@ struct lru_gen_folio { /* the multi-gen LRU lists, lazily sorted on eviction */ struct list_head folios[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES]; /* the multi-gen LRU sizes, eventually consistent */ - long nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES]; + atomic_long_t nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES]; /* the exponential moving average of refaulted */ unsigned long avg_refaulted[ANON_AND_FILE][MAX_NR_TIERS]; /* the exponential moving average of evicted+protected */ diff --git a/mm/vmscan.c b/mm/vmscan.c index daa7e37c2236..cab3ba14d20c 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -3495,8 +3495,7 @@ static void reset_batch_size(struct lru_gen_mm_walk *= walk) continue; =20 walk->nr_pages[gen][type][zone] =3D 0; - WRITE_ONCE(lrugen->nr_pages[gen][type][zone], - lrugen->nr_pages[gen][type][zone] + delta); + atomic_long_add(delta, &lrugen->nr_pages[gen][type][zone]); =20 if (lru_gen_is_active(lruvec, gen)) lru +=3D LRU_ACTIVE; @@ -4116,11 +4115,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int t= ype, int swappiness) break; } flush_lru_batch(head, &batch_end, target_list); - - WRITE_ONCE(lrugen->nr_pages[old_gen][type][zone], - lrugen->nr_pages[old_gen][type][zone] - delta); - WRITE_ONCE(lrugen->nr_pages[target_gen][type][zone], - lrugen->nr_pages[target_gen][type][zone] + delta); + atomic_long_sub(delta, &lrugen->nr_pages[old_gen][type][zone]); + atomic_long_add(delta, &lrugen->nr_pages[target_gen][type][zone]); if (!remaining) return false; } @@ -4225,8 +4221,8 @@ static bool inc_max_seq(struct lruvec *lruvec, unsign= ed long seq, int swappiness for (type =3D 0; type < ANON_AND_FILE; type++) { for (zone =3D 0; zone < MAX_NR_ZONES; zone++) { enum lru_list lru =3D type * LRU_INACTIVE_FILE; - long delta =3D lrugen->nr_pages[prev][type][zone] - - lrugen->nr_pages[next][type][zone]; + long delta =3D atomic_long_read(&lrugen->nr_pages[prev][type][zone]) - + atomic_long_read(&lrugen->nr_pages[next][type][zone]); =20 if (!delta) continue; @@ -4344,7 +4340,8 @@ static unsigned long lruvec_evictable_size(struct lru= vec *lruvec, int swappiness for (seq =3D min_seq[type]; seq <=3D max_seq; seq++) { gen =3D lru_gen_from_seq(seq); for (zone =3D 0; zone < MAX_NR_ZONES; zone++) - total +=3D max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L); + total +=3D max(atomic_long_read(&lrugen->nr_pages[gen][type][zone]), + 0L); } } =20 @@ -4764,7 +4761,7 @@ static void __lru_gen_reparent_memcg(struct lruvec *c= hild_lruvec, struct lruvec =20 for (i =3D 0; i < get_nr_gens(child_lruvec, type); i++) { int gen =3D lru_gen_from_seq(child_lrugen->max_seq - i); - long nr_pages =3D child_lrugen->nr_pages[gen][type][zone]; + long nr_pages =3D atomic_long_read(&child_lrugen->nr_pages[gen][type][zo= ne]); int child_lru_active =3D lru_gen_is_active(child_lruvec, gen) ? LRU_ACTI= VE : 0; int parent_lru_active =3D lru_gen_is_active(parent_lruvec, gen) ? LRU_AC= TIVE : 0; =20 @@ -4772,9 +4769,8 @@ static void __lru_gen_reparent_memcg(struct lruvec *c= hild_lruvec, struct lruvec list_splice_tail_init(&child_lrugen->folios[gen][type][zone], &parent_lrugen->folios[gen][type][zone]); =20 - WRITE_ONCE(child_lrugen->nr_pages[gen][type][zone], 0); - WRITE_ONCE(parent_lrugen->nr_pages[gen][type][zone], - parent_lrugen->nr_pages[gen][type][zone] + nr_pages); + atomic_long_set(&child_lrugen->nr_pages[gen][type][zone], 0); + atomic_long_add(nr_pages, &parent_lrugen->nr_pages[gen][type][zone]); =20 if (lru_gen_is_active(child_lruvec, gen) !=3D lru_gen_is_active(parent_l= ruvec, gen)) { __update_lru_size(child_lruvec, lru + child_lru_active, zone, -nr_pages= ); @@ -5859,7 +5855,8 @@ static int lru_gen_seq_show(struct seq_file *m, void = *v) char mark =3D full && seq < min_seq[type] ? 'x' : ' '; =20 for (zone =3D 0; zone < MAX_NR_ZONES; zone++) - size +=3D max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L); + size +=3D max(atomic_long_read(&lrugen->nr_pages[gen][type][zone]), + 0L); =20 seq_printf(m, " %10lu%c", size, mark); } --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 D593A541E4E; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088155; cv=none; b=VU7pNkxXcHhjpVqf2ra3rzYhOPsSQv+D43OpoKlZhOnp87lFRsjz3eM8H0Y2+20UH4yraPxTIX4M0p+8/pO2DJvGL0zx81/I0DZG6cwnh022M/epu5juTVV+mO/ZHMPsWWR+j5Lx6sGOqVp1qz1VJFpT000vi6dH8e1WbQJGQ8Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088155; c=relaxed/simple; bh=Tgv6BaPO00+S97jAlKTn6xh90zKsXielMTf93zwSMVo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=G0UFu89fDLHaTPDcvL8GPjqM3+FFMHhK+7wwUuf7qGrFXAmzPMO63NQd03OzzzarjZQHEd2kML28BQkDaVALGkGzIyHF7Cpz9odjVvilKRAvYsSCDVmVRztTNad+IA7xHM6xMtoCVjJGVBToFXv5qtGT7PvMG4uxtmb4fHKlQDc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=eMg02pV0; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="eMg02pV0" Received: by smtp.kernel.org (Postfix) with ESMTPS id 545AAC2BD04; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=Tgv6BaPO00+S97jAlKTn6xh90zKsXielMTf93zwSMVo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=eMg02pV0j2JCWPb5cuVkzKt2xEl4QphaZGbfI/lXaJw8KGG5JF9kkE4cie2WNKuB2 ofMM9t9xZVswRKoTmAlE5gSYjt/feGOAMlzk4KWDxJm3/LFKPMlF0qrNNB4RIGpdGy +lrd35d8Yp9f03o1mV4dfGhXNBNHkC3wwkVlYJfzL1TWuU2PipcDsrI20lzokMVou1 OcvvHleFUSgFEjXRvr5kLNcPYQAp9RRUmxfkEe7dEPBxsiwZhIh3VzvrbL+9uN7nYH oPMQlItj9qAkIBXzJpvArrW+JkSsIQ440+7kvd+N8beNa4W0v/MgHAftlWM5C+6/bM t1Q5JjM/s9WpA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 36382CCA467; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:52 +0800 Subject: [PATCH v4 03/17] mm/memcg: add folio-based lruvec live helper 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-3-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=2034; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=bqSj6GEUFE1ch4tTEQEPZOkssBWYsWV/WhQc/GCJ0Bs=; b=ade8dgVk/QLtMdvzfHEj3EebA82ilil4wqag8aQYk8TBs2jVnr7PYCpkRECMbBvrjTfV5YSLN PEO3hnbVTjMAeyNrCoshcrvmZa1ho7dU4F2NiVCiQv+mbLQ0DEfSQ5d X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Add a helper that resolves a stable lruvec for a folio under RCU without taking the lruvec lock. It takes a folio directly so the lruvec lookup happens inside the RCU read-side critical section, which a lruvec-based interface cannot guarantee. No functional change. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- include/linux/memcontrol.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index 74110a324f9e..829ab776ab44 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -1535,6 +1535,45 @@ static inline void lruvec_lock_irq(struct lruvec *lr= uvec) spin_lock_irq(&lruvec->lru_lock); } =20 +/** + * folio_lruvec_live_get - get a live lruvec for a folio under RCU + * @folio: the folio + * + * Computes @folio's lruvec and walks up to the nearest live ancestor + * if the folio's memcg is dying. Paired with folio_lruvec_live_put(). + * The result may be stale: RCU keeps it alive but does not pin @folio + * to it. That is fine as the counters are fixed up on reparenting. + * + * Return: the live lruvec, with rcu_read_lock held. + */ +static inline struct lruvec *folio_lruvec_live_get(struct folio *folio) +{ +#ifdef CONFIG_MEMCG + struct lruvec *lruvec; + struct pglist_data *pgdat; + struct mem_cgroup *memcg; + + rcu_read_lock(); + lruvec =3D folio_lruvec(folio); + pgdat =3D lruvec_pgdat(lruvec); + memcg =3D lruvec_memcg(lruvec); + while (unlikely(memcg && css_is_dying(&memcg->css))) { + memcg =3D parent_mem_cgroup(memcg); + lruvec =3D mem_cgroup_lruvec(memcg, pgdat); + } + return lruvec; +#else + return folio_lruvec(folio); +#endif +} + +static inline void folio_lruvec_live_put(struct lruvec *lruvec) +{ +#ifdef CONFIG_MEMCG + rcu_read_unlock(); +#endif +} + static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec) { #ifdef CONFIG_MEMCG --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 F021356E06F; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=Ud+9c9uSMRGVe1kA6J4aYscn2FV8cneogO7UWE3eyghnVMwA6ujz3X+ElcgxkRwWsY27VPA2hK2E9icrINEniJdqpgHdu0gmb0ewwQgc+w+jlRztDtgqGuz7TNj4dVOXPY8xgasUePF1T1D1sfppvZfSTg47gSRTfnKAYhaNwC4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=CvPSTWKbJkzmMCIykbfy2PrhU4speR1WveluNfiYs5M=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JLUjscvRP9Sqwrf+Y9BzMVuXuACksWDRSL9VGFNuZ3WrCrsIxcxR0kK7Rn1LPzBIB+2XsGUu2JpYIigDHIOAssOlpb57cfXD6tmD4ki6lXEDWLEbjP0cT8XcFg4+uQq9fkvwfODcEe9hcGZ/67MT6WNLZoDqo35FNBYjUMqiDug= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=liTTEkpw; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="liTTEkpw" Received: by smtp.kernel.org (Postfix) with ESMTPS id 63A53C4AF0F; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=CvPSTWKbJkzmMCIykbfy2PrhU4speR1WveluNfiYs5M=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=liTTEkpwtbD+L3pv6EdNrMisSRnLwUgea1tL4hR54AKroZYAqzeqkz4avJYWMyxUs XE75iqvb4up/ner6xOcOsBN2SkZCR3dVNFEmVnru9aSAmkOdGGYt3x7kv/unHe+2GP P2Ps0Tx7A/kNCqUEbY5TmBpDKqTiT1S0PNSDyDFkOKlEMrL5OBCDrKh67GnSDqX/LV heKePwoINhA/kGG+CRpxdAp7TK7ruVwFkpOEFvvN7VQ72yqennoYSb6l56PuKZYQAJ 5n0aLJNqT+ekJI30HXVymKyZllHQhdiUwuKdyVm4/YuBeH+BEYI0lfsRh4a4NBPbP2 vv6Ryf4mIhzCg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4E69ECCA465; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:53 +0800 Subject: [PATCH v4 04/17] mm/mglru: frequency guided workingset promotion (MGLRU-FG) 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-4-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=48575; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=/IkH8KD0rXlQK2dl3v5IdZnM5Wj9+44RS99bGANy0eA=; b=UIlIF0NUb4eyPO8umGVCdw8doZ1EGqtbT/pSiHwCUTz5g4FF+5NfTp2qPDopsPBl1H8Oui1R3 XYGQSdGnS5aDwCUXrUwn2+lUZUc7zZR7EXshpYcesYwPC9e4Nq0FZs/ X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Complement MGLRU's eviction-time tier-PID protection with access-time frequency-guided promotion. Introduce a unified set of helpers built based on referenced (access) count of a folio. Each access increments a folio's referenced count stored in folio flags (refs), refs still maps to a logarithmic tier just like before, but with more formal bit definitions, a few special thresholds are introduced: LRU_REFS_REFERENCED (1), LRU_REFS_WORKINGSET (2), LRU_REFS_PROTECTED (3), and LRU_REFS_MAX (7). When refs reaches a certain threshold, the folio is promoted proactively instead of waiting for the PID controller to kick in. Also simplify MGLRU's usage of PG_workingset and PG_referenced: they become the low two bits of the refs count, with the higher bits provided by LRU_REFS_MASK. This reduces MGLRU's original refs count bit usage by one, since only one extra bit is now needed to record a max referenced count of 7, and makes MGLRU's refs accounting more accurate. This doesn't affect classical LRU in any way, and it addresses several shortcomings of MGLRU's old tier-only cache protection model: - Long feedback loop: protection only activated after enough re-faults, by which time the hot folios are already evicted, or no longer hot. - Limited tier resolution: once referenced count exceeded the bits limit (8 previously), MGLRU could no longer distinguish hotter folios as they are capped by the tier. And what's worse, PG_workingset forces a folio to stay on tier 3. - Eviction hotness reversion: because PID protection activates upon eviction and always targets the LRU tail, it tends to protect cold tail folios at the expense of hotter head folios. Once the tail folios consume the PID protection budget, head folios lose their protection. - Additionally, the PID cannot distinguish the access time of folios that share the same reference count, and there are only 4 tiers. To achieve a frequency-guided framework, this commit introduces and reworks the LRU_REFS related helpers and definitions; most of the work is done by the helpers below, and their inline comments describe the details. - folio_inc_lru_refs(): Called on any cache access (folio_mark_accessed) or page table access. This is the main helper: it promotes folios according to their access frequency. A first access only defers eviction for a folio at the tail of its LRU, i.e., in one of the MIN_NR_GENS oldest generations of its own type (see lru_gen_at_tail()), and a workingset folio accessed through a file descriptor is deferred from the same tail. Those are the generations the eviction scans next, so the deferral moves the folio out of its way for one generation. Promotion is lazy: the gen bits and size counters are updated eagerly, while the list move is deferred to the next isolation. NOTE: For now, the lruvec lock is unconditionally taken on every on-list access to block concurrent aging; a lockless fast path will be implemented very soon in a following commit. - folio_inc_lru_refs_walk(): Used by the PTE walk path during aging and by the batched look-around, both without the LRU lock, so max_seq may be stale. A first page table access only advances a folio at the tail of its LRU, i.e., in one of the MIN_NR_GENS oldest generations of its own type (second chance), a second access promotes it to the newest generation; this also performs lazy promotion. - folio_inc_lru_refs_isolated(): Used by the rmap check before eviction. The folio is isolated and hence this doesn't perform promotion by itself; the folio will be added back to the right gen upon return according to the access frequency. This path also has a higher promotion bias. The eviction-time folio_inc_gen() still handles PID protection, but the protection ratio is softer than before since proactive promotion is mostly good enough already. The PID gain factors are relaxed from (2:3) to (1:2) and the setpoint now spans the cumulative mass of the tiers below the candidate instead of tier 0 alone. folio_inc_gen() caps refs at WORKINGSET so the folio retains enough history to stay above the cold tier. Tier 1 is the fallback tier for PID, and tier 2 is the fallback tier for frequency-guided promotion. The forced protection for full-refs folios is removed, obsoleted by the proactive promotion. Refaults are now activated purely according to access frequency: the old fault bias applied in folio_add_lru() is simplified, since a folio's access history is a more consistent signal than the context of the faulting task. Page table access is still considered a slightly stronger signal. Consequently the placement hint for newly added file folios also uses LRU_REFS_WORKINGSET instead of PG_referenced, so a folio accessed only once now starts in the oldest generation rather than the second oldest. The previous one-generation grace was an artifact of the fault bias which is well described in the cover letter of this series, and it depended on LRU drain timing. This also redefines PG_workingset and PG_referenced as the low two bits of the refs count, eliminating the old restriction where LRU_REFS_MASK was only valid when PG_referenced was set, and allows all paths to use the same encoding consistently. Following this idea, a workingset folio is now defined as refs >=3D LRU_REFS_WORKINGSET (2), matching the active/inactive LRU's definition and giving in-kernel consumers (PSI, readahead) consistent behavior on MGLRU, fixing the longstanding issue that these users don't work well with MGLRU. PG_workingset and PG_referenced are no longer independent flags under MGLRU. Adjusting existing raw folio_test_*() callers to the new semantics is left as follow-ups. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- include/linux/mm_inline.h | 96 ++++++++------ include/linux/mmzone.h | 145 +++++++++++++++------ kernel/bounds.c | 2 +- mm/folio.c | 50 +------- mm/vmscan.c | 320 +++++++++++++++++++++++++++++++-----------= ---- mm/workingset.c | 45 ++++--- 6 files changed, 409 insertions(+), 249 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index f52f02e8e5be..74d1b7a04ca1 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -144,12 +144,13 @@ static inline int lru_hist_from_seq(unsigned long seq) return seq % NR_HIST_GENS; } =20 -static inline int lru_tier_from_refs(int refs, bool workingset) +static inline int lru_tier_from_refs(unsigned int refs) { - VM_WARN_ON_ONCE(refs > BIT(LRU_REFS_WIDTH)); - - /* see the comment on MAX_NR_TIERS */ - return workingset ? MAX_NR_TIERS - 1 : order_base_2(refs); + BUILD_BUG_ON(fls(LRU_REFS_MAX - 1) > MAX_NR_TIERS - 1); + VM_WARN_ON_ONCE(refs > LRU_REFS_MAX); + if (refs < LRU_REFS_WORKINGSET) + return 0; + return fls(refs - 1); } =20 /** @@ -187,21 +188,24 @@ static inline int lru_get_gen_flags(unsigned long fla= gs) * @flags: pointer to the folio flags * @refs: referenced / access count number, between 0 and LRU_REFS_MAX, in= clusive. * - * For MGLRU, PG_referenced holds the first ref, and the extra bits hold t= he - * remaining refs. For classical LRU the extra bits are not used, so it can - * also be seen as the refs count never exceeds 1. In both cases, refs =3D= =3D 1 - * means PG_referenced is set and the extra bits are zero, and refs =3D=3D= 0 means - * PG_referenced and the extra bits are all unset. + * For MGLRU, PG_referenced, PG_workingset are used as the lower two bits = of + * refs counter, and extra bits hold the remaining higher bits. For classi= cal + * LRU the extra bits are not used, and the two flags has no direct + * relationship with each other, but this helper can still be used to sync + * them. For both cases, refs =3D=3D 0 means these two flags and the extra= bits + * are all unset. And refs =3D=3D 1 / 2 / 3 means PG_referenced and PG_wor= kingset + * are set in an bit order way, which is more meaningful for MGLRU though. */ static inline void lru_set_refs_flags(unsigned long *flags, unsigned int r= efs) { VM_WARN_ON_ONCE(refs > LRU_REFS_MAX); - BUILD_BUG_ON(LRU_REFS_MAX !=3D (LRU_REFS_MASK >> LRU_REFS_PGOFF) + 1); - + BUILD_BUG_ON(LRU_REFS_MASK & (BIT(PG_referenced) | BIT(PG_workingset))); *flags &=3D ~LRU_REFS_FLAGS; - if (!refs) - return; - *flags |=3D (BIT(PG_referenced) | ((refs - 1UL) << LRU_REFS_PGOFF)); + if (refs & BIT(0)) + *flags |=3D BIT(PG_referenced); + if (refs & BIT(1)) + *flags |=3D BIT(PG_workingset); + *flags |=3D ((unsigned long)refs >> 2) << LRU_REFS_PGOFF; } =20 /** @@ -212,13 +216,13 @@ static inline void lru_set_refs_flags(unsigned long *= flags, unsigned int refs) */ static inline int lru_get_refs_flags(unsigned long flags) { - if (!(flags & BIT(PG_referenced))) - return 0; - /* - * Return the total number of accesses including PG_referenced. Also see - * the comment on LRU_REFS_FLAGS. - */ - return ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) + 1; + int refs; + + /* Return the total number of accesses. See the comment above MAX_NR_TIER= S. */ + refs =3D (flags & BIT(PG_referenced)) ? BIT(0) : 0; + refs +=3D (flags & BIT(PG_workingset)) ? BIT(1) : 0; + refs +=3D ((flags & LRU_REFS_MASK) >> LRU_REFS_PGOFF) << 2; + return refs; } =20 static inline int folio_lru_refs(const struct folio *folio) @@ -236,14 +240,29 @@ static inline void folio_set_lru_refs(struct folio *f= olio, unsigned int refs) } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); } =20 +void folio_inc_lru_refs(struct folio *folio, unsigned int flags); + static inline int folio_lru_gen(const struct folio *folio) { return lru_get_gen_flags(READ_ONCE(*const_folio_flags(folio, 0))); } =20 +static inline bool lru_gen_at_tail(struct lruvec *lruvec, int type, int ge= n) +{ + unsigned long min_seq =3D READ_ONCE(lruvec->lrugen.min_seq[type]); + + /* + * Consider a folio is at the tail of its LRU when it is within + * MIN_NR_GENS of the oldest generation of its own type + */ + BUILD_BUG_ON(MIN_NR_GENS !=3D 2); + return gen =3D=3D lru_gen_from_seq(min_seq) || + gen =3D=3D lru_gen_from_seq(min_seq + 1); +} + static inline bool lru_gen_is_active(const struct lruvec *lruvec, int gen) { - unsigned long max_seq =3D lruvec->lrugen.max_seq; + unsigned long max_seq =3D READ_ONCE(lruvec->lrugen.max_seq); =20 VM_WARN_ON_ONCE(gen >=3D MAX_NR_GENS); =20 @@ -300,23 +319,24 @@ static inline unsigned long lru_gen_folio_seq(const s= truct lruvec *lruvec, bool reclaiming) { int gen; + int refs =3D folio_lru_refs(folio); int type =3D folio_is_file_lru(folio); const struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 /* - * +-----------------------------------+---------------------------------= --+ - * | Accessed through page tables and | Accessed through file descriptor= s | - * | promoted by folio_update_gen() | and protected by folio_inc_gen()= | - * +-----------------------------------+---------------------------------= --+ - * | PG_active (set while isolated) | = | - * +-----------------+-----------------+-----------------+---------------= --+ - * | PG_workingset | PG_referenced | PG_workingset | LRU_REFS_FLAG= S | - * +-----------------------------------+---------------------------------= --+ - * |<---------- MIN_NR_GENS ---------->| = | - * |<---------------------------- MAX_NR_GENS ---------------------------= ->| + * +------------------------------------------+--------------------------= ----------------+ + * | Accessed through page tables and | Accessed through file= descriptors | + * | promoted by folio_inc_lru_refs_walk() | protected by folio_inc_lr= u_refs/inc_gen | + * +------------------------------------------+--------------------------= ----------------+ + * | PG_active (set at isolation or refault) | = | + * +--------------------+---------------------+--------------------+-----= ----------------+ + * | LRU_REFS_MAX | LRU_REFS_WORKINGSET | LRU_REFS_MAX | LRU_= REFS_WORKINGSET | + * +------------------------------------------+--------------------------= ----------------+ + * |<-------------- MIN_NR_GENS ------------->| = | + * |<----------------------------------- MAX_NR_GENS --------------------= --------------->| */ if (folio_test_active(folio)) - gen =3D MIN_NR_GENS - folio_test_workingset(folio); + gen =3D MIN_NR_GENS - (refs >=3D LRU_REFS_WORKINGSET); else if (reclaiming) gen =3D MAX_NR_GENS; else if ((!folio_is_file_lru(folio) && !folio_test_swapcache(folio)) || @@ -324,7 +344,7 @@ static inline unsigned long lru_gen_folio_seq(const str= uct lruvec *lruvec, (folio_test_dirty(folio) || folio_test_writeback(folio)))) gen =3D MIN_NR_GENS; else - gen =3D MAX_NR_GENS - (folio_test_workingset(folio) || folio_test_refere= nced(folio)); + gen =3D MAX_NR_GENS - (refs >=3D LRU_REFS_WORKINGSET); =20 return max(READ_ONCE(lrugen->max_seq) - gen + 1, READ_ONCE(lrugen->min_se= q[type])); } @@ -338,6 +358,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lru= vec, struct folio *folio, int zone =3D folio_zonenum(folio); struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 + BUILD_BUG_ON(BIT(LRU_GEN_WIDTH - 1) !=3D MAX_NR_GENS); VM_WARN_ON_ONCE_FOLIO(gen !=3D -1, folio); =20 if (folio_test_unevictable(folio) || !lrugen->enabled) @@ -392,7 +413,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lru= vec, struct folio *folio, */ static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) { - BUILD_BUG_ON(LRU_REFS_MASK & BIT(PG_referenced)); folio_set_lru_refs(new, folio_lru_refs(old)); } #else /* !CONFIG_LRU_GEN */ @@ -422,6 +442,10 @@ static inline bool lru_gen_del_folio(struct lruvec *lr= uvec, struct folio *folio, return false; } =20 +static inline void folio_inc_lru_refs(struct folio *folio, unsigned int fl= ags) +{ +} + static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) { if (folio_test_referenced(old)) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 09ce82fa841a..b5a3769fdcab 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -453,57 +453,122 @@ enum lruvec_flags { #define MAX_NR_GENS 4U =20 /* - * Each generation is divided into multiple tiers. A folio accessed N times - * through file descriptors is in tier order_base_2(N). A folio in the fir= st - * tier (N=3D0,1) is marked by PG_referenced unless it was faulted in thro= ugh page - * tables or read ahead. A folio in the last tier (MAX_NR_TIERS-1) is mark= ed by - * PG_workingset. A folio in any other tier (1flags. + * Each generation is divided into multiple tiers. A folio's referenced + * count maps to a tier as shown below: * - * In contrast to moving across generations which requires the LRU lock, m= oving - * across tiers only involves atomic operations on folio->flags and theref= ore - * has a negligible cost in the buffered access path. In the eviction path, - * comparisons of refaulted/(evicted+protected) from the first tier and th= e rest - * infer whether folios accessed multiple times through file descriptors a= re - * statistically hot and thus worth protecting. + * MGLRU (frequency guidance) + * Refs Tier |- Refs: how many times (at least) a folio has been refere= nced. + * 0 0 |- Mostly cold pages, readahead, etc. [1] + * 1 0 |=3D LRU_REFS_REFERENCED: Used at least once. [2] + * -WORKINGSET-+|- Pages beyond are workingset and never fall below this f= loor. [3] + * 2 1<-+|=3D LRU_REFS_WORKINGSET: Classical workingset, accessed t= wice, protected. [4] + * 3 2 |- LRU_REFS_PROTECTED: Protected workingset, promoted page= s capped at here. [5] + * 4 2 | + * 5 3 |- The tier here is MAX_NR_TIERS - 1 + * 6 3 | + * 7 3 |=3D LRU_REFS_MAX: Promotion candidate. [6] + * -PROMOTION->-/ * - * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice the - * number of categories of the active/inactive LRU when keeping track of - * accesses through file descriptors. This uses MAX_NR_TIERS-2 spare bits = in - * folio->flags, masked by LRU_REFS_MASK. + * Ideally each tier holds folios of similar access patterns: lower tiers + * are less important and evicted faster. A page's reference count and + * tier are capped when it changes generation, preventing it from + * dominating the new generation based on old-generation access history. + * Generation ordering already ensures a newer-gen page is hotter than an + * older-gen one regardless of tier. + * + * Refs tracks accesses from two sources: page table (lazily collected by + * the page table aging walk or rmap eviction lookup) and file descriptors + * (by folio_mark_accessed). Page table accesses are weighted heavier + * because the accessed bit is sticky (undercounts repeated accesses), + * passively collected, and page faults are generally more important as + * userspace does not expect a memory access to block on reclaim. Both + * access types increment refs by one; the result is capped at + * LRU_REFS_PROTECTED on promotion or deferral, or LRU_REFS_MAX otherwise. + * + * 1. Tier is fls(N-1) for N > 1, 0 otherwise. Folios with zero + * accesses (refs =3D=3D 0) are generally cold, e.g. readahead folios. + * + * Freshly allocated folios start with refs =3D=3D 0; faulted and mapped + * folios have their page table access bit set, so the first page table + * access check always sets LRU_REFS_REFERENCED. That is the second + * chance described above MIN_NR_GENS: it moves the folio one + * generation forward if it is at the tail of its LRU, i.e., in one + * of the two oldest generations of its type. + * + * 2. Folios accessed once stay on tier 0: one-time usage does not + * qualify for protection. A second access advances the folio, + * aligning with classical LRU's use-twice threshold. A second page + * table access promotes to the latest gen; file access only defers + * eviction from the tail gens of its type. + * + * 3. Folios accessed at least twice are considered workingset. This + * mostly aligns with classical LRU: at least one I/O is saved by + * keeping them in memory. Folios at or above this level never fall + * below tier 1 (the workingset floor), so tier 0 stays a clean tier + * for cold cache while tier 1 serves as the fallback line for + * actually reused or historically hot folios. + * + * Folios refaulted through a page fault at refs 1 will enter the second + * newest gen, so faulting will be protected better. + * + * 4. Starting from tier 1, PID protection sacrifices lower tiers to + * protect higher tiers by comparing refault rates for long-term + * accuracy, and caps higher refs to this value. Since PID protection + * bypasses page table lookup and clearing, when a further eviction + * attempt occurs after PID loosens, the folio's page table access is + * rechecked and a referenced folio is put back with refs capped to + * this value as well. This also gives folios a fair opportunity to be + * promoted by file access again. + * + * Folios refaulted through a page fault at tier 1 or above are activat= ed + * and enter the newest gen. Non fault page will enter second oldest ge= n, + * driving aging and workingset shifting. + * + * 5. Pages beyond the ordinary workingset tier form new tiers for the + * PID controller to protect differently. Folios at or above this + * level are capped at LRU_REFS_PROTECTED on promotion or deferral, + * and at LRU_REFS_WORKINGSET under PID protection in the oldest + * generation, where they represent a historical workingset. + * + * 6. Folios that reach LRU_REFS_MAX are advanced to the next generation + * on further access, with refs capped to LRU_REFS_PROTECTED. This + * gives them a fair start for advancement to an even newer generation + * while keeping hot folios distinguishable. + * + * Tiering uses PG_referenced and PG_workingset as the lower two bits, + * and the bits masked by LRU_REFS_MASK as the higher bits, so the refs + * count ranges from 0 to LRU_REFS_MAX. A folio is on the workingset + * tier once accessed at least twice, which is more consistent with the + * classical LRU. + * + * A folio's referenced count never goes backwards except upon gen + * increase as described above, or when explicitly reset by + * lru_gen_clear_refs(). Refault of a reclaimed folio restores + * its referenced count, capped at LRU_REFS_PROTECTED, which aligns with + * promotion. Page table refaults of previous workingset folios send + * them to the latest gen, driving aging faster. + * + * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice + * the number of categories of the active/inactive LRU. */ #define MAX_NR_TIERS 4U #define LRU_TIER_MIN 0U #define LRU_TIER_MAX (MAX_NR_TIERS - 1) =20 +/* Access source flags for folio_inc_lru_refs() */ +#define LRU_REF_MAPPED 0x1U +#define LRU_REF_EXEC 0x2U + +#define LRU_REFS_REFERENCED 0x1 +#define LRU_REFS_WORKINGSET 0x2 +#define LRU_REFS_PROTECTED 0x3 + #ifndef __GENERATING_BOUNDS_H =20 #define LRU_GEN_MASK ((BIT(LRU_GEN_WIDTH) - 1) << LRU_GEN_PGOFF) #define LRU_REFS_MASK ((BIT(LRU_REFS_WIDTH) - 1) << LRU_REFS_PGOFF) -#define LRU_REFS_MAX BIT(LRU_REFS_WIDTH) - -/* - * For folios accessed multiple times through file descriptors, - * lru_gen_inc_refs() sets additional bits of LRU_REFS_WIDTH in folio->fla= gs - * after PG_referenced, then PG_workingset after LRU_REFS_WIDTH. After all= its - * bits are set, i.e., LRU_REFS_FLAGS|BIT(PG_workingset), a folio is lazily - * promoted into the second oldest generation in the eviction path. And wh= en - * folio_inc_gen() does that, it clears LRU_REFS_FLAGS so that - * lru_gen_inc_refs() can start over. Note that for this case, LRU_REFS_MA= SK is - * only valid when PG_referenced is set. - * - * For folios accessed multiple times through page tables, folio_update_ge= n() - * from a page table walk or lru_gen_set_refs() from a rmap walk sets - * PG_referenced after the accessed bit is cleared for the first time. - * Thereafter, those two paths set PG_workingset and promote folios to the - * youngest generation. Like folio_inc_gen(), folio_update_gen() also clea= rs - * PG_referenced. Note that for this case, LRU_REFS_MASK is not used. - * - * For both cases above, after PG_workingset is set on a folio, it remains= until - * this folio is either reclaimed, or "deactivated" by lru_gen_clear_refs(= ). It - * can be set again if lru_gen_test_recent() returns true upon a refault. - */ -#define LRU_REFS_FLAGS (LRU_REFS_MASK | BIT(PG_referenced)) +#define LRU_REFS_FLAGS (LRU_REFS_MASK | BIT(PG_referenced) | BIT(PG_worki= ngset)) +#define LRU_REFS_MAX (BIT(LRU_REFS_WIDTH + 2) - 1) =20 struct lruvec; struct page_vma_mapped_walk; diff --git a/kernel/bounds.c b/kernel/bounds.c index 02b619eb6106..06a034713b5d 100644 --- a/kernel/bounds.c +++ b/kernel/bounds.c @@ -25,7 +25,7 @@ int main(void) DEFINE(SPINLOCK_SIZE, sizeof(spinlock_t)); #ifdef CONFIG_LRU_GEN DEFINE(LRU_GEN_WIDTH, order_base_2(MAX_NR_GENS + 1)); - DEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 2); + DEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 3); #else DEFINE(LRU_GEN_WIDTH, 0); DEFINE(__LRU_REFS_WIDTH, 0); diff --git a/mm/folio.c b/mm/folio.c index 35e242b48870..e200efab814f 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -273,7 +273,6 @@ static void lru_activate(struct lruvec *lruvec, struct = folio *folio) if (folio_test_active(folio) || folio_test_unevictable(folio)) return; =20 - lruvec_del_folio(lruvec, folio); folio_set_active(folio); lruvec_add_folio(lruvec, folio); @@ -352,32 +351,6 @@ static void __lru_cache_activate_folio(struct folio *f= olio) =20 #ifdef CONFIG_LRU_GEN =20 -static void lru_gen_inc_refs(struct folio *folio) -{ - unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); - int refs; - - if (folio_test_unevictable(folio)) - return; - - /* see the comment on LRU_REFS_FLAGS */ - if (!folio_lru_refs(folio)) { - folio_set_lru_refs(folio, 1); - return; - } - - do { - new_flags =3D old_flags; - refs =3D lru_get_refs_flags(old_flags); - if (refs =3D=3D LRU_REFS_MAX) { - if (!folio_test_workingset(folio)) - folio_set_workingset(folio); - return; - } - lru_set_refs_flags(&new_flags, refs + 1); - } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); -} - static bool lru_gen_clear_refs(struct folio *folio) { int gen =3D folio_lru_gen(folio); @@ -388,7 +361,6 @@ static bool lru_gen_clear_refs(struct folio *folio) return true; =20 folio_set_lru_refs(folio, 0); - folio_clear_workingset(folio); =20 rcu_read_lock(); seq =3D READ_ONCE(folio_lruvec(folio)->lrugen.min_seq[type]); @@ -399,10 +371,6 @@ static bool lru_gen_clear_refs(struct folio *folio) =20 #else /* !CONFIG_LRU_GEN */ =20 -static void lru_gen_inc_refs(struct folio *folio) -{ -} - static bool lru_gen_clear_refs(struct folio *folio) { return false; @@ -428,7 +396,8 @@ void folio_mark_accessed(struct folio *folio) if (folio_test_dropbehind(folio)) return; if (lru_gen_enabled()) { - lru_gen_inc_refs(folio); + if (!folio_test_unevictable(folio)) + folio_inc_lru_refs(folio, 0); return; } =20 @@ -474,21 +443,6 @@ void folio_add_lru(struct folio *folio) folio_test_unevictable(folio), folio); VM_BUG_ON_FOLIO(folio_test_lru(folio), folio); =20 - /* - * For refaulted workingset folios, set PG_active so they - * can be added to active generations. - * For prefaulted file folios, folio_mark_accessed() sets - * PG_referenced so lru_gen_folio_seq() places them into - * the second oldest generation. - */ - if (lru_gen_enabled() && !folio_test_unevictable(folio) && - lru_gen_in_fault() && !(current->flags & PF_MEMALLOC)) { - if (folio_test_workingset(folio)) - folio_set_active(folio); - else if (!folio_test_referenced(folio)) - folio_mark_accessed(folio); - } - folio_batch_add_and_move(folio, lru_add); } EXPORT_SYMBOL(folio_add_lru); diff --git a/mm/vmscan.c b/mm/vmscan.c index cab3ba14d20c..d0f0e5a25d87 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -931,38 +931,186 @@ enum folio_references { }; =20 #ifdef CONFIG_LRU_GEN +/*************************************************************************= ***** + * Referenced count feedback + *************************************************************************= *****/ + /* - * Only used on a mapped folio in the eviction (rmap walk) path, where pro= motion - * needs to be done by taking the folio off the LRU list and then adding i= t back - * with PG_active set. In contrast, the aging (page table walk) path uses - * folio_update_gen(). + * The folio_inc_lru_refs{_*} helpers below collect the referenced info + * (hotness) from other parts, including the page table walker, the rmap w= alk + * upon eviction, the rmap lookaround, and file descriptors + * (folio_mark_accessed). + * + * Page table accesses use refs from either source. The first reference + * advances tail-gen folios by one generation; a second promotes to the + * newest. Executable file folios promote on their first access to avoid + * I/O thrashing. Isolated rmap accesses use evict_folios() for putback. + * + * Promotion changes folio->flags; sort_folio() or inc_min_seq() moves the + * folio later. Young PTEs found during unmapping use folio_mark_accessed() + * and follow the file descriptor rules below. + * + * File descriptor accesses do not promote. They only defer eviction from + * the tail gens of their type, and only once the folio is a workingset + * folio (LRU_REFS_WORKINGSET), leaving the rest to PID protection. Page + * table accesses are treated more generously because the accessed bit is + * sticky (it under-counts repeated accesses) and because a page fault is + * more costly than file descriptor I/O. + * + * PID protection operates on tier > 0 folios. The one proactive promotion + * outside of it and the page table path is the overflow case where the + * referenced count exceeds LRU_REFS_MAX, which means the folio is hotter + * than everything else in its generation. + * + * Whenever a folio changes generation here its referenced count is capped= at + * LRU_REFS_PROTECTED, so it starts at or below the protected tier regardl= ess + * of its old-generation access history. PID protection (folio_inc_gen) c= aps + * at LRU_REFS_WORKINGSET independently. */ -static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_f= lags) -{ - /* see the comment on LRU_REFS_FLAGS */ - if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { - /* Activate file-backed executable folios after first usage. */ - if (is_exec_file_folio(folio, vma_flags)) { - folio_set_workingset(folio); - folio_set_lru_refs(folio, 0); - return true; + +/* + * Update the folio's lru refs indicator. The caller doesn't need to hold + * the folio lock, isolate the folio, or hold the lruvec lock. Used by both + * cache access (flags =3D=3D 0) and page table access (LRU_REF_MAPPED, + * optionally with LRU_REF_EXEC). + */ +void folio_inc_lru_refs(struct folio *folio, unsigned int flags) +{ + int max_gen; + int type, refs, old_gen, gen; + unsigned long new_flags, old_flags, max_seq; + struct lru_gen_folio *lrugen; + struct lruvec *lruvec =3D NULL; + + type =3D folio_is_file_lru(folio); + old_flags =3D READ_ONCE(*folio_flags(folio, 0)); + do { + new_flags =3D old_flags; + old_gen =3D lru_get_gen_flags(old_flags); + refs =3D lru_get_refs_flags(old_flags) + 1; + gen =3D old_gen; + if (old_gen < 0) + goto out; + /* + * Lock the lruvec if the folio is on-list. We are already + * doing lazy promotion so in theory we don't need this, + * but for now, concurrent aging would still corrupt the + * size counters. This is a temporary limitation and + * will be lifted very soon, so the lock here is not a + * performance concern. + */ + if (!lruvec) { + lruvec =3D lruvec_live_lock_irq(folio_lruvec(folio)); + lrugen =3D &lruvec->lrugen; } + max_seq =3D READ_ONCE(lrugen->max_seq); + max_gen =3D lru_gen_from_seq(max_seq); + if (old_gen =3D=3D max_gen) + goto out; =20 - folio_set_lru_refs(folio, 1); - return false; - } + if (flags & (LRU_REF_MAPPED | LRU_REF_EXEC)) { + /* Promote second page table access or executable */ + if (refs > LRU_REFS_REFERENCED || flags & LRU_REF_EXEC) + gen =3D max_gen; + /* First access only defers eviction of a tail gen */ + else if (lru_gen_at_tail(lruvec, type, old_gen)) + gen =3D (old_gen + 1) % MAX_NR_GENS; + refs =3D min(refs, LRU_REFS_PROTECTED); + } else if (refs > LRU_REFS_MAX) { + /* LRU refs counting overflow, bump the gen */ + gen =3D (old_gen + 1) % MAX_NR_GENS; + refs =3D LRU_REFS_PROTECTED; + } else if (lru_gen_at_tail(lruvec, type, old_gen) && refs >=3D LRU_REFS_= WORKINGSET) { + /* Defer eviction of just accessed workingset */ + gen =3D (old_gen + 1) % MAX_NR_GENS; + refs =3D min(refs, LRU_REFS_PROTECTED); + } +out: + refs =3D min(refs, LRU_REFS_MAX); + lru_set_refs_flags(&new_flags, refs); + if (gen !=3D old_gen) + lru_set_gen_flags(&new_flags, gen); + if (new_flags =3D=3D old_flags) + break; + } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); =20 - /* Promote on second access */ - if (folio_lru_refs(folio) > 1) { - folio_set_workingset(folio); - folio_set_lru_refs(folio, 0); - } else { - folio_mark_accessed(folio); - } - return true; + if (gen !=3D old_gen) + lru_gen_update_size(lruvec, folio, old_gen, gen); + if (lruvec) + lruvec_unlock_irq(lruvec); +} + +/* + * Update the folio's lru refs indicator during a page table walk or the + * look-around. max_seq can be stale as neither holds the LRU lock. + * + * Returns the old generation and stores the new generation in @new_gen if + * the folio is on the LRU and not in the newest generation, or -1 otherwi= se. + */ +static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lru= vec, + const vma_flags_t *vma_flags, + int *new_gen, int *type) +{ + unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); + int refs, gen, max_gen, ret; + unsigned long max_seq; + + do { + max_seq =3D READ_ONCE(lruvec->lrugen.max_seq); + max_gen =3D lru_gen_from_seq(max_seq); + gen =3D lru_get_gen_flags(old_flags); + refs =3D lru_get_refs_flags(old_flags) + 1; + *type =3D folio_flags_is_file_lru(&old_flags); + new_flags =3D old_flags; + + if (gen >=3D 0 && gen !=3D max_gen) { + ret =3D gen; + /* Promote second page table access or executable */ + if (refs > LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags)) + *new_gen =3D max_gen; + /* First access only defers eviction of a tail gen */ + else if (lru_gen_at_tail(lruvec, *type, gen)) + *new_gen =3D (gen + 1) % MAX_NR_GENS; + else + *new_gen =3D gen; + lru_set_gen_flags(&new_flags, *new_gen); + lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_PROTECTED)); + } else { + ret =3D -1; + lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_MAX)); + } + if (new_flags =3D=3D old_flags) + break; + } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); + + return ret; +} + +/* + * Update the lru refs indicator of an isolated folio, only used on + * mapped folios upon the final eviction. + * + * Increments the refs count (capped at LRU_REFS_PROTECTED, evict_folios() + * then caps it at LRU_REFS_WORKINGSET). Returns true if the caller should + * activate the folio (second access or executable), false to put it back + * for a second chance. + */ +static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_fla= gs_t *vma_flags) +{ + unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); + int refs; + + do { + new_flags =3D old_flags; + refs =3D lru_get_refs_flags(old_flags) + 1; + lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_PROTECTED)); + } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); + + /* Promote second page table access or executable */ + return refs > LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags); } #else -static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_f= lags) +static bool folio_inc_lru_refs_isolated(struct folio *folio, const vma_fla= gs_t *vma_flags) { return false; } @@ -997,7 +1145,8 @@ static enum folio_references folio_check_references(st= ruct folio *folio, if (!referenced_ptes) return FOLIOREF_RECLAIM; =20 - return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIORE= F_KEEP; + return folio_inc_lru_refs_isolated(folio, &vma_flags) ? + FOLIOREF_ACTIVATE : FOLIOREF_KEEP; } =20 referenced_folio =3D folio_test_clear_referenced(folio); @@ -3298,9 +3447,9 @@ static bool iterate_mm_list_nowalk(struct lruvec *lru= vec, unsigned long seq) * P term over the generations previously evicted, using the smoothing fac= tor * 1/2; the D term isn't supported. * - * The setpoint (SP) is always the first tier of one type; the process var= iable - * (PV) is either any tier of the other type or any other tier of the same - * type. + * To select a type, the setpoint (SP) is all tiers of one type and the pr= ocess + * variable (PV) is all tiers of the other type. To select a tier to prot= ect, + * the SP is the tiers below it and the PV is the tier itself. * * The error is the difference between the SP and the PV; the correction i= s to * turn off protection when SP>PV or turn on protection when SP=3D 0 && new_gen !=3D old_gen) { if (increased) *increased =3D false; @@ -3440,9 +3554,9 @@ static int __folio_inc_gen(struct folio *folio, int o= ld_gen, bool *increased) =20 new_flags =3D old_flags; new_gen =3D (old_gen + 1) % MAX_NR_GENS; - + refs =3D lru_get_refs_flags(old_flags); lru_set_gen_flags(&new_flags, new_gen); - lru_set_refs_flags(&new_flags, 0); + lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_WORKINGSET)); } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); =20 if (increased) @@ -3450,17 +3564,21 @@ static int __folio_inc_gen(struct folio *folio, int= old_gen, bool *increased) return new_gen; } =20 -/* protect pages accessed multiple times through file descriptors */ +/* + * Force bump a folio's generation. Used for PID protection or to skip a + * folio from a zone ineligible for the current reclaim. + */ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio) { + bool gen_increased; int type =3D folio_is_file_lru(folio); struct lru_gen_folio *lrugen =3D &lruvec->lrugen; int new_gen, old_gen =3D lru_gen_from_seq(lrugen->min_seq[type]); - bool gen_increased; =20 new_gen =3D __folio_inc_gen(folio, old_gen, &gen_increased); if (gen_increased) lru_gen_update_size(lruvec, folio, old_gen, new_gen); + return new_gen; } =20 @@ -3655,25 +3773,25 @@ static void walk_update_folio(struct lru_gen_mm_wal= k *walk, struct vm_area_struc struct lruvec *lruvec, struct folio *folio, bool dirty) { int new_gen, old_gen, type; + unsigned int flags =3D LRU_REF_MAPPED; =20 if (!folio) return; =20 - new_gen =3D lru_gen_from_seq(READ_ONCE(lruvec->lrugen.max_seq)); - if (dirty && !folio_test_dirty(folio) && !(folio_test_anon(folio) && folio_test_swapbacked(folio) && !folio_test_swapcache(folio))) folio_mark_dirty(folio); =20 if (walk) { - old_gen =3D folio_update_gen(folio, new_gen, &type, &vma->flags); + old_gen =3D folio_inc_lru_refs_walk(folio, lruvec, &vma->flags, + &new_gen, &type); if (old_gen >=3D 0 && old_gen !=3D new_gen) update_batch_size(walk, folio, old_gen, new_gen, type); - } else if (lru_gen_set_refs(folio, &vma->flags)) { - old_gen =3D folio_lru_gen(folio); - if (old_gen >=3D 0 && old_gen !=3D new_gen) - folio_activate(folio); + } else { + if (is_exec_file_folio(folio, &vma->flags)) + flags |=3D LRU_REF_EXEC; + folio_inc_lru_refs(folio, flags); } } =20 @@ -4080,7 +4198,7 @@ static bool inc_min_seq(struct lruvec *lruvec, int ty= pe, int swappiness) struct folio *folio =3D list_entry(pos, struct folio, lru); long nr_pages =3D folio_nr_pages(folio); int refs =3D folio_lru_refs(folio); - bool workingset =3D folio_test_workingset(folio); + int tier =3D lru_tier_from_refs(refs); bool gen_increased; =20 VM_WARN_ON_ONCE_FOLIO(folio_test_unevictable(folio), folio); @@ -4100,13 +4218,8 @@ static bool inc_min_seq(struct lruvec *lruvec, int t= ype, int swappiness) delta +=3D nr_pages; batch_end =3D &folio->lru; =20 - /* don't count the workingset being lazily promoted */ - if (refs + workingset !=3D BIT(LRU_REFS_WIDTH) + 1) { - int tier =3D lru_tier_from_refs(refs, workingset); - - WRITE_ONCE(lrugen->protected[hist][type][tier], - lrugen->protected[hist][type][tier] + nr_pages); - } + WRITE_ONCE(lrugen->protected[hist][type][tier], + lrugen->protected[hist][type][tier] + nr_pages); } else { flush_lru_batch(head, &batch_end, target_list); list_move(&folio->lru, &lrugen->folios[new_gen][type][zone]); @@ -4821,8 +4934,7 @@ static bool sort_folio(struct lruvec *lruvec, struct = folio *folio, struct scan_c int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); int refs =3D folio_lru_refs(folio); - bool workingset =3D folio_test_workingset(folio); - int tier =3D lru_tier_from_refs(refs, workingset); + int tier =3D lru_tier_from_refs(refs); struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 VM_WARN_ON_ONCE_FOLIO(gen >=3D MAX_NR_GENS, folio); @@ -4838,17 +4950,15 @@ static bool sort_folio(struct lruvec *lruvec, struc= t folio *folio, struct scan_c } =20 /* protected */ - if (tier > tier_idx || refs + workingset =3D=3D BIT(LRU_REFS_WIDTH) + 1) { + if (tier > tier_idx) { + int hist =3D lru_hist_from_seq(lrugen->min_seq[type]); + gen =3D folio_inc_gen(lruvec, folio); list_move(&folio->lru, &lrugen->folios[gen][type][zone]); =20 - /* don't count the workingset being lazily promoted */ - if (refs + workingset !=3D BIT(LRU_REFS_WIDTH) + 1) { - int hist =3D lru_hist_from_seq(lrugen->min_seq[type]); + WRITE_ONCE(lrugen->protected[hist][type][tier], + lrugen->protected[hist][type][tier] + delta); =20 - WRITE_ONCE(lrugen->protected[hist][type][tier], - lrugen->protected[hist][type][tier] + delta); - } return true; } =20 @@ -4876,10 +4986,6 @@ static bool isolate_folio(struct lruvec *lruvec, str= uct folio *folio, struct sca return false; } =20 - /* see the comment on LRU_REFS_FLAGS */ - if (!folio_test_referenced(folio)) - folio_set_lru_refs(folio, 0); - success =3D lru_gen_del_folio(lruvec, folio, true); VM_WARN_ON_ONCE_FOLIO(!success, folio); =20 @@ -4967,13 +5073,14 @@ static int get_tier_idx(struct lruvec *lruvec, int = type) struct ctrl_pos sp, pv; =20 /* - * To leave a margin for fluctuations, use a larger gain factor (2:3). - * This value is chosen because any other tier would have at least twice - * as many refaults as the first tier. + * To leave a margin for fluctuations, use a larger gain factor (1:2). + * Stop at the first tier whose refault rate is clearly worse than + * that of the cumulative mass of the tiers below it; the PID + * protects the tiers above it. */ - read_ctrl_pos(lruvec, type, LRU_TIER_MIN, LRU_TIER_MIN, 2, &sp); for (tier =3D LRU_TIER_MIN + 1; tier <=3D LRU_TIER_MAX; tier++) { - read_ctrl_pos(lruvec, type, tier, tier, 3, &pv); + read_ctrl_pos(lruvec, type, LRU_TIER_MIN, tier - 1, 1, &sp); + read_ctrl_pos(lruvec, type, tier, tier, 2, &pv); if (!positive_ctrl_err(&sp, &pv)) break; } @@ -5115,12 +5222,13 @@ static int evict_folios(unsigned long nr_to_scan, s= truct lruvec *lruvec, } =20 /* - * See the comments on LRU_REFS_FLAGS. - * * The rejected folios are never added to the oldest generation, * so this effectively promotes them by at least one generation. + * PG_active is the only placement hint here, so a folio kept on + * its first page table access lands in the second newest one. + * See "Referenced count feedback" above. */ - folio_set_lru_refs(folio, 0); + folio_set_lru_refs(folio, min(folio_lru_refs(folio), LRU_REFS_WORKINGSET= )); if (lru_gen_folio_seq(lruvec, folio, false) =3D=3D min_seq[type]) folio_set_active(folio); } diff --git a/mm/workingset.c b/mm/workingset.c index 1504f91cdca5..16cdd88e275e 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -188,6 +188,12 @@ #define EVICTION_SHIFT_ANON (EVICTION_SHIFT + SWAP_COUNT_SHIFT) #define EVICTION_MASK (~0UL >> EVICTION_SHIFT) #define EVICTION_MASK_ANON (~0UL >> EVICTION_SHIFT_ANON) +/* + * LRU refs uses LRU_REFS_WIDTH + 2 bits, the 2 bits being PG_workingset + * and PG_referenced. The lowest bit is recorded in the workingset field + * of the shadow entry (to reuse pack_shadow()). + */ +#define LRU_REFS_BITS ((LRU_REFS_WIDTH + 2) - 1) =20 /* * Eviction timestamps need to be able to cover the full range of @@ -242,13 +248,12 @@ static void *lru_gen_eviction(struct folio *folio) int type =3D folio_is_file_lru(folio); int delta =3D folio_nr_pages(folio); int refs =3D folio_lru_refs(folio); - bool workingset =3D folio_test_workingset(folio); - int tier =3D lru_tier_from_refs(refs, workingset); + int tier =3D lru_tier_from_refs(refs); struct mem_cgroup *memcg; struct pglist_data *pgdat =3D folio_pgdat(folio); unsigned short memcg_id; =20 - BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_WIDTH > + BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_BITS > BITS_PER_LONG - max(EVICTION_SHIFT, EVICTION_SHIFT_ANON)); =20 rcu_read_lock(); @@ -256,14 +261,14 @@ static void *lru_gen_eviction(struct folio *folio) lruvec =3D mem_cgroup_lruvec(memcg, pgdat); lrugen =3D &lruvec->lrugen; min_seq =3D READ_ONCE(lrugen->min_seq[type]); - token =3D (min_seq << LRU_REFS_WIDTH) | max(refs - 1, 0); + token =3D (min_seq << LRU_REFS_BITS) | refs >> 1; =20 hist =3D lru_hist_from_seq(min_seq); atomic_long_add(delta, &lrugen->evicted[hist][type][tier]); memcg_id =3D mem_cgroup_private_id(memcg); rcu_read_unlock(); =20 - return pack_shadow(memcg_id, pgdat, token, workingset, type); + return pack_shadow(memcg_id, pgdat, token, refs & 1, type); } =20 /* @@ -284,9 +289,9 @@ static bool lru_gen_test_recent(void *shadow, struct lr= uvec **lruvec, *lruvec =3D mem_cgroup_lruvec(memcg, pgdat); =20 max_seq =3D READ_ONCE((*lruvec)->lrugen.max_seq); - max_seq &=3D (file ? EVICTION_MASK : EVICTION_MASK_ANON) >> LRU_REFS_WIDT= H; + max_seq &=3D (file ? EVICTION_MASK : EVICTION_MASK_ANON) >> LRU_REFS_BITS; =20 - return abs_diff(max_seq, *token >> LRU_REFS_WIDTH) < MAX_NR_GENS; + return abs_diff(max_seq, *token >> LRU_REFS_BITS) < MAX_NR_GENS; } =20 static void lru_gen_refault(struct folio *folio, void *shadow) @@ -314,22 +319,26 @@ static void lru_gen_refault(struct folio *folio, void= *shadow) lrugen =3D &lruvec->lrugen; =20 hist =3D lru_hist_from_seq(READ_ONCE(lrugen->min_seq[type])); - refs =3D (token & (BIT(LRU_REFS_WIDTH) - 1)) + 1; - tier =3D lru_tier_from_refs(refs, workingset); + refs =3D ((token & (BIT(LRU_REFS_BITS) - 1)) << 1) + workingset; + tier =3D lru_tier_from_refs(refs); =20 atomic_long_add(delta, &lrugen->refaulted[hist][type][tier]); =20 - if (workingset) { - /* - * see folio_add_lru(), where folio_set_active() is - * called for workingset folios - */ - if (lru_gen_in_fault()) + /* + * Activate a fault-driven refault folio, which would have been + * promoted had it stayed in memory. + */ + if (refs >=3D LRU_REFS_REFERENCED) { + if (lru_gen_in_fault()) { + folio_set_active(folio); mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + type, delta); - folio_set_workingset(folio); + } + /* Refault is also promotion, cap the refs like folio_inc_lru_refs */ + folio_set_lru_refs(folio, min(refs, LRU_REFS_PROTECTED)); + } + + if (refs >=3D LRU_REFS_WORKINGSET) mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + type, delta); - } else - set_mask_bits(&folio->flags.f, LRU_REFS_MASK, (refs - 1UL) << LRU_REFS_P= GOFF); unlock: rcu_read_unlock(); } --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 1036956E071; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=qSLbKwvPtdN1/ryxTm8ZVjbvyfmb+ONKnmJBUEJpRBiwWzRQKWWFOf4Zvtpwmn58tcB1/AxgjQwp8GhM0vpvOMj5dyz3QfZC4U8fFMKjWPM9bmqGkziZCZ5AIQpRSkIRZlRNndqadYBV7n7qut9tvfDkY9+GWxrcOf3MGckYPIk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=EtFdBNMl7rTc3+2KnBe5NCt6EPdFW/oy5PCsxQTtGYg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cXdrKifgvAUes2FjPQrDfykuBpnUOvnHnXpLy0L1908X1bcjdn5zv5ff1DjaW6qzB/cMGZ5bhK3pRpfkjQ4V6/BipguGNYkTIixGaBnsVgUBpimd2IkBBSJxKJ/NT4GoVvwdmnoasPmyPkovmhT2WR12xOvxCi4W5E/bcMWrYwk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Kb391yTr; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Kb391yTr" Received: by smtp.kernel.org (Postfix) with ESMTPS id 780E4C2BD05; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=EtFdBNMl7rTc3+2KnBe5NCt6EPdFW/oy5PCsxQTtGYg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Kb391yTrlC4wqc+SJNZmT89MRNc9OEN475jtNUimO8h5z4RHUz40UWiwBPKlRYi4b BRcSx6NPvAB8Bhlt5ggeFbJd3xK9L38LUm4G4HTyfSR/w68wtAyoQN2MQGcX8Sx5yM 0Tzhe6wjKtfLxIypH+atxvRH+FYhV4bwU5QatUIhLIIkJaFBuwp7kU8xv2syKolgMA nBpz1R42+F81MXByxQdA0syxQg+mRfgQUnPpXHVpWwkEnqKyALvYHQyt07h/myF/Hl GOXL+pwGG/dQ/W4Zt8jnBZAIU2bzt2POm4pm7L3eZA2X6Ex92zDcXWf6+lAPZcJnr6 AuYFDuEDuTu0A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 61F7FCCA469; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:54 +0800 Subject: [PATCH v4 05/17] mm/mglru: make folio lru referenced times count a generic API 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-5-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=4691; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=pnvvt+TNjTFasMrCaGELLai88pW+DMjr8ivb+8icxYs=; b=2pYnsvVBeywNZk/IqxKSwnVGHLJe4YzTLE0xzeBV928nbdHQBo8JVn3nfsIV5SXIAHdB9m5pu um1VbmccsnbBUOWooMumSGVyWRDol3yYP5jq8lKdlkvTeYic/0Sa+u4 X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Pure code shuffle, no behavior change. Make the folio LRU refs helpers available regardless of CONFIG_LRU_GEN. Without MGLRU the refs bits are never set, so they are inert. Move the lru_gen_* helpers into the CONFIG_LRU_GEN section. This prepares for unifying the API for checking folio referenced and workingset status. folio_migrate_lru_refs() now migrates the complete refs count, including PG_workingset (bit 1 of the encoding), bitwise identical to the direct copy dropped from folio_migrate_flags(). lru_set_refs_flags() skips the nonexistent mask bits when LRU_REFS_WIDTH collapses to 0, keeping the generic helpers inert for !CONFIG_LRU_GEN builds. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- include/linux/mm_inline.h | 55 ++++++++++++++++++++++++-------------------= ---- mm/migrate.c | 2 -- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index 74d1b7a04ca1..cfcbe4a9aa85 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -183,6 +183,12 @@ static inline int lru_get_gen_flags(unsigned long flag= s) return gen; } =20 +static inline int folio_lru_gen(const struct folio *folio) +{ + return lru_get_gen_flags(READ_ONCE(*const_folio_flags(folio, 0))); +} +#endif + /** * lru_set_refs_flags - Set the LRU referenced count to folio flags. * @flags: pointer to the folio flags @@ -205,7 +211,8 @@ static inline void lru_set_refs_flags(unsigned long *fl= ags, unsigned int refs) *flags |=3D BIT(PG_referenced); if (refs & BIT(1)) *flags |=3D BIT(PG_workingset); - *flags |=3D ((unsigned long)refs >> 2) << LRU_REFS_PGOFF; + if (LRU_REFS_WIDTH) + *flags |=3D ((unsigned long)refs >> 2) << LRU_REFS_PGOFF; } =20 /** @@ -240,13 +247,31 @@ static inline void folio_set_lru_refs(struct folio *f= olio, unsigned int refs) } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); } =20 +#ifdef CONFIG_LRU_GEN void folio_inc_lru_refs(struct folio *folio, unsigned int flags); +#else +static inline void folio_inc_lru_refs(struct folio *folio, unsigned int fl= ags) +{ + /* Should not be called with !CONFIG_LRU_GEN */ + WARN_ON_ONCE(1); +} +#endif =20 -static inline int folio_lru_gen(const struct folio *folio) +/** + * folio_migrate_lru_refs - copy the reference state to a new folio + * @new: the destination folio + * @old: the source folio + * + * Transfer the reference state to @new during migration: the MGLRU + * refs count, or PG_referenced and PG_workingset (bits 0-1 of the + * encoding) for the active/inactive LRU. + */ +static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) { - return lru_get_gen_flags(READ_ONCE(*const_folio_flags(folio, 0))); + folio_set_lru_refs(new, folio_lru_refs(old)); } =20 +#ifdef CONFIG_LRU_GEN static inline bool lru_gen_at_tail(struct lruvec *lruvec, int type, int ge= n) { unsigned long min_seq =3D READ_ONCE(lruvec->lrugen.min_seq[type]); @@ -401,20 +426,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lr= uvec, struct folio *folio, =20 return true; } - -/** - * folio_migrate_lru_refs - copy the reference state to a new folio - * @new: the destination folio - * @old: the source folio - * - * Transfer the reference state to @new during migration: the MGLRU - * refs count, including PG_referenced, or just PG_referenced for the - * active/inactive LRU. - */ -static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) -{ - folio_set_lru_refs(new, folio_lru_refs(old)); -} #else /* !CONFIG_LRU_GEN */ =20 static inline bool lru_gen_enabled(void) @@ -441,16 +452,6 @@ static inline bool lru_gen_del_folio(struct lruvec *lr= uvec, struct folio *folio, { return false; } - -static inline void folio_inc_lru_refs(struct folio *folio, unsigned int fl= ags) -{ -} - -static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) -{ - if (folio_test_referenced(old)) - folio_set_referenced(new); -} #endif /* CONFIG_LRU_GEN */ =20 static __always_inline diff --git a/mm/migrate.c b/mm/migrate.c index 7bdcdb57652f..5eefd8547f70 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -786,8 +786,6 @@ void folio_migrate_flags(struct folio *newfolio, struct= folio *folio) folio_set_active(newfolio); } else if (folio_test_clear_unevictable(folio)) folio_set_unevictable(newfolio); - if (folio_test_workingset(folio)) - folio_set_workingset(newfolio); if (folio_test_checked(folio)) folio_set_checked(newfolio); /* --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 3A61356E07F; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=nVzHSwFpWhZufAeO2+tJuounWz1p+50DOiLWhG0hrvB7ST/XVijDhH0NyaqBFLaPqFPdc9bfqcxRQ5QENRxmqTHsoIiQDWBnJN96AwdjzGwYIQ4XmraqA8c4J8iQHeIBcipao8g9CULuvY3EGJBfRIRewF0jlJJP/FDLA1CjK10= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=aXanTrv/q1uzORNQ0D7X/HNSQWLDCgGjHvyMIPDoJMQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=H+aIeQDj8NovytR2a3j4Vsek1ai1+fDqgfY98m1vzm68j4alXc9CjMzdubY2bjf2IYrVA/KPFejgHJ6SGdzmRhDrnmWrQo7tAQ7545qtNxOVJ4l+q+yMFVlAj96OVeL4R5KFPAMUEZ3ZODy4c+ZM+mSJHaLrIOBmN6JtlL0pkIY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=O0c+xTaO; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="O0c+xTaO" Received: by smtp.kernel.org (Postfix) with ESMTPS id 93E98C4AF48; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=aXanTrv/q1uzORNQ0D7X/HNSQWLDCgGjHvyMIPDoJMQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=O0c+xTaOezeSAIpT8j/CsXqvpM9BMqBpSJtUVXUk8NK9s390HXMkwjqEQCckxETGq TFbxK366KbU8AmdBLEuVJ8VIPTLQvOxx5bbxpTlPUGzII5KO9UEffCYZ2TrXDuhTu3 Myoaf3hCv5wruPYq+iFS7Of07Iv/R3AEtpqgjsKVM834ufUH2vVF55DVQ/Yy9vXYaH N2A8pEWKL3oPgIRgh4pGBRGwTr9KOKs3FPyfistQGo9yvB4QKprmulZv4FbBnhNQ8t jR64YoreNIle3uwxSQ3Foyufx4Dd2EaqkJCRE5N5317FOuXiaaoBjRtoxrnnWYOAy1 pfavLGAA+JSwA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 79BD1CCA464; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:55 +0800 Subject: [PATCH v4 06/17] mm/mglru: move add/del LRU size accounting out of lru_gen_update_size() 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-6-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=3201; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=R/1YuK+DuWVnDJLZi3UVamo0/HdrkSNNWuBiwaeVr3w=; b=2XLndrLsif/WtrelNCXSnsafTuNoASKjH8a8Rr0KhO6jbM9NO93sfCEV4tVmH61jPs7137Vv2 m5xLqJfCA4/BdxGQmIqtwu86gcbOIJVwUzpuYsoIQ563tkjn20h4A8R X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Pure code shuffle, no behavior change. lru_gen_update_size() now only updates the per-generation counters plus the promotion move; the addition and deletion active/inactive accounting move to lru_gen_add_folio() and lru_gen_del_folio(), which know the generation and can apply the active-window test directly. The unevictable case is untouched: unevictable folios never enter lru_gen_add_folio() and keep being accounted by lruvec_add_folio() as before. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- include/linux/mm_inline.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index cfcbe4a9aa85..65415eeac802 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -313,21 +313,9 @@ static inline void lru_gen_update_size(struct lruvec *= lruvec, struct folio *foli if (new_gen >=3D 0) atomic_long_add(delta, &lrugen->nr_pages[new_gen][type][zone]); =20 - /* addition */ - if (old_gen < 0) { - if (lru_gen_is_active(lruvec, new_gen)) - lru +=3D LRU_ACTIVE; - __update_lru_size(lruvec, lru, zone, delta); + /* return now if not a promotion */ + if (old_gen < 0 || new_gen < 0) return; - } - - /* deletion */ - if (new_gen < 0) { - if (lru_gen_is_active(lruvec, old_gen)) - lru +=3D LRU_ACTIVE; - __update_lru_size(lruvec, lru, zone, -delta); - return; - } =20 /* promotion */ if (!lru_gen_is_active(lruvec, old_gen) && lru_gen_is_active(lruvec, new_= gen)) { @@ -381,6 +369,8 @@ static inline bool lru_gen_add_folio(struct lruvec *lru= vec, struct folio *folio, int gen =3D folio_lru_gen(folio); int type =3D folio_is_file_lru(folio); int zone =3D folio_zonenum(folio); + int delta =3D folio_nr_pages(folio); + enum lru_list lru =3D type * LRU_INACTIVE_FILE; struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 BUILD_BUG_ON(BIT(LRU_GEN_WIDTH - 1) !=3D MAX_NR_GENS); @@ -396,6 +386,10 @@ static inline bool lru_gen_add_folio(struct lruvec *lr= uvec, struct folio *folio, set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK | BIT(PG_active), flags= ); =20 lru_gen_update_size(lruvec, folio, -1, gen); + if (lru_gen_is_active(lruvec, gen)) + lru +=3D LRU_ACTIVE; + __update_lru_size(lruvec, lru, zone, delta); + /* for folio_rotate_reclaimable() */ if (reclaiming) list_add_tail(&folio->lru, &lrugen->folios[gen][type][zone]); @@ -409,6 +403,9 @@ static inline bool lru_gen_del_folio(struct lruvec *lru= vec, struct folio *folio, { unsigned long flags; int gen =3D folio_lru_gen(folio); + int zone =3D folio_zonenum(folio); + int delta =3D folio_nr_pages(folio); + enum lru_list lru =3D folio_is_file_lru(folio) * LRU_INACTIVE_FILE; =20 if (gen < 0) return false; @@ -422,6 +419,9 @@ static inline bool lru_gen_del_folio(struct lruvec *lru= vec, struct folio *folio, gen =3D ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1; =20 lru_gen_update_size(lruvec, folio, gen, -1); + if (lru_gen_is_active(lruvec, gen)) + lru +=3D LRU_ACTIVE; + __update_lru_size(lruvec, lru, zone, -delta); list_del(&folio->lru); =20 return true; --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 44B9856E073; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=bb64odZDVaTFC7QsM8ZPhhYiXNZquls7FH96nYTY7Y15UzF6JKWBDbXh2v/M8BFMznjtwFbxVPMIhiLl6mML7wjeNDeksSSDqLD+vktKpNrHihgpgnII78XFFPYE8ei5ksmvmWO26gfT4ml4bAvVmAr1fWnXm/k0Z/RiIJNF+2M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=U0XTsJ3+hlRGEX8wHc1IuMeoyed0jAtILmjU1NZvH7w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=b0ZHYG9yWLPoZTs/6GFYPa+3gMcBxwZZOeAojuwAFwpHGerWMufqfisKW8Syhx5SEaF9ZNqvOikAdjlZ3COEcCnD5+AEUKc9yTvxYST1ruw8ztNbFw0yyaGVdAuYcl7VvmC3BuhoN1boAzwKqojCTzMQzDODodTi0oaCwwNnwDI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=CV8B2hm7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="CV8B2hm7" Received: by smtp.kernel.org (Postfix) with ESMTPS id A7765C4AF13; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=U0XTsJ3+hlRGEX8wHc1IuMeoyed0jAtILmjU1NZvH7w=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=CV8B2hm79IL3au/BRgJceXAVGFB0b7K12Rr/NWqEyNnbXX7amjLfdm0wluVSsFgRF WborxfzNSPU0rmjfSIWbHFPm8OK1nC7jDJDNiz2+ipusnLqOPJzOHNpxdTfZrjfDE3 QFRiOPkJDhbdbSkosH526nz7dxALeVQudDkxv6FzWHJ8nkNXUxeoXZnJLfTnvi9ZbK NwxsO33cRf0ekSetDlPycUKFfGV8L7ifiuaCHjefZ+DJOd/K5MgOQk9YKTAq6Qd0ww ZRSKfCO5DSbv6EfJU5uoOLGAdznGii+X0XmtN9KGamYSDj0f+nLhBh15UXyGnYw6Nm wR7PrHqHy4/Zg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 90207CCA465; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:56 +0800 Subject: [PATCH v4 07/17] mm/mglru, gup: mark folios referenced via a fast helper 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-7-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=2648; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=ITEZgO0dikHo93wyt9sJ147BaR3AkKEeJae1nLOf4S8=; b=m7t9PG61hS7nPGevrgY2lLxFygT2BRwOkANXUPK9pgm7cjzydaFuFNGer2+HyT8kYNrOODT8f JQDjMBv3q9CBhHyCf1aD5G9FDxDU4iLASn7H87eMqcXifIm9Voax7yj X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Introduce folio_inc_lru_refs_fast(): raise refs from 0 to LRU_REFS_REFERENCED via cmpxchg, or just set PG_referenced for the classical LRU as before. No counter update, promotion, and guarteens no other complex operations in following commits. Convert the gup fast-path sites. No behavior change for classical LRU. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- include/linux/mm_inline.h | 25 +++++++++++++++++++++++++ mm/gup.c | 6 +++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index 65415eeac802..effa13c63632 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -454,6 +454,31 @@ static inline bool lru_gen_del_folio(struct lruvec *lr= uvec, struct folio *folio, } #endif /* CONFIG_LRU_GEN */ =20 +/** + * folio_inc_lru_refs_fast - Bump folio refs count without promotion. + * @folio: the folio + * + * Raise refs from 0 to LRU_REFS_REFERENCED and leave hotter folios + * untouched. For the classical LRU, a plain PG_referenced set. + */ +static __always_inline void folio_inc_lru_refs_fast(struct folio *folio) +{ + unsigned long new_flags, old_flags; + + if (!lru_gen_enabled()) { + folio_set_referenced(folio); + return; + } + + old_flags =3D READ_ONCE(*folio_flags(folio, 0)); + do { + if (lru_get_refs_flags(old_flags)) + break; + new_flags =3D old_flags; + lru_set_refs_flags(&new_flags, LRU_REFS_REFERENCED); + } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); +} + static __always_inline void lruvec_add_folio(struct lruvec *lruvec, struct folio *folio) { diff --git a/mm/gup.c b/mm/gup.c index f166acf794e3..b3f563d41192 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -2896,7 +2896,7 @@ static unsigned long gup_fast_pte_range(pmd_t pmd, pm= d_t *pmdp, gup_put_folio(folio, 1, flags); goto pte_unmap; } - folio_set_referenced(folio); + folio_inc_lru_refs_fast(folio); pages[nr_pages] =3D page; nr_pages++; } while (ptep++, addr +=3D PAGE_SIZE, addr !=3D end); @@ -2965,7 +2965,7 @@ static unsigned long gup_fast_pmd_leaf(pmd_t orig, pm= d_t *pmdp, =20 for (i =3D 0; i < nr_pages; i++) *(pages++) =3D page++; - folio_set_referenced(folio); + folio_inc_lru_refs_fast(folio); return nr_pages; } =20 @@ -3007,7 +3007,7 @@ static unsigned long gup_fast_pud_leaf(pud_t orig, pu= d_t *pudp, =20 for (i =3D 0; i < nr_pages; i++) *(pages++) =3D page++; - folio_set_referenced(folio); + folio_inc_lru_refs_fast(folio); return nr_pages; } =20 --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 3690656E07B; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=Z1BW/Rjp41oyj0cObB71D4eN7wzuPIrn20lB8DjKZtMgnf6hlQ+gq3ZE4NpTcno5rnW+4sP55Mp/6DhqTp0WAQ7NgoSWC5vtXI/Argkrfi35CICWnbUHA3OK7oGXKA2F2gOaEa/Ifk45zsulSwIdo0JW6cJr2k4OJvDgevrlVZc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=ZzlqwZdbbSmlpiAV59dl01bIvah2RXTPlK1Ztf6kC9w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=f7gEnbdiqha1Z8WRWNLPeQoyZeybYAhU89OzjFwIIpvN28EU/L5vJ+0FE9949yoVVNDrFZlWwB3lsYVsZPEDAXsxGrXb99j5+egqRrpxD7dZPiH/f86YGt1nAgOv5ljr7hIlJM+IMoDfyOy83TU/WwsNmkBBQqMw3DbAvSjTjMs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=YT7UwdZ2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="YT7UwdZ2" Received: by smtp.kernel.org (Postfix) with ESMTPS id B833BC4AF0B; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=ZzlqwZdbbSmlpiAV59dl01bIvah2RXTPlK1Ztf6kC9w=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=YT7UwdZ25W42wsgsFlkTJmaSSoOhzC8oGgl5P7Ac+jDupj+GoMJ5fceCdxdQNxoSc ncNxxDyyeFbyQTTi6+zi4OHQodqhysvHGFWtMpPqzGE+LQMOdCWHvuSZlxbNR9oW5A 5zY6gy7ue6W1e78Cod15DMMDqm43a8GRyX4Zv9H5ZRM2G4JwwAVyzCsymXiuTaMkl2 99tV0PL+0Wg6cG1w4SfqBZnW1L0t0aoOyJ64rIyRsthrF99oozVBpbK/DiJmTvpnbq gXaG2hLBTKcf6Y1iUBcRwshERDuNnnrODTI85xGwMSGxlgu3FHDLZ3YYt/Zu/oOV2S itPNzqwJ1pxRw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id A4049CCA466; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:57 +0800 Subject: [PATCH v4 08/17] mm/mglru, smaps: convert to LRU refs based operations for MGLRU 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-8-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=3775; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=1v4LOI4JHk+vBXr1A8j77mCVzkc1JT179HQJTN1nz/8=; b=nJf/e5uxq7wxKBLvgMhdEEpMSQy5vLPa9CaLfszqpF4Jni0dW53W0S1f38toJQy1B0iMhmzeH mMSVOTnvvWeAA1/1zaUtw/+ofRhQp3FH37zeT/yem6PsGVi/QPFzahQ X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song PG_referenced is only the lowest bit of the folio LRU refs count (LRU_REFS_FLAGS) now, and MGLRU never kept it stable: promotion used to clear it, and now it toggles as the count grows. But with the LRU referenced count as a stable API now, we can use two helpers to provide in-kernel users a stable and sane reading of the referenced status of an LRU managed folio. Add folio_check_lru_refs() and folio_clear_lru_refs(), which use the refs count when MGLRU is enabled and PG_referenced otherwise, and switch the smaps paths over to them. The lru_gen_enabled() test stays inside the helpers, so its static key is not splattered over the few call sites. Convert smaps as the first user of them. MGLRU now reports every folio with a nonzero count, so the reading no longer flickers and clearing no longer results in an ambiguous status. The classical LRU case is unchanged. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- fs/proc/task_mmu.c | 6 +++--- include/linux/mm_inline.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 81c464139f8f..55f3a35fd3af 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -888,7 +888,7 @@ static void smaps_account(struct mem_size_stats *mss, s= truct page *page, =20 mss->resident +=3D size; /* Accumulate the size in pages that have been accessed. */ - if (young || folio_test_young(folio) || folio_test_referenced(folio)) + if (young || folio_test_young(folio) || folio_check_lru_refs(folio)) mss->referenced +=3D size; =20 /* @@ -1685,7 +1685,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned = long addr, /* Clear accessed and referenced bits. */ pmdp_test_and_clear_young(vma, addr, pmd); folio_test_clear_young(folio); - folio_clear_referenced(folio); + folio_clear_lru_refs(folio); out: spin_unlock(ptl); return 0; @@ -1714,7 +1714,7 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned = long addr, /* Clear accessed and referenced bits. */ ptep_test_and_clear_young(vma, addr, pte); folio_test_clear_young(folio); - folio_clear_referenced(folio); + folio_clear_lru_refs(folio); } pte_unmap_unlock(pte - 1, ptl); cond_resched(); diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index effa13c63632..6c9510002754 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -479,6 +479,37 @@ static __always_inline void folio_inc_lru_refs_fast(st= ruct folio *folio) } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); } =20 +/** + * folio_check_lru_refs - Test if an LRU managed folio has been referenced. + * @folio: The folio to test. + * + * Returns true if the MGLRU refs count is nonzero, or PG_referenced is se= t, + * which is also correct for the non-MGLRU case. Kept as a separate helper, + * to avoid splattering the lru_gen_enabled() static key. + */ +static inline bool folio_check_lru_refs(const struct folio *folio) +{ + if (lru_gen_enabled()) + return folio_lru_refs(folio) > 0; + return folio_test_referenced(folio); +} + +/** + * folio_clear_lru_refs - Clear the referenced state of an LRU managed fol= io. + * @folio: The folio to clear. + * + * Reset the MGLRU refs count to zero, or clear PG_referenced, leaving + * PG_workingset alone for the non-MGLRU case. Kept as a separate helper + * just like folio_check_lru_refs(). + */ +static inline void folio_clear_lru_refs(struct folio *folio) +{ + if (lru_gen_enabled()) + folio_set_lru_refs(folio, 0); + else + folio_clear_referenced(folio); +} + static __always_inline void lruvec_add_folio(struct lruvec *lruvec, struct folio *folio) { --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 51EE957266A; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=LNahHw4rVpvG0uij5wj4M7cVfPqh5PZ/ShNuF40vUnuhkpu1xpAp9zwQ3xYJAxz6/36QKESqs8pFR6QLnit8ICfoskAS7Ki24R+W3OxZsAU0yRdHJ1f6NuaJVOFF0WjYYuoS5kowWjsxeztxDW9icbUANxrH7PMWJ9xiJuHhWk8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=pughgVfMKfCJ9xqikMGkfv2NRbdXiHsr3Sk79GCYkoc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Yu3OL5LHS8j50fz7JxQn3s+SDLvflFRD7CaraYxDTixAvD3S3JEUlorE2J9SpmeN0/+rrDGUoCYepqQ3WxJ+PxxkZmiGIglskRUbnYBscuaC3ZHwTZHjZaC6w5Ip6aE45v7xxnZsW/i+fEGDe13vsNYfVnKWINwodEgnvLJyOIQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=IDkFtyu1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="IDkFtyu1" Received: by smtp.kernel.org (Postfix) with ESMTPS id CCB8BC2BCFC; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=pughgVfMKfCJ9xqikMGkfv2NRbdXiHsr3Sk79GCYkoc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=IDkFtyu1xmRn8a+O3+PAEm1hPF3AY2R9tbhZz/tbxKpWee4tzPOpaOKYd9CyLv3pv dAJ16sivZg0xheEOy90bFy2b2CLFYtsxU6VCBTF8ktVIIuVID+rJDdiDFRGoXHnZcS BSa+Na119f0ljoLX6pZ727OF7GqJOA67yeTIx9GBlqbqlZ1PkP8L8QIeH71PxsOjPJ KE2Q2tkKZVKFXFBQBKya07eZiD1BW6p5HJhvhkIIkqTY/RqfboB9gIxOHrdEVViV85 yCKjkYtSyJZSolpQSlgh9yaRyXVLdgq3pCaNUAlpdq2iJS/3d3fLjaAD+86rscCFwX 7nHY323fXrmZQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B6EDCCCA464; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:58 +0800 Subject: [PATCH v4 09/17] mm/damon: convert to LRU refs based operations for MGLRU 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-9-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=935; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=IdCP7RuDv2hWzX1uoW+WX1Go69TBrhltD7BGki0ofGQ=; b=Jvnw/LzNfAwF0Q6VT7GjTKg2XPpgBmn8d8tPZH6NZUS89sJE3igFX1rLRNxA0eR1bno0GkTug trWX/+m9nCICP1Y2k3Z/SKOS/64rdqmsq80XiuX9nKzQcQXYdDvvbls X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song damon_pa_pageout() cleared PG_referenced directly, which under MGLRU may corrupts the folio's refs count in later commits. That flag is now bit 0 of the count (LRU_REFS_FLAGS). Reset the count instead under MGLRU. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- mm/damon/paddr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 2cfdc356b415..6412ff2396b3 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -279,7 +279,8 @@ static unsigned long damon_pa_pageout(struct damon_regi= on *r, else *sz_filter_passed +=3D folio_size(folio) / addr_unit; =20 - folio_clear_referenced(folio); + /* The regions is cold, better drop the hotness info */ + folio_clear_lru_refs(folio); folio_test_clear_young(folio); if (!folio_isolate_lru(folio)) goto put_folio; --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 4D303572664; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=ByAfIGHJxj5RTm5KjofKCYizUGdFWDO2BxUAMxWWHX0uEPfYFgscommoSBpmXc4z/HS/fDIz9RZQCqK2uC4DMt8p5/z2QisP77siJtwvfo5sP8gTlXzh4guNe/HC150aq3h6iPEMzk4MCHTPWC6j1NLZZGlqLzLaVAi0tnLgDt4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=IMIPLnANXxcSiolHkXP5An66gjpZDy2DGAjDFMRr3K0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=gjIYCaKmJmelSbDRq+bpHwcHC1WX7RxRSt4dBg/VrGdi0UDfdgPKrjH++bbUG0vKBbcMJfWbQw1uVQTnWsI7rWnjLQLpUZ2WdFIoH+B4Nd1lS1RT0FqqcD1MCTY1xewL4lXyMd3FJZcs6A4Wof7smHA2tTbyHqO17brIqzFTVp4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=g9bXnKJr; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="g9bXnKJr" Received: by smtp.kernel.org (Postfix) with ESMTPS id DD688C2BCFF; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088155; bh=IMIPLnANXxcSiolHkXP5An66gjpZDy2DGAjDFMRr3K0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=g9bXnKJrBTQ6Ld6/kpqL1Ajd9NGGt1ufkGztPE8/MeYq6Y7QuILia/Uw05u5r/j4o 8b8o7nMWtDmzqBgpfVePSXay61ORbWNthuvoJRsVALw1Orvn7E5gpM+UJD5GBwhzMv 08wE1BTkmYHVRtQ5AiT5+E8v1tOt9/TgV3e/ov824ojz86MNf0y1utcklNfTIkKfGS Dq20Hns8fFSd4NTYM/j4uvdIWkwxukrCrNgAXydo6mKfHkbm6xn7UaALuqg4hjuid6 dfDSKo8RWsEdVEwv7uJ4Z9n428ZUAtHgWPoF7gc7MsQbih74WXO9F/4joaMGQkbx78 3P3r0WGuztjyg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C92C9CCA467; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:15:59 +0800 Subject: [PATCH v4 10/17] mm/khugepaged: convert to LRU refs based operations for MGLRU 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-10-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=1305; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=+Tb0hPQRVBeGNFQursundpLJPnEU7iUig7NZAg5dyxU=; b=CyGgF3KwdKpDK/OPe5JsFLV9+9m3zkFUvTK3nxJVeinp1EKn/EhM9YluR8cGMSowDmMM9MQFe v00Gu8A/iiFBT95X30QnZS7uBlb25LDBy7tmWh/fvzwrgOY4vWuOcKC X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song For MGLRU, PG_referenced is only the lowest bit of the folio LRU refs count, so folios with a higher refs count no longer have the bit set. folio_pte_referenced() testing the raw bit misses hot folios at refs >=3D 2, under-counting the referenced folios of a candidate range and aborting otherwise good collapses with SCAN_LACK_REFERENCED_PAGE. Test the refs count directly when MGLRU is enabled, mirroring the smaps conversion. The classical LRU keeps the plain bit test. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- mm/khugepaged.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index ef5cd6fda34f..5332b4c51fec 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -579,7 +579,9 @@ static bool folio_pte_referenced(struct folio *folio, struct vm_area_struct *vma, unsigned long addr, pte_t pteval) { /* The folio was referenced previously ... */ - if (folio_test_young(folio) || folio_test_referenced(folio)) + if (folio_test_young(folio)) + return true; + if (folio_check_lru_refs(folio)) return true; /* ... or the PTE mapping was recently used */ return pte_young(pteval) || mmu_notifier_test_young(vma->vm_mm, addr); --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 65D4E572671; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=dW530svWa+VYRTEgdbWVa5rY4Kb55p316VXkQLHEbZny85Sr4vA1DRoWq8xvOLBvNinkd6QFwL24pUKefLOHhWhXZzlpND4/iUnQ8MJHsYfoygab/BhJ6BqfK2c1/e4qEbcHp5YsWL+cYfIMwzj23yWAejyQUSaxIH1Eoh0l4co= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=nd2RmTuSslJnHHTOLfAjN4i3GoAr0BthhFzpuPLIffs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HZ0JNi7lBIiSj9D69YCL3gs+1+pSs6vGYVi/qQbp5hZiWqR+ZPi1XSrzN7mRqvqrUnA67ci3LZoLBchRPaOcWR1sfr7IhGPu4ybmvSXhffdK2rgDkmXoSNtHmtINt+7ow9yQdqFp9CFkwFt0vut/C9WfWv9AaL5rZ2ZpBtIebiM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=JH20tM6q; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="JH20tM6q" Received: by smtp.kernel.org (Postfix) with ESMTPS id F36B3C4AF0D; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=nd2RmTuSslJnHHTOLfAjN4i3GoAr0BthhFzpuPLIffs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=JH20tM6qgsPSZ7MBDmiPlliFqWm52DizmYPhPQ3LiUrb+gAFkp5ca88sSWKZTkWRJ +E0OXwTjaR4DdzlR+YHjyt3xNDwwoAt91voCSILH6JWvDKjOCMLKSgj6Lwhz811vHv +s4YRiI4ZRIAOZLomBkcU9sR6v9iIGi3hd05+wSlE7JpjRFu426Mjj5O8PyyGitrFT PH+gq+piDtxQQ323lIkHOf/iylJp6OR+j4uiyMZOM4lMOxFn4cU7u8hnt51A+1xVZr u1q6kZKKr7JVcEuYQzCQF9x/jE5bVndRDsK+GO1gotarEmgTSK5uavYmbAVSp0KYI5 KV4mFkhfWX6Fw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id DBAA7CC6B38; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:00 +0800 Subject: [PATCH v4 11/17] mm/madvise: adapt for LRU refs based operations for MGLRU 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-11-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=4236; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=d/kV0oKuWBLCvIIPKjnAO1UkbGprDut4GOEz6Q+ljhg=; b=LkpTc3Ht2aEIlGNioGudDwjL0alCNwKFNQQ+EepfbcbV2f/Wrt0p+JBHeBJaNievGrpbTY66k EW79JAIXU0oAbIkClChmotD8b6bslzRP941GoERuEspVmt5JiaAIfNd X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song For the active/inactive LRU, madvise clears PG_referenced so that one more access is not enough to reactivate a folio, and keeps PG_workingset on a folio demoted out of the active list so its refault is still accounted as a workingset refault (PSI). MGLRU keeps that history in the folio's refs count instead, which now lives in PG_referenced, PG_workingset and LRU_REFS_MASK, so writing to those bits directly corrupts the count. The two hints want different things: - MADV_COLD resets the count in folio_deactivate(), which also moves the folio to the oldest generation. - MADV_PAGEOUT caps the count at LRU_REFS_WORKINGSET before handing the folio to reclaim_pages(). The hint marks the folio as cold, so the eviction shadow records the capped count and does not inflate the eviction/refault counters of the hot tiers that drive aging, while folios at or above the workingset threshold keep their PSI classification. PG_young is cleared for both LRUs as before, so the referenced walk does not count stale history as a recent access. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- mm/madvise.c | 56 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/mm/madvise.c b/mm/madvise.c index 75b409e97dbf..2b4a8b2cb477 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -360,6 +360,44 @@ static inline int madvise_folio_pte_batch(unsigned lon= g addr, unsigned long end, FPB_MERGE_YOUNG_DIRTY); } =20 +static void madvise_cold_prep_folio(struct folio *folio) +{ + /* + * VM couldn't reclaim the folio unless we clear PG_young. + * As a side effect, it makes confuse idle-page tracking + * because they will miss recent referenced history. + */ + folio_test_clear_young(folio); + + /* + * For the active/inactive LRU, a folio demoted out of the active + * list should have PG_workingset so its refault is still accounted + * as a workingset refault, and the referenced bit always needs to + * be cleared even for inactive folios. Nothing to do for MGLRU as + * folio_deactivate() always calls lru_gen_clear_refs(). + */ + if (!lru_gen_enabled()) { + folio_clear_referenced(folio); + if (folio_test_active(folio)) + folio_set_workingset(folio); + } +} + +static void madvise_pageout_prep_folio(struct folio *folio) +{ + int refs =3D folio_lru_refs(folio); + + /* + * For MGLRU, drop the count below LRU_REFS_WORKINGSET and cap + * the rest at it: MADV is + * suggesting the folio is actually cold, so the eviction shadow + * should not record its hotness in the high tier counters, but + * the workingset classification is preserved for PSI. + */ + if (lru_gen_enabled()) + folio_set_lru_refs(folio, refs >=3D LRU_REFS_WORKINGSET ? LRU_REFS_WORKI= NGSET : 0); +} + static int madvise_cold_or_pageout_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, struct mm_walk *walk) @@ -434,12 +472,10 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *p= md, tlb_remove_pmd_tlb_entry(tlb, pmd, addr); } =20 - folio_clear_referenced(folio); - folio_test_clear_young(folio); - if (folio_test_active(folio)) - folio_set_workingset(folio); + madvise_cold_prep_folio(folio); if (pageout) { if (folio_isolate_lru(folio)) { + madvise_pageout_prep_folio(folio); if (folio_test_unevictable(folio)) folio_putback_lru(folio); else @@ -544,18 +580,10 @@ static int madvise_cold_or_pageout_pte_range(pmd_t *p= md, tlb_remove_tlb_entries(tlb, pte, nr, addr); } =20 - /* - * We are deactivating a folio for accelerating reclaiming. - * VM couldn't reclaim the folio unless we clear PG_young. - * As a side effect, it makes confuse idle-page tracking - * because they will miss recent referenced history. - */ - folio_clear_referenced(folio); - folio_test_clear_young(folio); - if (folio_test_active(folio)) - folio_set_workingset(folio); + madvise_cold_prep_folio(folio); if (pageout) { if (folio_isolate_lru(folio)) { + madvise_pageout_prep_folio(folio); if (folio_test_unevictable(folio)) folio_putback_lru(folio); else --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 5AEED57266F; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=jJJ2ENssQkeYoFRKq6T20UH7gyMhfyYQ34Y2IPXEB2hzKQbo8PzU8+GnfruUHRYcMYnuHem84xwnhm9XVeyqMtooaZDWFWem47bKnoigSJ7KftSfZpGgO3dvsW1cW9O5UpaG+grGZR7mv+yXhilEMPr5spoDxO6+O/zPiZCnJH4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=8gf9AogalgklAj0/E1KhlEb73BycuhAZd+4aVQS/8LM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=I4XF+I3wXeS/NMskI/IL5TKigEIaO2j0Kwz3EdmFAx8Ua3PrkhIrqUms8iLPsfLGrZOD64Zn5r0UJNKwlWMOn4ek0iMvEmQwE06hX8jprFTwBAAhW3pnRq2yjsHzI7pcVNHWV7tDbHpcPk7RWBT9p+ig4gaGly0nrtaYYjvD+O4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=LtTPvZb5; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="LtTPvZb5" Received: by smtp.kernel.org (Postfix) with ESMTPS id 139CBC2BCC7; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=8gf9AogalgklAj0/E1KhlEb73BycuhAZd+4aVQS/8LM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=LtTPvZb5LrYzAssllzS3Ch2E/QhQz420yqJQQM0/Ig1AgLUFSa3chI23kZX56qAAP xPEwiLeQ2QE83C5dCJoX8QPI2ey/Rzh4F/kqhqcNkrX7LIC6vRaCcThX3Z87/I/uRE gP5cm1giv6cHCGVKKou/Q8zp5D8TzUTceyQQW3zoEf8/R/UZzRV4tRv3iAW2wm3lZ0 tqfDiuipkbfdetdfs74lNUhUWuWglgoeIBihKuC3zNdxe5p1kcvUL4bWlQFmwPJz48 kwEYTmNsg1yHg5HM6mo4yHdv05LEDQWG+lXMcfLNcFTN4tE9grLuqNH2DHuec8sVpm fE6gPDkAPcsMw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id F2004CCA465; Thu, 15 Oct 2026 18:15:55 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:01 +0800 Subject: [PATCH v4 12/17] mm/huge_memory: mark folio as accessed more accurately on split 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-12-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=1882; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=8/kB3jYF2xvs54AjVF+kE6j3TkOpVQe7AYkhnz+HMSw=; b=tTKBt4tbGQ0fIDmdylFSAA5Og3kRUgAZK47NTX2iPzoR13ZjAG9OT3t0oZSGVCwoL/JJCEZn8 zftXgBIT5FsAod03/bFjXDDElj8FzkBq+ectBNqBwZPDK7UQEawF90Y X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song The behavior of updating the folio's access info isn't consistent for huge mapping splitting or ordinary unmapping. The page table's young flag has to be translated into folio's access info. Right now it only check and set folio's referenced flag, which isn't enough since folio flags update on access have its rules. Ordinary unmapping (zapping) calls folio_mark_accessed(), and it also checks if the VMA has recency to avoid false updates. So first just use the right helper here to be more consistent. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- mm/huge_memory.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index bd44d58e8684..1e2b6448c010 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -3093,8 +3093,8 @@ static void __split_huge_pud_locked(struct vm_area_st= ruct *vma, pud_t *pud, =20 if (!folio_test_dirty(folio) && pud_dirty(old_pud)) folio_mark_dirty(folio); - if (!folio_test_referenced(folio) && pud_young(old_pud)) - folio_set_referenced(folio); + if (pud_young(old_pud) && vma_has_recency(vma)) + folio_mark_accessed(folio); folio_remove_rmap_pud(folio, page, vma); add_mm_counter(vma->vm_mm, mm_counter_file(folio), -HPAGE_PUD_NR); @@ -3216,8 +3216,8 @@ static void __split_huge_pmd_locked(struct vm_area_st= ruct *vma, pmd_t *pmd, folio =3D page_folio(page); if (!folio_test_dirty(folio) && pmd_dirty(old_pmd)) folio_mark_dirty(folio); - if (!folio_test_referenced(folio) && pmd_young(old_pmd)) - folio_set_referenced(folio); + if (pmd_young(old_pmd) && vma_has_recency(vma)) + folio_mark_accessed(folio); folio_remove_rmap_pmd(folio, page, vma); add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR); folio_put(folio); --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 7D29E572679; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=UIvpDkUww3WjQSPZIliQMMjMh82OW2jpjOFAej8SrZ2TJWgxtq7RtCAa0An9lajL7Bmn6Xxh1XjtvjTgY+I/A6OfQoi36fdG+epwOuF5WOY7m0qHgVEHOfWZvF0o40QenrHaBzqoaywvMglubYOLKov2oqhB4IiEzf+esfbgFqc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=XzfMd/JnuGtq6Lo4DlHyUwy9oozGNrqi1mV227kp/OQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qcOPESXzHBa5Fc0jr/XJH0rbOW4oPtEUwb5dGn4vYYjtdO3PuthGRTYkR2AU5q67FY4ExGeWZZ6k6dt+ya78Jpdc7uQm7w3iKO2sA9v0cHWHDFtunbAx+GQqnXIorcU3HN7A3qiubhmhmKUVP8cy2n0hNVdv3msSGob9jIK3NJ4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=thRhbbY5; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="thRhbbY5" Received: by smtp.kernel.org (Postfix) with ESMTPS id 29C27C2BCF4; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=XzfMd/JnuGtq6Lo4DlHyUwy9oozGNrqi1mV227kp/OQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=thRhbbY5JFhqv7WVW+qlsKtm1HpH3zdnpZjMzYQ4r4IvW4RKbNz+tknxHpvtm/UTZ GCrHGgWR4GepuPIi9Du1DVYzWLshLoCu46jU2J+is272Ia9tScTSPWHcgscrrTnnSe ZKwgN5sA1D4QZ+l7/mt9U5+RllL2IQuYCX4Y2vJeBW63aputxe3ZrF8ldMBa0pJnDT AkbI0EoS1TijLlUWg40pUyra/Z0pyNkYtdlB0IIf63n3TiIRd1oHJGlgx/ECfQOoWq iIaV0JxE/Iqk57WMa7TzPm0gwLrY9RCY7tJCe9EjfchZsU2lGSvKvOYpNVoZrUGNF8 nKnPcpLrHcEVw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 14C98CCA466; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:02 +0800 Subject: [PATCH v4 13/17] mm/mglru: folio LRU refs based active/inactive number accounting 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-13-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=35324; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=qWDdMfKD3aX6cmey53dF93NBvlPBXQmaxW3lUP1XLmw=; b=dejRaCPCCxuLUpUxI48w0hC7ZCd0J1BLc1RUyDg9b3bWfDdNWITi/tM8kpOcBvQZBfRfu8eSV b54JIXCfEDeC0jRvUukLMWwL9hT//H6gYOxEFBFd/vgEGfLIGynm+an X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song Currently the active/inactive LRU sizes exported through /proc/vmstat and memory.stat are derived from the generation window: folios in the two newest generations are accounted as active, the rest as inactive. This has always caused many problems: - Unmapped file folios stick to the oldest gens, so active files are always under-reported, unlike classical LRU. - The reading is jumpy: on aging, the second-newest gen, previously active, becomes inactive all at once, causing a huge drop of active folios for no reason from time to time. - The reading is reversed on swapless machines, and the active anon number is an oscillating random number. When swappiness is 0 (no swap or swap is full), MGLRU ages anon by moving the gens directly without walk or touching any folio, to reduce the overhead. So all inactive anon folios will suddenly count as active, or vice versa, which breaks many metric reading components, and the reading itself doesn't make any sense in any way. With frequency guided promotion, hotness is already tracked per folio by its referenced count, so we can do folio level accounting instead: a folio is active once its refs reach LRU_REFS_ACTIVATED (currently LRU_REFS_PROTECTED). On access, folios are accounted to the active part. On forced aging or PID protecting, folios are reset to LRU_REFS_WORKINGSET, dropping them to the inactive part. The reading is now smooth and consistent, and much closer to classical LRU. Note that on swapless machines anon refs are never reset by aging (the shortcut skips the per-folio walk), so active anon grows monotonically, but the reading is stable and still much more meaningful than the previous oscillating random number. The accounting is now per folio and lruvec independent, so no size adjustment is needed when reparenting a dying memcg: the hierarchical stats keep the counts in the parent automatically. folio_migrate_lru_refs() also gains off-LRU VM_WARN_ON_ONCE checks and __folio_set_lru_refs() warns when the folio is still on an LRU list. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- Documentation/admin-guide/mm/multigen_lru.rst | 4 +- include/linux/mm_inline.h | 115 +++++++------- include/linux/mmzone.h | 23 ++- mm/folio.c | 35 +---- mm/madvise.c | 5 +- mm/vmscan.c | 211 ++++++++++++++++------= ---- mm/workingset.c | 2 +- 7 files changed, 220 insertions(+), 175 deletions(-) diff --git a/Documentation/admin-guide/mm/multigen_lru.rst b/Documentation/= admin-guide/mm/multigen_lru.rst index 9cb54b4ff5d9..808d2e97b3f3 100644 --- a/Documentation/admin-guide/mm/multigen_lru.rst +++ b/Documentation/admin-guide/mm/multigen_lru.rst @@ -149,8 +149,8 @@ generations less than or equal to ``min_gen_nr``. ``- memcg_id node_id min_gen_nr [swappiness [nr_to_reclaim]]`` =20 ``min_gen_nr`` should be less than ``max_gen_nr-1``, since -``max_gen_nr`` and ``max_gen_nr-1`` are not fully aged (equivalent to -the active list) and therefore cannot be evicted. ``swappiness`` +``max_gen_nr`` and ``max_gen_nr-1`` are not fully aged and therefore +cannot be evicted. ``swappiness`` overrides the default value in ``/proc/sys/vm/swappiness`` and the valid range is [0-200, max], with max being exclusively used for the reclamation of anonymous memory. ``nr_to_reclaim`` limits the number of pages to evict. diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index 6c9510002754..41c2e9150c70 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -105,6 +105,22 @@ static __always_inline enum lru_list folio_lru_list(co= nst struct folio *folio) return lru; } =20 +/** + * lru_get_gen_flags - Return the LRU generation number from folio flags. + * @flags: folio flags + * + * Returns: A number between 0 and (MAX_NR_GENS - 1), inclusive. Returns + * -1 if the flags indicate the folio is off the list (e.g., isolated). + */ +static inline int lru_get_gen_flags(unsigned long flags) +{ + int gen =3D ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1; + + /* Exclude the legal -1 from the unsigned MAX_NR_GENS comparison */ + VM_WARN_ON_ONCE(gen !=3D -1 && gen >=3D MAX_NR_GENS); + return gen; +} + #ifdef CONFIG_LRU_GEN =20 static inline bool lru_gen_switching(void) @@ -167,22 +183,6 @@ static inline void lru_set_gen_flags(unsigned long *fl= ags, int gen) *flags |=3D (gen + 1UL) << LRU_GEN_PGOFF; } =20 -/** - * lru_get_gen_flags - Return the LRU generation number from folio flags. - * @flags: folio flags - * - * Returns: A number between 0 and (MAX_NR_GENS - 1), inclusive. Returns - * -1 if the flags indicate the folio is off the list (e.g., isolated). - */ -static inline int lru_get_gen_flags(unsigned long flags) -{ - int gen =3D ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1; - - /* Exclude the legal -1 from the unsigned MAX_NR_GENS comparison */ - VM_WARN_ON_ONCE(gen !=3D -1 && gen >=3D MAX_NR_GENS); - return gen; -} - static inline int folio_lru_gen(const struct folio *folio) { return lru_get_gen_flags(READ_ONCE(*const_folio_flags(folio, 0))); @@ -237,24 +237,39 @@ static inline int folio_lru_refs(const struct folio *= folio) return lru_get_refs_flags(READ_ONCE(*const_folio_flags(folio, 0))); } =20 -static inline void folio_set_lru_refs(struct folio *folio, unsigned int re= fs) +/** + * __folio_set_lru_refs - Set a folio's LRU refs. + * @folio: the folio + * @refs: the new referenced count (0 .. LRU_REFS_MAX) + * + * Set the folio's LRU refs. The folio must be off the LRU list (e.g., + * isolated), or use folio_inc_lru_refs or folio_reset_lru_refs instead. + */ +static inline void __folio_set_lru_refs(struct folio *folio, unsigned int = refs) { unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); =20 do { new_flags =3D old_flags; + VM_WARN_ON_ONCE(lru_get_gen_flags(old_flags) !=3D -1); lru_set_refs_flags(&new_flags, refs); } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); } =20 #ifdef CONFIG_LRU_GEN void folio_inc_lru_refs(struct folio *folio, unsigned int flags); +bool folio_reset_lru_refs(struct folio *folio); #else static inline void folio_inc_lru_refs(struct folio *folio, unsigned int fl= ags) { /* Should not be called with !CONFIG_LRU_GEN */ WARN_ON_ONCE(1); } + +static inline bool folio_reset_lru_refs(struct folio *folio) +{ + return false; +} #endif =20 /** @@ -268,10 +283,18 @@ static inline void folio_inc_lru_refs(struct folio *f= olio, unsigned int flags) */ static inline void folio_migrate_lru_refs(struct folio *new, const struct = folio *old) { - folio_set_lru_refs(new, folio_lru_refs(old)); + VM_WARN_ON_ONCE_FOLIO(folio_test_lru(old), old); + VM_WARN_ON_ONCE_FOLIO(folio_test_lru(new), new); + __folio_set_lru_refs(new, folio_lru_refs(old)); } =20 #ifdef CONFIG_LRU_GEN +static inline bool lru_refs_is_active(unsigned int refs) +{ + VM_WARN_ON_ONCE(refs > LRU_REFS_MAX); + return refs >=3D LRU_REFS_ACTIVATED; +} + static inline bool lru_gen_at_tail(struct lruvec *lruvec, int type, int ge= n) { unsigned long min_seq =3D READ_ONCE(lruvec->lrugen.min_seq[type]); @@ -285,23 +308,12 @@ static inline bool lru_gen_at_tail(struct lruvec *lru= vec, int type, int gen) gen =3D=3D lru_gen_from_seq(min_seq + 1); } =20 -static inline bool lru_gen_is_active(const struct lruvec *lruvec, int gen) -{ - unsigned long max_seq =3D READ_ONCE(lruvec->lrugen.max_seq); - - VM_WARN_ON_ONCE(gen >=3D MAX_NR_GENS); - - /* see the comment on MIN_NR_GENS */ - return gen =3D=3D lru_gen_from_seq(max_seq) || gen =3D=3D lru_gen_from_se= q(max_seq - 1); -} - static inline void lru_gen_update_size(struct lruvec *lruvec, struct folio= *folio, int old_gen, int new_gen) { int type =3D folio_is_file_lru(folio); int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); - enum lru_list lru =3D type * LRU_INACTIVE_FILE; struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 VM_WARN_ON_ONCE(old_gen !=3D -1 && old_gen >=3D MAX_NR_GENS); @@ -312,19 +324,6 @@ static inline void lru_gen_update_size(struct lruvec *= lruvec, struct folio *foli atomic_long_sub(delta, &lrugen->nr_pages[old_gen][type][zone]); if (new_gen >=3D 0) atomic_long_add(delta, &lrugen->nr_pages[new_gen][type][zone]); - - /* return now if not a promotion */ - if (old_gen < 0 || new_gen < 0) - return; - - /* promotion */ - if (!lru_gen_is_active(lruvec, old_gen) && lru_gen_is_active(lruvec, new_= gen)) { - __update_lru_size(lruvec, lru, zone, -delta); - __update_lru_size(lruvec, lru + LRU_ACTIVE, zone, delta); - } - - /* demotion requires isolation, e.g., lru_deactivate_fn() */ - VM_WARN_ON_ONCE(lru_gen_is_active(lruvec, old_gen) && !lru_gen_is_active(= lruvec, new_gen)); } =20 static inline unsigned long lru_gen_folio_seq(const struct lruvec *lruvec, @@ -366,7 +365,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lru= vec, struct folio *folio, { unsigned long seq; unsigned long flags; - int gen =3D folio_lru_gen(folio); + int gen, refs; int type =3D folio_is_file_lru(folio); int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); @@ -374,7 +373,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lru= vec, struct folio *folio, struct lru_gen_folio *lrugen =3D &lruvec->lrugen; =20 BUILD_BUG_ON(BIT(LRU_GEN_WIDTH - 1) !=3D MAX_NR_GENS); - VM_WARN_ON_ONCE_FOLIO(gen !=3D -1, folio); + VM_WARN_ON_ONCE_FOLIO(folio_lru_gen(folio) !=3D -1, folio); =20 if (folio_test_unevictable(folio) || !lrugen->enabled) return false; @@ -383,10 +382,12 @@ static inline bool lru_gen_add_folio(struct lruvec *l= ruvec, struct folio *folio, gen =3D lru_gen_from_seq(seq); flags =3D (gen + 1UL) << LRU_GEN_PGOFF; /* see the comment on MIN_NR_GENS about PG_active */ - set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK | BIT(PG_active), flags= ); + flags =3D set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK | BIT(PG_acti= ve), flags); + /* use the refs from the atomic snapshot to avoid raced update */ + refs =3D lru_get_refs_flags(flags); =20 lru_gen_update_size(lruvec, folio, -1, gen); - if (lru_gen_is_active(lruvec, gen)) + if (lru_refs_is_active(refs)) lru +=3D LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, delta); =20 @@ -402,24 +403,31 @@ static inline bool lru_gen_add_folio(struct lruvec *l= ruvec, struct folio *folio, static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *= folio, bool reclaiming) { unsigned long flags; - int gen =3D folio_lru_gen(folio); + int gen, refs; int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); enum lru_list lru =3D folio_is_file_lru(folio) * LRU_INACTIVE_FILE; + unsigned long max_seq =3D READ_ONCE(lruvec->lrugen.max_seq); =20 + flags =3D set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK, 0); + gen =3D lru_get_gen_flags(flags); + refs =3D lru_get_refs_flags(flags); if (gen < 0) return false; =20 VM_WARN_ON_ONCE_FOLIO(folio_test_active(folio), folio); VM_WARN_ON_ONCE_FOLIO(folio_test_unevictable(folio), folio); =20 - /* for folio_migrate_flags() */ - flags =3D !reclaiming && lru_gen_is_active(lruvec, gen) ? BIT(PG_active) = : 0; - flags =3D set_mask_bits(folio_flags(folio, 0), LRU_GEN_MASK, flags); - gen =3D ((flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1; + /* + * See the comment in lru_gen_folio_seq. For migration, compaction, + * or any other isolation of a hot folio, try best to retain its gen + * info. Ideally we would keep the full gen info. + */ + if (!reclaiming && ((max_seq - gen) % MAX_NR_GENS) < MIN_NR_GENS) + folio_set_active(folio); =20 lru_gen_update_size(lruvec, folio, gen, -1); - if (lru_gen_is_active(lruvec, gen)) + if (lru_refs_is_active(refs)) lru +=3D LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, -delta); list_del(&folio->lru); @@ -470,6 +478,7 @@ static __always_inline void folio_inc_lru_refs_fast(str= uct folio *folio) return; } =20 + BUILD_BUG_ON(LRU_REFS_REFERENCED >=3D LRU_REFS_ACTIVATED); old_flags =3D READ_ONCE(*folio_flags(folio, 0)); do { if (lru_get_refs_flags(old_flags)) @@ -505,7 +514,7 @@ static inline bool folio_check_lru_refs(const struct fo= lio *folio) static inline void folio_clear_lru_refs(struct folio *folio) { if (lru_gen_enabled()) - folio_set_lru_refs(folio, 0); + folio_reset_lru_refs(folio); else folio_clear_referenced(folio); } diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index b5a3769fdcab..dacf066b499e 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -434,15 +434,15 @@ enum lruvec_flags { * least twice before handing this folio over to the eviction. The first c= heck * clears the accessed bit from the initial fault; the second check makes = sure * this folio hasn't been used since then. This process, AKA second chance, - * requires a minimum of two generations, hence MIN_NR_GENS. And to mainta= in ABI - * compatibility with the active/inactive LRU, e.g., /proc/vmstat, these t= wo - * generations are considered active; the rest of generations, if they exi= st, - * are considered inactive. See lru_gen_is_active(). + * requires a minimum of two generations, hence MIN_NR_GENS. + * + * Active/inactive is per-folio based on the referenced count; + * see the comment above MAX_NR_TIERS. * * PG_active is always cleared while a folio is on one of lrugen->folios[]= so - * that the sliding window needs not to worry about it. And it's set again= when - * a folio considered active is isolated for non-reclaiming purposes, e.g., - * migration. See lru_gen_add_folio() and lru_gen_del_folio(). + * that the sliding window needs not to worry about it. It is set on a + * fault-driven refault (see lru_gen_refault), or isolation (see + * lru_gen_del_folio). * * MAX_NR_GENS is set to 4 so that the multi-gen LRU can support twice the * number of categories of the active/inactive LRU when keeping track of @@ -543,11 +543,15 @@ enum lruvec_flags { * * A folio's referenced count never goes backwards except upon gen * increase as described above, or when explicitly reset by - * lru_gen_clear_refs(). Refault of a reclaimed folio restores + * folio_reset_lru_refs(). Refault of a reclaimed folio restores * its referenced count, capped at LRU_REFS_PROTECTED, which aligns with * promotion. Page table refaults of previous workingset folios send * them to the latest gen, driving aging faster. * + * For the active/inactive LRU ABI (/proc/vmstat), a folio is active + * if its referenced count reaches LRU_REFS_ACTIVATED. The threshold + * currently set to LRU_REFS_PROTECTED, which is tier 2. + * * MAX_NR_TIERS is set to 4 so that the multi-gen LRU can support twice * the number of categories of the active/inactive LRU. */ @@ -562,6 +566,7 @@ enum lruvec_flags { #define LRU_REFS_REFERENCED 0x1 #define LRU_REFS_WORKINGSET 0x2 #define LRU_REFS_PROTECTED 0x3 +#define LRU_REFS_ACTIVATED LRU_REFS_PROTECTED =20 #ifndef __GENERATING_BOUNDS_H =20 @@ -670,6 +675,8 @@ struct lru_gen_mm_walk { unsigned long seq; /* the next address within an mm to scan */ unsigned long next_addr; + /* to batch activated pages */ + int nr_activated[ANON_AND_FILE][MAX_NR_ZONES]; /* to batch promoted pages */ int nr_pages[MAX_NR_GENS][ANON_AND_FILE][MAX_NR_ZONES]; /* to batch the mm stats */ diff --git a/mm/folio.c b/mm/folio.c index e200efab814f..9b9a5fe568c9 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -349,35 +349,6 @@ static void __lru_cache_activate_folio(struct folio *f= olio) local_unlock(&cpu_fbatches.lock); } =20 -#ifdef CONFIG_LRU_GEN - -static bool lru_gen_clear_refs(struct folio *folio) -{ - int gen =3D folio_lru_gen(folio); - int type =3D folio_is_file_lru(folio); - unsigned long seq; - - if (gen < 0) - return true; - - folio_set_lru_refs(folio, 0); - - rcu_read_lock(); - seq =3D READ_ONCE(folio_lruvec(folio)->lrugen.min_seq[type]); - rcu_read_unlock(); - /* whether can do without shuffling under the LRU lock */ - return gen =3D=3D lru_gen_from_seq(seq); -} - -#else /* !CONFIG_LRU_GEN */ - -static bool lru_gen_clear_refs(struct folio *folio) -{ - return false; -} - -#endif /* CONFIG_LRU_GEN */ - /** * folio_mark_accessed - Mark a folio as having seen activity. * @folio: The folio to mark. @@ -554,7 +525,7 @@ static void lru_lazyfree(struct lruvec *lruvec, struct = folio *folio) lruvec_del_folio(lruvec, folio); folio_clear_active(folio); if (lru_gen_enabled()) - lru_gen_clear_refs(folio); + __folio_set_lru_refs(folio, 0); else folio_clear_referenced(folio); /* @@ -627,7 +598,7 @@ void deactivate_file_folio(struct folio *folio) if (folio_test_unevictable(folio) || !folio_test_lru(folio)) return; =20 - if (lru_gen_enabled() && lru_gen_clear_refs(folio)) + if (lru_gen_enabled() && !folio_reset_lru_refs(folio)) return; =20 folio_batch_add_and_move(folio, lru_deactivate_file); @@ -646,7 +617,7 @@ void folio_deactivate(struct folio *folio) if (folio_test_unevictable(folio) || !folio_test_lru(folio)) return; =20 - if (lru_gen_enabled() ? lru_gen_clear_refs(folio) : !folio_test_active(fo= lio)) + if (lru_gen_enabled() ? !folio_reset_lru_refs(folio) : !folio_test_active= (folio)) return; =20 folio_batch_add_and_move(folio, lru_deactivate); diff --git a/mm/madvise.c b/mm/madvise.c index 2b4a8b2cb477..184611fdd6f9 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -374,7 +374,8 @@ static void madvise_cold_prep_folio(struct folio *folio) * list should have PG_workingset so its refault is still accounted * as a workingset refault, and the referenced bit always needs to * be cleared even for inactive folios. Nothing to do for MGLRU as - * folio_deactivate() always calls lru_gen_clear_refs(). + * folio_deactivate() clears the refs count through + * folio_reset_lru_refs(). */ if (!lru_gen_enabled()) { folio_clear_referenced(folio); @@ -395,7 +396,7 @@ static void madvise_pageout_prep_folio(struct folio *fo= lio) * the workingset classification is preserved for PSI. */ if (lru_gen_enabled()) - folio_set_lru_refs(folio, refs >=3D LRU_REFS_WORKINGSET ? LRU_REFS_WORKI= NGSET : 0); + __folio_set_lru_refs(folio, refs >=3D LRU_REFS_WORKINGSET ? LRU_REFS_WOR= KINGSET : 0); } =20 static int madvise_cold_or_pageout_pte_range(pmd_t *pmd, diff --git a/mm/vmscan.c b/mm/vmscan.c index d0f0e5a25d87..617f69c1d9f9 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -977,17 +977,19 @@ enum folio_references { void folio_inc_lru_refs(struct folio *folio, unsigned int flags) { int max_gen; - int type, refs, old_gen, gen; + int file, old_refs, refs, old_gen, gen; unsigned long new_flags, old_flags, max_seq; + long nr_pages =3D folio_nr_pages(folio); struct lru_gen_folio *lrugen; struct lruvec *lruvec =3D NULL; =20 - type =3D folio_is_file_lru(folio); old_flags =3D READ_ONCE(*folio_flags(folio, 0)); do { new_flags =3D old_flags; old_gen =3D lru_get_gen_flags(old_flags); - refs =3D lru_get_refs_flags(old_flags) + 1; + old_refs =3D lru_get_refs_flags(old_flags); + file =3D folio_flags_is_file_lru(&old_flags); + refs =3D old_refs + 1; gen =3D old_gen; if (old_gen < 0) goto out; @@ -1013,14 +1015,14 @@ void folio_inc_lru_refs(struct folio *folio, unsign= ed int flags) if (refs > LRU_REFS_REFERENCED || flags & LRU_REF_EXEC) gen =3D max_gen; /* First access only defers eviction of a tail gen */ - else if (lru_gen_at_tail(lruvec, type, old_gen)) + else if (lru_gen_at_tail(lruvec, file, old_gen)) gen =3D (old_gen + 1) % MAX_NR_GENS; refs =3D min(refs, LRU_REFS_PROTECTED); } else if (refs > LRU_REFS_MAX) { /* LRU refs counting overflow, bump the gen */ gen =3D (old_gen + 1) % MAX_NR_GENS; refs =3D LRU_REFS_PROTECTED; - } else if (lru_gen_at_tail(lruvec, type, old_gen) && refs >=3D LRU_REFS_= WORKINGSET) { + } else if (lru_gen_at_tail(lruvec, file, old_gen) && refs >=3D LRU_REFS_= WORKINGSET) { /* Defer eviction of just accessed workingset */ gen =3D (old_gen + 1) % MAX_NR_GENS; refs =3D min(refs, LRU_REFS_PROTECTED); @@ -1036,54 +1038,108 @@ void folio_inc_lru_refs(struct folio *folio, unsig= ned int flags) =20 if (gen !=3D old_gen) lru_gen_update_size(lruvec, folio, old_gen, gen); + if (lru_refs_is_active(old_refs) !=3D lru_refs_is_active(refs) && old_gen= >=3D 0) { + enum lru_list lru =3D file * LRU_INACTIVE_FILE; + + __update_lru_size(lruvec, lru + lru_refs_is_active(old_refs), + folio_zonenum(folio), -nr_pages); + __update_lru_size(lruvec, lru + lru_refs_is_active(refs), + folio_zonenum(folio), nr_pages); + } if (lruvec) lruvec_unlock_irq(lruvec); } =20 +/* + * Reset the folio's lru refs indicator. The caller doesn't need to hold + * the folio lock, isolate the folio, or hold the lruvec lock. + */ +bool folio_reset_lru_refs(struct folio *folio) +{ + int type, gen, refs; + unsigned long seq, new_flags, old_flags; + long nr_pages =3D folio_nr_pages(folio); + struct lruvec *lruvec; + bool reset =3D false; + + old_flags =3D READ_ONCE(*folio_flags(folio, 0)); + do { + new_flags =3D old_flags; + refs =3D lru_get_refs_flags(old_flags); + type =3D folio_flags_is_file_lru(&old_flags); + gen =3D lru_get_gen_flags(old_flags); + if (!refs) + break; + lru_set_refs_flags(&new_flags, 0); + } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); + + if (gen >=3D 0) { + lruvec =3D folio_lruvec_live_get(folio); + + /* If the folio is on list, caller might also want to demote it */ + seq =3D READ_ONCE(lruvec->lrugen.min_seq[type]); + reset =3D (gen !=3D lru_gen_from_seq(seq)); + if (lru_refs_is_active(refs)) { + __update_lru_size(lruvec, type * LRU_FILE + LRU_ACTIVE, + folio_zonenum(folio), -nr_pages); + __update_lru_size(lruvec, type * LRU_FILE, + folio_zonenum(folio), nr_pages); + } + + folio_lruvec_live_put(lruvec); + } + + return reset; +} + /* * Update the folio's lru refs indicator during a page table walk or the * look-around. max_seq can be stale as neither holds the LRU lock. * - * Returns the old generation and stores the new generation in @new_gen if - * the folio is on the LRU and not in the newest generation, or -1 otherwi= se. + * Returns the old generation, or -1 if the folio is off the LRU list. + * Old LRU refs, and updated gen and LRU refs info by the successful + * cmpxchg are all stored by returning arguments. */ static int folio_inc_lru_refs_walk(struct folio *folio, struct lruvec *lru= vec, - const vma_flags_t *vma_flags, - int *new_gen, int *type) + const vma_flags_t *vma_flags, int *new_gen, + int *old_refs, int *new_refs, int *type) { unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); - int refs, gen, max_gen, ret; + int refs, old_gen, max_gen; unsigned long max_seq; =20 do { max_seq =3D READ_ONCE(lruvec->lrugen.max_seq); max_gen =3D lru_gen_from_seq(max_seq); - gen =3D lru_get_gen_flags(old_flags); + old_gen =3D lru_get_gen_flags(old_flags); refs =3D lru_get_refs_flags(old_flags) + 1; *type =3D folio_flags_is_file_lru(&old_flags); new_flags =3D old_flags; =20 - if (gen >=3D 0 && gen !=3D max_gen) { - ret =3D gen; + if (old_gen >=3D 0 && old_gen !=3D max_gen) { + *new_refs =3D min(refs, LRU_REFS_PROTECTED); /* Promote second page table access or executable */ if (refs > LRU_REFS_REFERENCED || is_exec_file_folio(folio, vma_flags)) *new_gen =3D max_gen; /* First access only defers eviction of a tail gen */ - else if (lru_gen_at_tail(lruvec, *type, gen)) - *new_gen =3D (gen + 1) % MAX_NR_GENS; + else if (lru_gen_at_tail(lruvec, *type, old_gen)) + *new_gen =3D (old_gen + 1) % MAX_NR_GENS; else - *new_gen =3D gen; + *new_gen =3D old_gen; lru_set_gen_flags(&new_flags, *new_gen); - lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_PROTECTED)); + lru_set_refs_flags(&new_flags, *new_refs); } else { - ret =3D -1; - lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_MAX)); + *new_gen =3D old_gen; + *new_refs =3D min(refs, LRU_REFS_MAX); + lru_set_refs_flags(&new_flags, *new_refs); } if (new_flags =3D=3D old_flags) break; } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); =20 - return ret; + *old_refs =3D lru_get_refs_flags(old_flags); + + return old_gen; } =20 /* @@ -3537,10 +3593,11 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, = struct ctrl_pos *pv) * the aging *************************************************************************= *****/ =20 -static int __folio_inc_gen(struct folio *folio, int old_gen, bool *increas= ed) +static int __folio_inc_gen(struct lruvec *lruvec, struct folio *folio, + int old_gen, bool *increased) { unsigned long new_flags, old_flags =3D READ_ONCE(*folio_flags(folio, 0)); - int refs, new_gen; + int file, refs, old_refs, new_gen; =20 do { new_gen =3D lru_get_gen_flags(old_flags); @@ -3554,11 +3611,24 @@ static int __folio_inc_gen(struct folio *folio, int= old_gen, bool *increased) =20 new_flags =3D old_flags; new_gen =3D (old_gen + 1) % MAX_NR_GENS; - refs =3D lru_get_refs_flags(old_flags); + old_refs =3D lru_get_refs_flags(old_flags); + file =3D folio_flags_is_file_lru(&old_flags); + refs =3D min(old_refs, LRU_REFS_WORKINGSET); lru_set_gen_flags(&new_flags, new_gen); - lru_set_refs_flags(&new_flags, min(refs, LRU_REFS_WORKINGSET)); + lru_set_refs_flags(&new_flags, refs); } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); =20 + /* Refs capped below PROTECTED: demote if previously active. */ + if (lru_refs_is_active(old_refs) !=3D lru_refs_is_active(refs)) { + enum lru_list lru =3D file * LRU_INACTIVE_FILE; + int nr_pages =3D folio_nr_pages(folio); + + __update_lru_size(lruvec, lru + lru_refs_is_active(old_refs), + folio_zonenum(folio), -nr_pages); + __update_lru_size(lruvec, lru + lru_refs_is_active(refs), + folio_zonenum(folio), nr_pages); + } + if (increased) *increased =3D true; return new_gen; @@ -3575,7 +3645,7 @@ static int folio_inc_gen(struct lruvec *lruvec, struc= t folio *folio) struct lru_gen_folio *lrugen =3D &lruvec->lrugen; int new_gen, old_gen =3D lru_gen_from_seq(lrugen->min_seq[type]); =20 - new_gen =3D __folio_inc_gen(folio, old_gen, &gen_increased); + new_gen =3D __folio_inc_gen(lruvec, folio, old_gen, &gen_increased); if (gen_increased) lru_gen_update_size(lruvec, folio, old_gen, new_gen); =20 @@ -3583,18 +3653,26 @@ static int folio_inc_gen(struct lruvec *lruvec, str= uct folio *folio) } =20 static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *= folio, - int old_gen, int new_gen, int type) + int old_gen, int new_gen, int old_refs, int new_refs, int type) { int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); =20 - VM_WARN_ON_ONCE(old_gen >=3D MAX_NR_GENS); - VM_WARN_ON_ONCE(new_gen >=3D MAX_NR_GENS); - - walk->batched++; + /* gen counter update */ + if (old_gen !=3D new_gen) { + VM_WARN_ON_ONCE(old_gen >=3D MAX_NR_GENS); + VM_WARN_ON_ONCE(new_gen >=3D MAX_NR_GENS); + walk->batched++; + walk->nr_pages[old_gen][type][zone] -=3D delta; + walk->nr_pages[new_gen][type][zone] +=3D delta; + } =20 - walk->nr_pages[old_gen][type][zone] -=3D delta; - walk->nr_pages[new_gen][type][zone] +=3D delta; + /* active/inactive counter update */ + if (lru_refs_is_active(old_refs) !=3D lru_refs_is_active(new_refs)) { + walk->nr_activated[type][zone] +=3D + lru_refs_is_active(new_refs) ? delta : -delta; + walk->batched++; + } } =20 static void reset_batch_size(struct lru_gen_mm_walk *walk) @@ -3606,7 +3684,6 @@ static void reset_batch_size(struct lru_gen_mm_walk *= walk) walk->batched =3D 0; =20 for_each_gen_type_zone(gen, type, zone) { - enum lru_list lru =3D type * LRU_INACTIVE_FILE; int delta =3D walk->nr_pages[gen][type][zone]; =20 if (!delta) @@ -3614,10 +3691,21 @@ static void reset_batch_size(struct lru_gen_mm_walk= *walk) =20 walk->nr_pages[gen][type][zone] =3D 0; atomic_long_add(delta, &lrugen->nr_pages[gen][type][zone]); + } =20 - if (lru_gen_is_active(lruvec, gen)) - lru +=3D LRU_ACTIVE; - __update_lru_size(lruvec, lru, zone, delta); + /* apply batched active/inactive updates */ + for (type =3D 0; type < ANON_AND_FILE; type++) { + for (zone =3D 0; zone < MAX_NR_ZONES; zone++) { + enum lru_list lru =3D type * LRU_INACTIVE_FILE; + int delta =3D walk->nr_activated[type][zone]; + + if (delta) { + __update_lru_size(lruvec, lru + LRU_ACTIVE, + zone, delta); + __update_lru_size(lruvec, lru, zone, -delta); + walk->nr_activated[type][zone] =3D 0; + } + } } =20 lruvec_unlock_irq(lruvec); @@ -3772,7 +3860,7 @@ static bool suitable_to_scan(int total, int young) static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area= _struct *vma, struct lruvec *lruvec, struct folio *folio, bool dirty) { - int new_gen, old_gen, type; + int new_gen, old_gen, old_refs, new_refs, type; unsigned int flags =3D LRU_REF_MAPPED; =20 if (!folio) @@ -3785,9 +3873,10 @@ static void walk_update_folio(struct lru_gen_mm_walk= *walk, struct vm_area_struc =20 if (walk) { old_gen =3D folio_inc_lru_refs_walk(folio, lruvec, &vma->flags, - &new_gen, &type); - if (old_gen >=3D 0 && old_gen !=3D new_gen) - update_batch_size(walk, folio, old_gen, new_gen, type); + &new_gen, &old_refs, &new_refs, &type); + if (old_gen >=3D 0) + update_batch_size(walk, folio, old_gen, new_gen, + old_refs, new_refs, type); } else { if (is_exec_file_folio(folio, &vma->flags)) flags |=3D LRU_REF_EXEC; @@ -4146,6 +4235,7 @@ static void clear_mm_walk(void) =20 VM_WARN_ON_ONCE(walk && memchr_inv(walk->nr_pages, 0, sizeof(walk->nr_pag= es))); VM_WARN_ON_ONCE(walk && memchr_inv(walk->mm_stats, 0, sizeof(walk->mm_sta= ts))); + VM_WARN_ON_ONCE(walk && memchr_inv(walk->nr_activated, 0, sizeof(walk->nr= _activated))); =20 current->reclaim_state->mm_walk =3D NULL; =20 @@ -4184,8 +4274,6 @@ static bool inc_min_seq(struct lruvec *lruvec, int ty= pe, int swappiness) goto done; =20 VM_WARN_ON_ONCE(get_nr_gens(lruvec, type) !=3D MAX_NR_GENS); - VM_WARN_ON_ONCE(lru_gen_is_active(lruvec, old_gen) !=3D - lru_gen_is_active(lruvec, target_gen)); /* prevent cold/hot inversion if the type is evictable */ for (zone =3D 0; zone < MAX_NR_ZONES; zone++) { struct list_head *target_list =3D &lrugen->folios[target_gen][type][zone= ]; @@ -4208,7 +4296,7 @@ static bool inc_min_seq(struct lruvec *lruvec, int ty= pe, int swappiness) =20 prefetchw_next_lru_folio(folio, head); pos =3D pos->next; - new_gen =3D __folio_inc_gen(folio, old_gen, &gen_increased); + new_gen =3D __folio_inc_gen(lruvec, folio, old_gen, &gen_increased); /* * If gen_increased is false, this is a promotion. Put folios * at the head of the promoted gen. Otherwise, put them at @@ -4295,8 +4383,7 @@ static void try_to_inc_min_seq(struct lruvec *lruvec,= int swappiness) static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swap= piness) { bool success; - int prev, next; - int type, zone; + int type, next; struct lru_gen_folio *lrugen =3D &lruvec->lrugen; restart: if (seq < READ_ONCE(lrugen->max_seq)) @@ -4322,32 +4409,10 @@ static bool inc_max_seq(struct lruvec *lruvec, unsi= gned long seq, int swappiness goto restart; } =20 - /* - * Update the active/inactive LRU sizes for compatibility. Both sides of - * the current max_seq need to be covered, since max_seq+1 can overlap - * with min_seq[LRU_GEN_ANON] if swapping is constrained. And if they do - * overlap, cold/hot inversion happens. - */ - prev =3D lru_gen_from_seq(lrugen->max_seq - 1); - next =3D lru_gen_from_seq(lrugen->max_seq + 1); - - for (type =3D 0; type < ANON_AND_FILE; type++) { - for (zone =3D 0; zone < MAX_NR_ZONES; zone++) { - enum lru_list lru =3D type * LRU_INACTIVE_FILE; - long delta =3D atomic_long_read(&lrugen->nr_pages[prev][type][zone]) - - atomic_long_read(&lrugen->nr_pages[next][type][zone]); - - if (!delta) - continue; - - __update_lru_size(lruvec, lru, zone, delta); - __update_lru_size(lruvec, lru + LRU_ACTIVE, zone, -delta); - } - } - for (type =3D 0; type < ANON_AND_FILE; type++) reset_ctrl_pos(lruvec, type, false); =20 + next =3D lru_gen_from_seq(lrugen->max_seq + 1); WRITE_ONCE(lrugen->timestamps[next], jiffies); /* make sure preceding modifications appear */ smp_store_release(&lrugen->max_seq, lrugen->max_seq + 1); @@ -4866,7 +4931,6 @@ static void __lru_gen_reparent_memcg(struct lruvec *c= hild_lruvec, struct lruvec int zone, int type) { struct lru_gen_folio *child_lrugen, *parent_lrugen; - enum lru_list lru =3D type * LRU_INACTIVE_FILE; int i; =20 child_lrugen =3D &child_lruvec->lrugen; @@ -4875,8 +4939,6 @@ static void __lru_gen_reparent_memcg(struct lruvec *c= hild_lruvec, struct lruvec for (i =3D 0; i < get_nr_gens(child_lruvec, type); i++) { int gen =3D lru_gen_from_seq(child_lrugen->max_seq - i); long nr_pages =3D atomic_long_read(&child_lrugen->nr_pages[gen][type][zo= ne]); - int child_lru_active =3D lru_gen_is_active(child_lruvec, gen) ? LRU_ACTI= VE : 0; - int parent_lru_active =3D lru_gen_is_active(parent_lruvec, gen) ? LRU_AC= TIVE : 0; =20 /* Assuming that child pages are colder than parent pages */ list_splice_tail_init(&child_lrugen->folios[gen][type][zone], @@ -4884,11 +4946,6 @@ static void __lru_gen_reparent_memcg(struct lruvec *= child_lruvec, struct lruvec =20 atomic_long_set(&child_lrugen->nr_pages[gen][type][zone], 0); atomic_long_add(nr_pages, &parent_lrugen->nr_pages[gen][type][zone]); - - if (lru_gen_is_active(child_lruvec, gen) !=3D lru_gen_is_active(parent_l= ruvec, gen)) { - __update_lru_size(child_lruvec, lru + child_lru_active, zone, -nr_pages= ); - __update_lru_size(parent_lruvec, lru + parent_lru_active, zone, nr_page= s); - } } } =20 @@ -5228,7 +5285,7 @@ static int evict_folios(unsigned long nr_to_scan, str= uct lruvec *lruvec, * its first page table access lands in the second newest one. * See "Referenced count feedback" above. */ - folio_set_lru_refs(folio, min(folio_lru_refs(folio), LRU_REFS_WORKINGSET= )); + __folio_set_lru_refs(folio, min(folio_lru_refs(folio), LRU_REFS_WORKINGS= ET)); if (lru_gen_folio_seq(lruvec, folio, false) =3D=3D min_seq[type]) folio_set_active(folio); } diff --git a/mm/workingset.c b/mm/workingset.c index 16cdd88e275e..7681af535ca7 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -334,7 +334,7 @@ static void lru_gen_refault(struct folio *folio, void *= shadow) mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + type, delta); } /* Refault is also promotion, cap the refs like folio_inc_lru_refs */ - folio_set_lru_refs(folio, min(refs, LRU_REFS_PROTECTED)); + __folio_set_lru_refs(folio, min(refs, LRU_REFS_PROTECTED)); } =20 if (refs >=3D LRU_REFS_WORKINGSET) --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 5FD76521207; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=JbosNwTh33CTXO2DNf7gu3CWDQZueddt4GjRdfmhSrfuOdXJqXZzt1KqzTthORUz+2IkaxozSy5DlUeTKMs+5cZ5QbEWC7MNJgatOudp3N9rq3TCN364+/DRPXJIZ2lPGKyFuxREOR53UlewHhtDb2PSzANquCMxALU3HmieGJk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=bRT9KZu8UqdKr2cNHa1Z0K3O61FAuTcx+Pq/huomZeM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=fUZFB5iQUsFYNT184uZmNfUHKOYPkxXirVW9GuPn9v9a5x1+FkhaDbWB8fKeygO/NdN9oEKJKAl6OINlqm1WOGlCrYFjeRqMUD7ta9MpmmcjM4Ik8qXw3R/D1QXJIGw5q0/cOJmWT7H8t1Z8pJmkBBuUVUvHwEPUqmxYipLrY0c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=tJzwFHOT; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="tJzwFHOT" Received: by smtp.kernel.org (Postfix) with ESMTPS id 3A1F9C2BCF5; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=bRT9KZu8UqdKr2cNHa1Z0K3O61FAuTcx+Pq/huomZeM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=tJzwFHOT8zBTg8zoFzjOk6Du5AKu/DQQxp4Xq+H0Z9puNGp3isAAG1RYSWsxSZ1Ka /dzmlU8W6x0kQrq5VkrdB/5ZOzEJTPU8LrKOpLVDZ80ZQI+UeYvFDxtswRKxF3SuX6 0d0E8ljHQrZvSZPs+dvXnPDjpoEgm+c1x4R43Tb1ayi/6CDnEf2ZGRKRIJ+wXj0eAt D5JvB38Epn6iRspp61XpShT8/Fh1DpOiMDD5+I0kHXi3t+wwuginwYrsGAW6sFOR8b YrejTtStE6doR81Nln2DNTr5WRlqzO8kgl4I7+75inzfeg5/etYlvNlqfawOk6GUqg MD6brIBrPFJqA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 28BB7CCA467; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:03 +0800 Subject: [PATCH v4 14/17] mm/mglru: reparent folios from all generations 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-14-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=1313; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=pdKvbjCwV47KGicri13lBUDrCkRk2q0xdMVXETA4zbM=; b=k/ubaRAqYgu0d5sqic7HAzaAbUulfQUwwP8R/2btRLVmVV9Nfb3namLyzH80c0wrc7heLL/QW ogSO2eylyVUAj7poqU5bzgEc3/spqODjrIxSkgSteeV3MFWumHGgPBC X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song A lockless promotion racing with aging can reuse a generation index, see folio_inc_lru_refs(). The retry repairs the folio flags, but sort_folio() may have already moved the folio to the list of a generation outside the [min_seq, max_seq] window. __lru_gen_reparent_memcg() only splices the lists inside the child's window, so such a folio would be left on a list of the dying memcg. Splice the lists and move the counters of all MAX_NR_GENS generations instead. Those outside the window are empty in the common case. Assisted-by: LLM Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- mm/vmscan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index 617f69c1d9f9..a9c224377234 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4936,7 +4936,8 @@ static void __lru_gen_reparent_memcg(struct lruvec *c= hild_lruvec, struct lruvec child_lrugen =3D &child_lruvec->lrugen; parent_lrugen =3D &parent_lruvec->lrugen; =20 - for (i =3D 0; i < get_nr_gens(child_lruvec, type); i++) { + /* Lockless promotion may leave folios outside the child's window */ + for (i =3D 0; i < MAX_NR_GENS; i++) { int gen =3D lru_gen_from_seq(child_lrugen->max_seq - i); long nr_pages =3D atomic_long_read(&child_lrugen->nr_pages[gen][type][zo= ne]); =20 --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 789E056853B; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=Niud19qnYLz4yfB9zyDsJZcjlz4uiIo4zHB4W6Q8Q5A2PzduaV1DU6ON1S6+mBCKAMym3fiXWxiHzTYAo5/gCxdG13TjiBWFG6llEKmUmCOZbyWIJeq9UknaCXcN1fnSIIDbPshd4ypmq2996kV20VECZCiXhWErpwvkWVT3ZjI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=rFRURW/PYDXky6uYP0rMZlZedhJeCXhr2ivNXilJ7Dg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=CqHuI3LAEn+zYQNekB91Pd3Q0zmDKyF9Rs3iCbM6LnKrtJBs5rqKWqp5PfPvnzwDXpj+HnLohLCKrGrKLbgtR0nBIBaXFH8MxF8qqYKSuhPWR/NMc8zCrv/LpdirTajUOJc1FWyHhEbZri8oj50KSRI9Hak+9rYJxGUvMyHC2jM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=c3oZNbx3; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="c3oZNbx3" Received: by smtp.kernel.org (Postfix) with ESMTPS id 512DBC32782; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=rFRURW/PYDXky6uYP0rMZlZedhJeCXhr2ivNXilJ7Dg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=c3oZNbx35iFJaJYyQQzfTVwfSGyS6ZmAWVL5L22BTt1nigZ3j5j2Bak2YZddPKajk AUBTSEjXvry+IT/HzjjwtgpwoymrZYVYUiHN9aZyDao+x1Y87YI99bx5Dkl+MqLfTR asM5RByEamNtlrAYd659OQuFhP3cECgnSihEolkvEsBzT1Q84WSMRby9vHdti5giJT AsKECMG7SnYDLXfEsLBLb6KXrtxR/dG4qCU6gGxSTkVKSNE04t2+9wjnC5FtSh0wWY zK3pCgZAEGLSMDne44XqlQCVPJIhfVgIoDyT1oStoGdgmn8NlYumM1zirlcYsCyHSn rUGwQCI/xxLhQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3BA4CCCA464; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:04 +0800 Subject: [PATCH v4 15/17] mm/mglru: make folio_inc_lru_refs lruvec lockless 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-15-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=8176; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=OP0akykE/qzlFerevSA96IrPzDmEgd2usDkP1ftKRMM=; b=orSajrE9bbuxD//LSvEthgkATHbgp1nA0A44mNSIzqup47QSbfW8puy/w5H7buxcJScuNX5St 9sHzgmv0w4NAp65DSJmbKz7VLv8d1/gTNaUlmmJ7K5CfygKrmXm2Mo6 X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song The lruvec spinlock in folio_inc_lru_refs was only taken to keep concurrent aging from corrupting the size counters. This is no longer needed: the per-generation counters are atomic, and the active/inactive ABI counters are updated per folio and linearized by the CAS on the folio flags, so concurrent aging can no longer corrupt them. The same argument already allowed folio_reset_lru_refs() to drop the lock. Use folio_lruvec_live_get()/folio_lruvec_live_put() to hold the RCU read lock around the lruvec lookup, and drop the spinlock entirely. Without the lock, aging can advance max_seq while a promotion is in flight. Generations are indexed by seq % MAX_NR_GENS, so the folio flags can return to the value read earlier and the cmpxchg succeeds with a target gen computed from a stale max_seq. Recheck max_seq after a gen-changing cmpxchg: if it moved, account the committed update and retry with LRU_REF_FORCE, which promotes the folio to the newest gen. The retry may count the access twice, which is acceptable and avoids folio_activate(), whose LRU lock causes latency jitter. smp_rmb() orders the flags read, including the one returned by a failed cmpxchg(), before the max_seq read. Also simplify the post-CAS accounting guards: gen is only assigned non-negative values after the old_gen < 0 early exit, so gen !=3D old_gen implies gen >=3D 0, and lru_gen_update_size() needs no explicit guard. The active/inactive ABI update keeps its gen >=3D 0 check because an off-LRU folio has no lruvec to update. Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- include/linux/mm_inline.h | 10 ++++----- include/linux/mmzone.h | 3 ++- mm/vmscan.c | 53 ++++++++++++++++++++++++++++++-------------= ---- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index 41c2e9150c70..fd77dbfdaf70 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -308,10 +308,9 @@ static inline bool lru_gen_at_tail(struct lruvec *lruv= ec, int type, int gen) gen =3D=3D lru_gen_from_seq(min_seq + 1); } =20 -static inline void lru_gen_update_size(struct lruvec *lruvec, struct folio= *folio, - int old_gen, int new_gen) +static inline void lru_gen_update_size(struct lruvec *lruvec, int type, + struct folio *folio, int old_gen, int new_gen) { - int type =3D folio_is_file_lru(folio); int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); struct lru_gen_folio *lrugen =3D &lruvec->lrugen; @@ -386,7 +385,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lru= vec, struct folio *folio, /* use the refs from the atomic snapshot to avoid raced update */ refs =3D lru_get_refs_flags(flags); =20 - lru_gen_update_size(lruvec, folio, -1, gen); + lru_gen_update_size(lruvec, type, folio, -1, gen); if (lru_refs_is_active(refs)) lru +=3D LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, delta); @@ -404,6 +403,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lru= vec, struct folio *folio, { unsigned long flags; int gen, refs; + int type =3D folio_is_file_lru(folio); int zone =3D folio_zonenum(folio); int delta =3D folio_nr_pages(folio); enum lru_list lru =3D folio_is_file_lru(folio) * LRU_INACTIVE_FILE; @@ -426,7 +426,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lru= vec, struct folio *folio, if (!reclaiming && ((max_seq - gen) % MAX_NR_GENS) < MIN_NR_GENS) folio_set_active(folio); =20 - lru_gen_update_size(lruvec, folio, gen, -1); + lru_gen_update_size(lruvec, type, folio, gen, -1); if (lru_refs_is_active(refs)) lru +=3D LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, -delta); diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index dacf066b499e..f50449d10242 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -559,9 +559,10 @@ enum lruvec_flags { #define LRU_TIER_MIN 0U #define LRU_TIER_MAX (MAX_NR_TIERS - 1) =20 -/* Access source flags for folio_inc_lru_refs() */ +/* Flags for folio_inc_lru_refs() */ #define LRU_REF_MAPPED 0x1U #define LRU_REF_EXEC 0x2U +#define LRU_REF_FORCE 0x4U =20 #define LRU_REFS_REFERENCED 0x1 #define LRU_REFS_WORKINGSET 0x2 diff --git a/mm/vmscan.c b/mm/vmscan.c index a9c224377234..e604b0893847 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -966,6 +966,9 @@ enum folio_references { * LRU_REFS_PROTECTED, so it starts at or below the protected tier regardl= ess * of its old-generation access history. PID protection (folio_inc_gen) c= aps * at LRU_REFS_WORKINGSET independently. + * + * folio_inc_lru_refs() retries with LRU_REF_FORCE if the aging recycled t= he + * gen index it was about to store: newest gen and an extra reference. */ =20 /* @@ -982,37 +985,35 @@ void folio_inc_lru_refs(struct folio *folio, unsigned= int flags) long nr_pages =3D folio_nr_pages(folio); struct lru_gen_folio *lrugen; struct lruvec *lruvec =3D NULL; + bool aged =3D false; =20 +retry: old_flags =3D READ_ONCE(*folio_flags(folio, 0)); do { new_flags =3D old_flags; old_gen =3D lru_get_gen_flags(old_flags); old_refs =3D lru_get_refs_flags(old_flags); - file =3D folio_flags_is_file_lru(&old_flags); refs =3D old_refs + 1; gen =3D old_gen; + file =3D folio_flags_is_file_lru(&old_flags); if (old_gen < 0) goto out; - /* - * Lock the lruvec if the folio is on-list. We are already - * doing lazy promotion so in theory we don't need this, - * but for now, concurrent aging would still corrupt the - * size counters. This is a temporary limitation and - * will be lifted very soon, so the lock here is not a - * performance concern. - */ if (!lruvec) { - lruvec =3D lruvec_live_lock_irq(folio_lruvec(folio)); + lruvec =3D folio_lruvec_live_get(folio); lrugen =3D &lruvec->lrugen; } + /* + * Read the flags before max_seq, paired with the store of a + * gen, which samples max_seq first. + */ + smp_rmb(); max_seq =3D READ_ONCE(lrugen->max_seq); max_gen =3D lru_gen_from_seq(max_seq); if (old_gen =3D=3D max_gen) goto out; - - if (flags & (LRU_REF_MAPPED | LRU_REF_EXEC)) { - /* Promote second page table access or executable */ - if (refs > LRU_REFS_REFERENCED || flags & LRU_REF_EXEC) + if (flags & (LRU_REF_MAPPED | LRU_REF_EXEC | LRU_REF_FORCE)) { + if (refs > LRU_REFS_REFERENCED || + flags & (LRU_REF_EXEC | LRU_REF_FORCE)) gen =3D max_gen; /* First access only defers eviction of a tail gen */ else if (lru_gen_at_tail(lruvec, file, old_gen)) @@ -1036,9 +1037,19 @@ void folio_inc_lru_refs(struct folio *folio, unsigne= d int flags) break; } while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags)); =20 - if (gen !=3D old_gen) - lru_gen_update_size(lruvec, folio, old_gen, gen); - if (lru_refs_is_active(old_refs) !=3D lru_refs_is_active(refs) && old_gen= >=3D 0) { + if (gen !=3D old_gen) { + /* + * Gen-index reuse can fool cmpxchg(). Retry locklessly, accepting + * an extra reference to avoid folio_activate() latency. + */ + if (unlikely(READ_ONCE(lrugen->max_seq) !=3D max_seq)) { + flags =3D LRU_REF_FORCE; + aged =3D true; + } + lru_gen_update_size(lruvec, file, folio, old_gen, gen); + } + + if (lru_refs_is_active(old_refs) !=3D lru_refs_is_active(refs) && gen >= =3D 0) { enum lru_list lru =3D file * LRU_INACTIVE_FILE; =20 __update_lru_size(lruvec, lru + lru_refs_is_active(old_refs), @@ -1046,8 +1057,12 @@ void folio_inc_lru_refs(struct folio *folio, unsigne= d int flags) __update_lru_size(lruvec, lru + lru_refs_is_active(refs), folio_zonenum(folio), nr_pages); } + if (aged) { + aged =3D false; + goto retry; + } if (lruvec) - lruvec_unlock_irq(lruvec); + folio_lruvec_live_put(lruvec); } =20 /* @@ -3647,7 +3662,7 @@ static int folio_inc_gen(struct lruvec *lruvec, struc= t folio *folio) =20 new_gen =3D __folio_inc_gen(lruvec, folio, old_gen, &gen_increased); if (gen_increased) - lru_gen_update_size(lruvec, folio, old_gen, new_gen); + lru_gen_update_size(lruvec, type, folio, old_gen, new_gen); =20 return new_gen; } --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 881E1572680; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=Dy+WWBxVvkfKXX1ocVdQX8mEyfjGo9XVLvqk32S26XOYer0YtJ84YJ6bn9YaxCSRcI9EbYdvxbUVbr15f/2WdJV4sJd8C1V6lbQi96didtZRwARjnlgdYuJUJn5Ur7P+QBoxXdLzvDHVicwtVsh43k/ER4mQfJSLOrGGCuhLPkU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=8cyXuXR2bejdM1i48YPVGkhrLP7RRhUB+l6kEr3xsS4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TjdFZ4BZFOd5KE1xbr2qexaoa6DzulmcQoylB+WGG3LNkp0wbGcX2ZOr/Txl/wpmDAigT0Cmeds467e8mSSOZZpO56uIf941Zj+qvQeIHZbrv7RstBTD4nW8D2sUVp1qnVvBPXfCRAWZtjIPj3aVdVZ74fn/ngZoUM38jtiHc/Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ht2xkvIU; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ht2xkvIU" Received: by smtp.kernel.org (Postfix) with ESMTPS id 64E3DC2BD00; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=8cyXuXR2bejdM1i48YPVGkhrLP7RRhUB+l6kEr3xsS4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ht2xkvIU+2pFwdRFiiZT67dWqOOU2NbdtsTsO+mcr6ItXLYKkzOHivvV4uEEy75Wc UGi5DH10Vmf9Y/Afs/L9gQVbTE8gTNbNOnd1Bkkpt1KCwc/vTD/Fze9lf15isUhPkl prNZng0dfw7jqdCaH/oxyB2rYWijJLoB0vZaxRnc5j8eaO4KoVDajiz4bFa3V1F+ek 6OEOTh1cfSXp8RkWl4DUYSFThxsJp0J/tMcac42qQWNGliC8y4ZgCJFLQkuef+tGR7 tMneffwZpHlQ3yWFGnuqGEhOWv3BUdRM/p4ZZPjie1XvTF8+Ed4Xk7epcisS4lmUXL CFteevLki/ZeA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 50D3DCCA465; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:05 +0800 Subject: [PATCH v4 16/17] mm/mglru: make folio_test_workingset() work based on folio LRU refs 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-16-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=5718; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=NZRuMZ9DHJqarWmmM6IMbKyXzp6luS4trNdMObpwCLI=; b=J/5V4RHF/wLROW7MgcSEu29RNBNNJPH06zH+vQfEGTpFxED4h+RDJibV/v5icelk6NMtSB6tg iCohZpXYNmiD81Dr07ISj0ys5x5+g+FdQy/Z62fe2TRmx53BTT8gB4+ X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song folio_test_workingset() currently tests the PG_workingset bit, which is only the second lowest access bit of the folio LRU refs count now. Folios with a higher refs count carry it in the LRU_REFS_MASK field and no longer have PG_workingset set, so the bit test misses them. folio_set_workingset() currently sets the PG_workingset bit with a plain set_bit(), which also over-promotes folios at a higher refs count: e.g. a folio at refs 4 is bumped to refs 6, advancing it toward LRU_REFS_MAX and an unintended generation promotion on the next access. Move the test into mm_inline.h as an inline helper based on folio_lru_refs(), checking refs >=3D LRU_REFS_WORKINGSET. The set stays a plain PG_workingset bit operation reserved for the classical LRU: under MGLRU the refs count is maintained by folio_inc_lru_refs(), and now triggers a debug WARN when called while MGLRU is fully on (the switching window is exempt, as the classical paths legitimately run alongside MGLRU then). The one PageWorkingset() user in erofs is converted to the folio helper. Under the classical LRU the refs count is not maintained, so the test falls back to the PG_workingset bit test, which is the old behavior. Tested-by: zhaozhengzhuo Signed-off-by: Kairui Song --- fs/btrfs/compression.c | 1 + fs/erofs/zdata.c | 3 ++- include/linux/mm_inline.h | 29 +++++++++++++++++++++++++++++ include/linux/page-flags.h | 2 -- mm/filemap.c | 1 + mm/page_io.c | 1 + 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index c62b5148d5ac..57d24413265e 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c index e981e371d6c2..34ee41e1ccdc 100644 --- a/fs/erofs/zdata.c +++ b/fs/erofs/zdata.c @@ -6,6 +6,7 @@ */ #include "compress.h" #include +#include #include #include =20 @@ -1713,7 +1714,7 @@ static void z_erofs_submit_queue(struct z_erofs_front= end *f, DBG_BUGON(bvec.bv_len < sb->s_blocksize); } =20 - if (unlikely(PageWorkingset(bvec.bv_page)) && + if (unlikely(folio_test_workingset(page_folio(bvec.bv_page))) && !memstall) { psi_memstall_enter(&pflags); memstall =3D 1; diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index fd77dbfdaf70..ae50f9880da8 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -288,6 +288,23 @@ static inline void folio_migrate_lru_refs(struct folio= *new, const struct folio __folio_set_lru_refs(new, folio_lru_refs(old)); } =20 +/** + * folio_test_workingset - Test if a folio is in the workingset. + * @folio: the folio + * + * A folio is workingset when its LRU refs count reaches + * LRU_REFS_WORKINGSET. Under the classical LRU the extra refs counter + * are always zero so this is just testing the PG_workingset bit. + * NOTE: folio_set_workingset() must not be used under MGLRU, as the + * folio refs are tracked by folio_inc_lru_refs() with rules. + * + * Return: true if the folio is workingset. + */ +static __always_inline bool folio_test_workingset(const struct folio *foli= o) +{ + return folio_lru_refs(folio) >=3D LRU_REFS_WORKINGSET; +} + #ifdef CONFIG_LRU_GEN static inline bool lru_refs_is_active(unsigned int refs) { @@ -519,6 +536,18 @@ static inline void folio_clear_lru_refs(struct folio *= folio) folio_clear_referenced(folio); } =20 +/* + * For the classical LRU only: with MGLRU the PG_workingset bit is + * part of the folio refs count maintained by folio_inc_lru_refs(), + * and a raw set would corrupt it. The switching window is exempt + * because the classical paths legitimately run alongside MGLRU then. + */ +static __always_inline void folio_set_workingset(struct folio *folio) +{ + VM_WARN_ON_ONCE(lru_gen_enabled() && !lru_gen_switching()); + set_bit(PG_workingset, folio_flags(folio, FOLIO_HEAD_PAGE)); +} + static __always_inline void lruvec_add_folio(struct lruvec *lruvec, struct folio *folio) { diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index b0ddc652e76c..f6625dc042da 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -549,8 +549,6 @@ PAGEFLAG(LRU, lru, PF_HEAD) __CLEARPAGEFLAG(LRU, lru, P= F_HEAD) FOLIO_FLAG(active, FOLIO_HEAD_PAGE) __FOLIO_CLEAR_FLAG(active, FOLIO_HEAD_PAGE) FOLIO_TEST_CLEAR_FLAG(active, FOLIO_HEAD_PAGE) -PAGEFLAG(Workingset, workingset, PF_HEAD) - TESTCLEARFLAG(Workingset, workingset, PF_HEAD) PAGEFLAG(Checked, checked, PF_NO_COMPOUND) /* Used by some filesystems = */ =20 /* Xen */ diff --git a/mm/filemap.c b/mm/filemap.c index b74bc1e5015c..49e827058e14 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/mm/page_io.c b/mm/page_io.c index c6824fcd483e..540ae8caa932 100644 --- a/mm/page_io.c +++ b/mm/page_io.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include --=20 2.56.0 From nobody Fri Oct 23 03:30:57 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 9865A572682; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; cv=none; b=Cg1LUAooXAkRiKChgsOg6zwqY8enaLI9bFTvquJC07fOlcsWxw5scyUkN2h6CXfCwqcvaWWlBFOB2SVm3a074JakJgMDA0Ch6Fnw6jFvL4cQyg6CzsaoUYkQYA/bIbdXkNXqW6nSYyTfPpRtNhng3/pOPwwKBhp9v5licvz8rXw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792088156; c=relaxed/simple; bh=9vYadqVkh4WrhDMsH1jX5lN/XH9JwLR+fwEjQqvXr+8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tnkL1029knAPoPnZlJzmEXTXGJ18dV7fCIydBk1BC4yIFnjNhkStF+Y8dNMN/+YUbhA+HLdbHJRJQRXr7Zqj8dHloLJyZiSpS4NrVc8ccYum2tKn4yKaW7+ahlnBaHapyuUv+4aF9FLT/93Fyq4p9SJGhK0ofrxZJOibOthGJXk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=M/VSqVM9; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="M/VSqVM9" Received: by smtp.kernel.org (Postfix) with ESMTPS id 76DCAC2BD05; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1792088156; bh=9vYadqVkh4WrhDMsH1jX5lN/XH9JwLR+fwEjQqvXr+8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=M/VSqVM9jJUGAMm2rmzaGRTlfA1KecX+avNavtRyTAfdlsSBYSzUgFtLcsq4XD3aS wU3RIHerSYc+iW5yiW8PxYF4E+++seprkzUMoLXA8hCUq7GBle9n5VW6wY24yxhG+q gteyIE0lOqQYcw6X87lY46GlqqfRSA3DT74VvWOpjCksfA6vqR4uHuEyx4soGNYKZ4 JNkUdGDRr+mFbPMImkog47DofQrw9N2LmqNU43+r5RpvKJp6331Xgq8kX5EgKDqqsj W0V3LUjXEeSDEWXPtMem3RwzrwPbPJ3xaPggetVITO0wHJn9957FMFd20Gz9q96B2M LTvlP1bXbY35g== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 63007CC6B38; Thu, 15 Oct 2026 18:15:56 +0000 (UTC) From: Kairui Song via B4 Relay Date: Fri, 16 Oct 2026 02:16:06 +0800 Subject: [PATCH v4 17/17] Documentation/mm: multi-gen LRU: update for frequency guided promotion 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-Transfer-Encoding: quoted-printable Message-Id: <20261016-mglru-fg-v4-17-2106750c0bfa@tencent.com> References: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com> To: linux-mm@kvack.org, "Liam R. Howlett" , SJ Park Cc: Andrew Morton , Johannes Weiner , Muchun Song , Qi Zheng , Ying Huang , Chris Li , Baoquan He , Nico Pache , Usama Arif , Michal Hocko , Roman Gushchin , Shakeel Butt , David Hildenbrand , Lorenzo Stoakes , Barry Song , Axel Rasmussen , Yuanchu Xie , Wei Xu , Vlastimil Babka , Suren Baghdasaryan , Kemeng Shi , Nhat Pham , Youngjun Park , Zi Yan , Gregory Price , "Matthew Wilcox (Oracle)" , Baolin Wang , Ryan Roberts , Dev Jain , Lance Yang , Hugh Dickins , David Rientjes , Yu Zhao , Vernon Yang , Zicheng Wang , Chen Ridong , Tal Zussman , Kairui Song , Xiang Liu , Ehab Ababneh , zhaozhengzhuo , Lian Wang , Kunwu Chan , linux-kernel@vger.kernel.org, cgroups@vger.kernel.org, damon@lists.linux.dev, Kairui Song X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1792088152; l=5953; i=kasong@tencent.com; s=kasong-sign-tencent; h=from:subject:message-id; bh=H9ePyPxQfKEyJG8pjQryA22Bu9URRS7jnmN2RCV298Y=; b=Vb9KmjNNstGoNvaMOwNk7ofyjEq/gP8OVY3K9CwFsX4iWIUxqEgFFz0bvwWvH+Y2p0iByDKlP wZqWyoquQutCr8DLLctzG4RU7bhi8F6VKDcrlaEAi+LHGq3mAiOnAnb X-Developer-Key: i=kasong@tencent.com; a=ed25519; pk=kCdoBuwrYph+KrkJnrr7Sm1pwwhGDdZKcKrqiK8Y1mI= X-Endpoint-Received: by B4 Relay for kasong@tencent.com/kasong-sign-tencent with auth_id=562 X-Original-From: Kairui Song Reply-To: kasong@tencent.com From: Kairui Song The design doc still describes the old tier and promotion model: - Tiers are no longer order_base_2() of the file descriptor accesses. A folio's tier is derived from its referenced count, which counts accesses from both page tables and file descriptors. - A young PTE no longer always promotes a folio to the newest generation. The first access only moves a folio one generation forward if it is at the tail of its LRU, i.e., in one of the two oldest generations of its type, a second access promotes it, and executable file folios are promoted on their first access. - The eviction no longer uses the first tier as the only baseline. The type is selected by comparing all tiers of each type, and a tier is protected by comparing it against the tiers below it. Update the aging and eviction sections accordingly. Assisted-by: LLM Signed-off-by: Kairui Song Tested-by: zhaozhengzhuo --- Documentation/mm/multigen_lru.rst | 57 ++++++++++++++++++++++-------------= ---- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Documentation/mm/multigen_lru.rst b/Documentation/mm/multigen_= lru.rst index 52ed5092022f..ec8744c1f42c 100644 --- a/Documentation/mm/multigen_lru.rst +++ b/Documentation/mm/multigen_lru.rst @@ -95,16 +95,19 @@ at most ``MAX_NR_GENS`` generations. The gen counter st= ores a value within ``[1, MAX_NR_GENS]`` while a page is on one of ``lrugen->folios[]``; otherwise it stores zero. =20 -Each generation is divided into multiple tiers. A page accessed ``N`` -times through file descriptors is in tier ``order_base_2(N)``. Unlike -generations, tiers do not have dedicated ``lrugen->folios[]``. In -contrast to moving across generations, which requires the LRU lock, -moving across tiers only involves atomic operations on -``folio->flags`` and therefore has a negligible cost. A feedback loop -modeled after the PID controller monitors refaults over all the tiers -from anon and file types and decides which tiers from which types to -evict or protect. The desired effect is to balance refault percentages -between anon and file types proportional to the swappiness level. +Each generation is divided into multiple tiers. A page's tier is +derived from its referenced count, which counts the accesses collected +from both channels above and is capped at ``LRU_REFS_MAX``: pages +accessed at most once are in the lowest tier, and the tiers above hold +pages accessed at least twice. Unlike generations, tiers do not have +dedicated ``lrugen->folios[]``. In contrast to moving across +generations, which requires the LRU lock, updating the referenced +count only involves atomic operations on ``folio->flags`` and +therefore has a negligible cost. A feedback loop modeled after the PID +controller monitors refaults over all the tiers from anon and file +types and decides which tiers from which types to evict or protect. +The desired effect is to balance refault percentages between anon and +file types proportional to the swappiness level. =20 There are two conceptually independent procedures: the aging and the eviction. They form a closed-loop system, i.e., the page reclaim. @@ -122,25 +125,31 @@ and calls ``walk_page_range()`` with each ``mm_struct= `` on this list to scan PTEs, and after each iteration, it increments ``max_seq``. For the latter, when the eviction walks the rmap and finds a young PTE, the aging scans the adjacent PTEs. For both, on finding a young PTE, -the aging clears the accessed bit and updates the gen counter of the -page mapped by this PTE to ``(max_seq%MAX_NR_GENS)+1``. +the aging clears the accessed bit and bumps the referenced count of +the page mapped by this PTE. A second access promotes the page by +updating its gen counter to ``(max_seq%MAX_NR_GENS)+1``; the first +access only moves a page one generation forward if it is at the tail +of its LRU, i.e., in one of the two oldest generations of its type, +so that the next time it becomes the oldest one, its accessed bit +reflects whether it has been used again. Executable file pages are +promoted on their first access, since reclaiming them causes typical +I/O thrashing. =20 Eviction -------- The eviction consumes old generations. Given an ``lruvec``, it increments ``min_seq`` when ``lrugen->folios[]`` indexed by -``min_seq%MAX_NR_GENS`` becomes empty. To select a type and a tier to -evict from, it first compares ``min_seq[]`` to select the older type. -If both types are equally old, it selects the one whose first tier has -a lower refault percentage. The first tier contains single-use -unmapped clean pages, which are the best bet. The eviction sorts a -page according to its gen counter if the aging has found this page -accessed through page tables and updated its gen counter. It also -moves a page to the next generation, i.e., ``min_seq+1``, if this page -was accessed multiple times through file descriptors and the feedback -loop has detected outlying refaults from the tier this page is in. To -this end, the feedback loop uses the first tier as the baseline, for -the reason stated earlier. +``min_seq%MAX_NR_GENS`` becomes empty. To select a type to evict from, +it first compares ``min_seq[]`` to select the older type. If both +types are equally old, it compares the refault percentages of all the +tiers of each type proportional to the swappiness level. To select a +tier to protect, it compares the refault percentage of each tier +against that of the tiers below it. The lowest tier contains pages +accessed at most once, which are the best bet. The eviction sorts a +page according to its gen counter if the aging or an access has +updated its gen counter. It also moves a page to the next generation, +i.e., ``min_seq+1``, if the feedback loop has detected outlying +refaults from the tier this page is in. =20 Working set protection ---------------------- --=20 2.56.0