From: Kairui Song via B4 Relay <devnull+kasong.tencent.com@kernel.org>
To: linux-mm@kvack.org, "Liam R. Howlett" <liam@infradead.org>,
SJ Park <sj@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>,
Johannes Weiner <hannes@cmpxchg.org>,
Muchun Song <muchun.song@linux.dev>,
Qi Zheng <qi.zheng@linux.dev>,
Ying Huang <ying.huang@linux.alibaba.com>,
Chris Li <chrisl@kernel.org>, Baoquan He <baoquan.he@linux.dev>,
Nico Pache <nico.pache@linux.dev>,
Usama Arif <usama.arif@linux.dev>,
Michal Hocko <mhocko@kernel.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>, Barry Song <baohua@kernel.org>,
Axel Rasmussen <axelrasmussen@google.com>,
Yuanchu Xie <yuanchu@google.com>, Wei Xu <weixugc@google.com>,
Vlastimil Babka <vbabka@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Kemeng Shi <shikemeng@huaweicloud.com>,
Nhat Pham <nphamcs@gmail.com>,
Youngjun Park <youngjun.park@lge.com>, Zi Yan <ziy@nvidia.com>,
Gregory Price <gourry@gourry.net>,
"Matthew Wilcox (Oracle)" <willy@infradead.org>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Ryan Roberts <ryan.roberts@arm.com>, Dev Jain <dev.jain@arm.com>,
Lance Yang <lance.yang@linux.dev>,
Hugh Dickins <hughd@google.com>,
David Rientjes <rientjes@google.com>,
Yu Zhao <yuzhao@google.com>, Vernon Yang <vernon2gm@gmail.com>,
Zicheng Wang <wangzicheng@honor.com>,
Chen Ridong <chenridong@xiaomi.com>,
Tal Zussman <tz2294@columbia.edu>,
Kairui Song <ryncsn@gmail.com>,
Xiang Liu <liuxiang.277@bytedance.com>,
Ehab Ababneh <ehab.ababneh@intel.com>,
zhaozhengzhuo <zhaozhengzhuo@uniontech.com>,
Lian Wang <lianux.mm@gmail.com>,
Kunwu Chan <kunwu.chan@gmail.com>,
linux-kernel@vger.kernel.org, cgroups@vger.kernel.org,
damon@lists.linux.dev, Kairui Song <kasong@tencent.com>
Subject: [PATCH v4 15/17] mm/mglru: make folio_inc_lru_refs lruvec lockless
Date: Fri, 16 Oct 2026 02:16:04 +0800 [thread overview]
Message-ID: <20261016-mglru-fg-v4-15-2106750c0bfa@tencent.com> (raw)
In-Reply-To: <20261016-mglru-fg-v4-0-2106750c0bfa@tencent.com>
From: Kairui Song <kasong@tencent.com>
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 != old_gen
implies gen >= 0, and lru_gen_update_size() needs no explicit guard.
The active/inactive ABI update keeps its gen >= 0 check because an
off-LRU folio has no lruvec to update.
Signed-off-by: Kairui Song <kasong@tencent.com>
---
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 *lruvec, int type, int gen)
gen == lru_gen_from_seq(min_seq + 1);
}
-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 = folio_is_file_lru(folio);
int zone = folio_zonenum(folio);
int delta = folio_nr_pages(folio);
struct lru_gen_folio *lrugen = &lruvec->lrugen;
@@ -386,7 +385,7 @@ static inline bool lru_gen_add_folio(struct lruvec *lruvec, struct folio *folio,
/* use the refs from the atomic snapshot to avoid raced update */
refs = lru_get_refs_flags(flags);
- lru_gen_update_size(lruvec, folio, -1, gen);
+ lru_gen_update_size(lruvec, type, folio, -1, gen);
if (lru_refs_is_active(refs))
lru += LRU_ACTIVE;
__update_lru_size(lruvec, lru, zone, delta);
@@ -404,6 +403,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
{
unsigned long flags;
int gen, refs;
+ int type = folio_is_file_lru(folio);
int zone = folio_zonenum(folio);
int delta = folio_nr_pages(folio);
enum lru_list lru = folio_is_file_lru(folio) * LRU_INACTIVE_FILE;
@@ -426,7 +426,7 @@ static inline bool lru_gen_del_folio(struct lruvec *lruvec, struct folio *folio,
if (!reclaiming && ((max_seq - gen) % MAX_NR_GENS) < MIN_NR_GENS)
folio_set_active(folio);
- lru_gen_update_size(lruvec, folio, gen, -1);
+ lru_gen_update_size(lruvec, type, folio, gen, -1);
if (lru_refs_is_active(refs))
lru += 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)
-/* 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
#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 regardless
* of its old-generation access history. PID protection (folio_inc_gen) caps
* at LRU_REFS_WORKINGSET independently.
+ *
+ * folio_inc_lru_refs() retries with LRU_REF_FORCE if the aging recycled the
+ * gen index it was about to store: newest gen and an extra reference.
*/
/*
@@ -982,37 +985,35 @@ void folio_inc_lru_refs(struct folio *folio, unsigned int flags)
long nr_pages = folio_nr_pages(folio);
struct lru_gen_folio *lrugen;
struct lruvec *lruvec = NULL;
+ bool aged = false;
+retry:
old_flags = READ_ONCE(*folio_flags(folio, 0));
do {
new_flags = old_flags;
old_gen = lru_get_gen_flags(old_flags);
old_refs = lru_get_refs_flags(old_flags);
- file = folio_flags_is_file_lru(&old_flags);
refs = old_refs + 1;
gen = old_gen;
+ file = 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 = lruvec_live_lock_irq(folio_lruvec(folio));
+ lruvec = folio_lruvec_live_get(folio);
lrugen = &lruvec->lrugen;
}
+ /*
+ * Read the flags before max_seq, paired with the store of a
+ * gen, which samples max_seq first.
+ */
+ smp_rmb();
max_seq = READ_ONCE(lrugen->max_seq);
max_gen = lru_gen_from_seq(max_seq);
if (old_gen == 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 = 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, unsigned int flags)
break;
} while (!try_cmpxchg(folio_flags(folio, 0), &old_flags, new_flags));
- if (gen != old_gen)
- lru_gen_update_size(lruvec, folio, old_gen, gen);
- if (lru_refs_is_active(old_refs) != lru_refs_is_active(refs) && old_gen >= 0) {
+ if (gen != 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) != max_seq)) {
+ flags = LRU_REF_FORCE;
+ aged = true;
+ }
+ lru_gen_update_size(lruvec, file, folio, old_gen, gen);
+ }
+
+ if (lru_refs_is_active(old_refs) != lru_refs_is_active(refs) && gen >= 0) {
enum lru_list lru = file * LRU_INACTIVE_FILE;
__update_lru_size(lruvec, lru + lru_refs_is_active(old_refs),
@@ -1046,8 +1057,12 @@ void folio_inc_lru_refs(struct folio *folio, unsigned int flags)
__update_lru_size(lruvec, lru + lru_refs_is_active(refs),
folio_zonenum(folio), nr_pages);
}
+ if (aged) {
+ aged = false;
+ goto retry;
+ }
if (lruvec)
- lruvec_unlock_irq(lruvec);
+ folio_lruvec_live_put(lruvec);
}
/*
@@ -3647,7 +3662,7 @@ static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
new_gen = __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);
return new_gen;
}
--
2.56.0
next prev parent reply other threads:[~2026-10-15 18:15 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 18:15 [PATCH v4 00/17] mm/mglru: frequency guided promotion (MGLRU-FG) and flag cleanup Kairui Song via B4 Relay
2026-10-15 18:15 ` [PATCH v4 01/17] mm/memcontrol: allow update of LRU statistic without holding LRU lock Kairui Song via B4 Relay
2026-11-02 21:39 ` Barry Song (Xiaomi)
2026-10-15 18:15 ` [PATCH v4 02/17] mm/mglru: make generation page counters atomic Kairui Song via B4 Relay
2026-10-15 18:15 ` [PATCH v4 03/17] mm/memcg: add folio-based lruvec live helper Kairui Song via B4 Relay
2026-10-15 19:11 ` Harry Yoo
2026-10-16 6:15 ` Kairui Song
2026-10-19 13:46 ` Harry Yoo
2026-10-19 14:14 ` Harry Yoo
2026-10-16 7:39 ` Muchun Song
2026-10-16 7:54 ` Kairui Song
2026-10-16 9:09 ` Muchun Song
2026-10-15 18:15 ` [PATCH v4 04/17] mm/mglru: frequency guided workingset promotion (MGLRU-FG) Kairui Song via B4 Relay
2026-10-19 10:10 ` Barry Song
2026-10-19 12:08 ` Kairui Song
2026-10-26 22:34 ` Barry Song
2026-10-15 18:15 ` [PATCH v4 05/17] mm/mglru: make folio lru referenced times count a generic API Kairui Song via B4 Relay
2026-10-15 18:15 ` [PATCH v4 06/17] mm/mglru: move add/del LRU size accounting out of lru_gen_update_size() Kairui Song via B4 Relay
2026-11-08 20:48 ` Barry Song
2026-11-09 9:33 ` Kairui Song
2026-11-09 9:51 ` Barry Song
2026-10-15 18:15 ` [PATCH v4 07/17] mm/mglru, gup: mark folios referenced via a fast helper Kairui Song via B4 Relay
2026-11-08 21:04 ` Barry Song
2026-11-09 9:32 ` Kairui Song
2026-10-15 18:15 ` [PATCH v4 08/17] mm/mglru, smaps: convert to LRU refs based operations for MGLRU Kairui Song via B4 Relay
2026-10-15 18:15 ` [PATCH v4 09/17] mm/damon: " Kairui Song via B4 Relay
2026-10-15 18:15 ` [PATCH v4 10/17] mm/khugepaged: " Kairui Song via B4 Relay
2026-10-15 18:16 ` [PATCH v4 11/17] mm/madvise: adapt for " Kairui Song via B4 Relay
2026-10-15 18:16 ` [PATCH v4 12/17] mm/huge_memory: mark folio as accessed more accurately on split Kairui Song via B4 Relay
2026-11-08 22:14 ` Barry Song
2026-11-09 3:20 ` Kairui Song
2026-10-15 18:16 ` [PATCH v4 13/17] mm/mglru: folio LRU refs based active/inactive number accounting Kairui Song via B4 Relay
2026-10-15 18:16 ` [PATCH v4 14/17] mm/mglru: reparent folios from all generations Kairui Song via B4 Relay
2026-10-15 18:16 ` Kairui Song via B4 Relay [this message]
2026-11-08 21:48 ` [PATCH v4 15/17] mm/mglru: make folio_inc_lru_refs lruvec lockless Barry Song
2026-11-09 8:08 ` Kairui Song
2026-11-09 9:45 ` Barry Song
2026-10-15 18:16 ` [PATCH v4 16/17] mm/mglru: make folio_test_workingset() work based on folio LRU refs Kairui Song via B4 Relay
2026-10-15 18:16 ` [PATCH v4 17/17] Documentation/mm: multi-gen LRU: update for frequency guided promotion Kairui Song via B4 Relay
2026-10-16 8:42 ` [PATCH v4 00/17] mm/mglru: frequency guided promotion (MGLRU-FG) and flag cleanup Barry Song (Xiaomi)
2026-10-16 11:43 ` Kairui Song
2026-10-16 12:11 ` Kairui Song
2026-10-20 20:47 ` Barry Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261016-mglru-fg-v4-15-2106750c0bfa@tencent.com \
--to=devnull+kasong.tencent.com@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=cgroups@vger.kernel.org \
--cc=chenridong@xiaomi.com \
--cc=chrisl@kernel.org \
--cc=damon@lists.linux.dev \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=ehab.ababneh@intel.com \
--cc=gourry@gourry.net \
--cc=hannes@cmpxchg.org \
--cc=hughd@google.com \
--cc=kasong@tencent.com \
--cc=kunwu.chan@gmail.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=lianux.mm@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=liuxiang.277@bytedance.com \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=muchun.song@linux.dev \
--cc=nico.pache@linux.dev \
--cc=nphamcs@gmail.com \
--cc=qi.zheng@linux.dev \
--cc=rientjes@google.com \
--cc=roman.gushchin@linux.dev \
--cc=ryan.roberts@arm.com \
--cc=ryncsn@gmail.com \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=sj@kernel.org \
--cc=surenb@google.com \
--cc=tz2294@columbia.edu \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=vernon2gm@gmail.com \
--cc=wangzicheng@honor.com \
--cc=weixugc@google.com \
--cc=willy@infradead.org \
--cc=ying.huang@linux.alibaba.com \
--cc=youngjun.park@lge.com \
--cc=yuanchu@google.com \
--cc=yuzhao@google.com \
--cc=zhaozhengzhuo@uniontech.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®