* [RFC PATCH v3 1/6] mm: mglru: prevent min_seq[type] from pointing to an empty generation
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 2/6] mm: mglru: let scan_folios() scan both reclaimable generations Barry Song (Xiaomi)
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song (Xiaomi)
In try_to_inc_min_seq(), min_seq[LRU_GEN_ANON] and
min_seq[LRU_GEN_FILE] can be adjusted to point to empty generations
to keep their gap within one. As a result, scan_folios() may
repeatedly scan empty generations because it assumes the min_seq
generation still contains folios. Likewise,
should_run_aging() has no way to tell that the min_seq generation
has already been drained.
This is quite confusing. I observed scan_folios() returning 0 even
though the following check in scan_folios() doesn't take effect:
if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
return 0;
There is no need to adjust min_seq[], since the reclaim logic already
triggers aging when the number of generations reaches
MIN_NR_GENS, and reclaim never reduces it below MIN_NR_GENS.
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
include/linux/mmzone.h | 6 ++----
mm/vmscan.c | 10 ----------
2 files changed, 2 insertions(+), 14 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index a26c8b855222..233d2006a541 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -552,10 +552,8 @@ enum {
* The youngest generation number is stored in max_seq for both anon and file
* types as they are aged on an equal footing. The oldest generation numbers are
* stored in min_seq[] separately for anon and file types so that they can be
- * incremented independently. Ideally min_seq[] are kept in sync when both anon
- * and file types are evictable. However, to adapt to situations like extreme
- * swappiness, they are allowed to be out of sync by at most
- * MAX_NR_GENS-MIN_NR_GENS-1.
+ * incremented independently. For both file and anonymous memory, the minimum
+ * generation must be at least MIN_NR_GENS.
*
* The number of pages in each generation is eventually consistent and therefore
* can be transiently negative when reset_batch_size() is pending.
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 566c4e837c7d..ce027c271e9b 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -3972,16 +3972,6 @@ static void try_to_inc_min_seq(struct lruvec *lruvec, int swappiness)
if (!seq_inc_flag)
return;
- /* see the comment on lru_gen_folio */
- if (swappiness && swappiness <= MAX_SWAPPINESS) {
- unsigned long seq = lrugen->max_seq - MIN_NR_GENS;
-
- if (min_seq[LRU_GEN_ANON] > seq && min_seq[LRU_GEN_FILE] < seq)
- min_seq[LRU_GEN_ANON] = seq;
- else if (min_seq[LRU_GEN_FILE] > seq && min_seq[LRU_GEN_ANON] < seq)
- min_seq[LRU_GEN_FILE] = seq;
- }
-
for_each_evictable_type(type, swappiness) {
if (min_seq[type] <= lrugen->min_seq[type])
continue;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH v3 2/6] mm: mglru: let scan_folios() scan both reclaimable generations
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 1/6] mm: mglru: prevent min_seq[type] from pointing to an empty generation Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 3/6] mm/mglru: improve readability of isolate_folios() Barry Song (Xiaomi)
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song (Xiaomi)
When we have four generations, and scan_folios() exhausts the oldest
one while the second-oldest still contains reclaimable folios, the
current implementation doesn't move on to scan the second-oldest
generation. Instead, it returns, leaving that generation with no
chance to be scanned at the current sc->priority.
This doesn't seem right. Rather than breaking out and starting a
larger next iteration, let's let scan_folios() continue scanning the
second-oldest generation directly.
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
mm/vmscan.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index ce027c271e9b..31947fa60f18 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4718,6 +4718,7 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
int skipped = 0;
unsigned long remaining = nr_to_scan;
struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ unsigned long min_seq = lrugen->min_seq[type];
VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
VM_WARN_ON_ONCE(!list_empty(list));
@@ -4725,8 +4726,8 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
return 0;
- gen = lru_gen_from_seq(lrugen->min_seq[type]);
-
+next_gen:
+ gen = lru_gen_from_seq(min_seq);
for (i = MAX_NR_ZONES; i > 0; i--) {
LIST_HEAD(moved);
int skipped_zone = 0;
@@ -4768,6 +4769,14 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
break;
}
+ /*
+ * This generation is exhausted across all zones, but the scan
+ * target has not been reached yet. Continue with the next
+ * reclaimable generation.
+ */
+ if (i == 0 && ++min_seq + MIN_NR_GENS <= lrugen->max_seq)
+ goto next_gen;
+
item = PGSCAN_KSWAPD + reclaimer_offset(sc);
mod_lruvec_state(lruvec, item, isolated);
mod_lruvec_state(lruvec, PGREFILL, sorted);
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH v3 3/6] mm/mglru: improve readability of isolate_folios()
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 1/6] mm: mglru: prevent min_seq[type] from pointing to an empty generation Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 2/6] mm: mglru: let scan_folios() scan both reclaimable generations Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 4/6] mm: mglru: improve scan_folios() exhaustion detection Barry Song (Xiaomi)
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song
From: Ridong Chen <chenridong@xiaomi.com>
The for_each_evictable_type() loop in isolate_folios()
is misleading: it does not actually iterate over each
evictable type. Instead, get_type_to_scan() selects the
type to scan, while the iterator `i` merely bounds the
number of attempts.
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
Co-developed-by: Barry Song (Xiaomi) <baohua@kernel.org>
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
mm/vmscan.c | 47 +++++++++++++++++++++++++++--------------------
1 file changed, 27 insertions(+), 20 deletions(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 31947fa60f18..0038f33aa318 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4828,35 +4828,42 @@ static int get_type_to_scan(struct lruvec *lruvec, int swappiness)
return positive_ctrl_err(&sp, &pv);
}
+static inline bool is_single_type_reclaim(int swappiness)
+{
+ return swappiness == MIN_SWAPPINESS ||
+ swappiness == SWAPPINESS_ANON_ONLY;
+}
+
static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
struct scan_control *sc, int swappiness,
struct list_head *list, int *isolated,
int *isolate_type, int *isolate_scanned)
{
- int i;
- int total_scanned = 0;
+ bool single_type = is_single_type_reclaim(swappiness);
int type = get_type_to_scan(lruvec, swappiness);
+ int total_scanned = 0, scanned, tier;
+ bool tried = false;
- for_each_evictable_type(i, swappiness) {
- int scanned;
- int tier = get_tier_idx(lruvec, type);
+retry:
+ tier = get_tier_idx(lruvec, type);
+ scanned = scan_folios(nr_to_scan, lruvec, sc,
+ type, tier, list, isolated);
- scanned = scan_folios(nr_to_scan, lruvec, sc,
- type, tier, list, isolated);
+ total_scanned += scanned;
+ if (*isolated) {
+ *isolate_type = type;
+ *isolate_scanned = scanned;
+ return total_scanned;
+ }
- total_scanned += scanned;
- if (*isolated) {
- *isolate_type = type;
- *isolate_scanned = scanned;
- break;
- }
- /*
- * If scanned > 0 and isolated == 0, avoid falling back to the
- * other type, as this type remains sufficient. Falling back
- * too readily can disrupt the positive_ctrl_err() bias.
- */
- if (!scanned)
- type = !type;
+ /*
+ * We are running out of the current reclaim type. Fall back to
+ * the other type if allowed.
+ */
+ if (!tried && !scanned && !single_type) {
+ type = !type;
+ tried = true;
+ goto retry;
}
return total_scanned;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH v3 4/6] mm: mglru: improve scan_folios() exhaustion detection
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
` (2 preceding siblings ...)
2026-07-31 8:38 ` [RFC PATCH v3 3/6] mm/mglru: improve readability of isolate_folios() Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 5/6] mm: mglru: run aging when pages are severely imbalanced across gens Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 6/6] mm: mglru: run aging if the preferred type has no folios in reclaimable gens Barry Song (Xiaomi)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song (Xiaomi)
Commit 16b475d2ac3c ("mm/mglru: avoid reclaim type fall back when
isolation makes no progress") uses scanned == 0 to determine
whether scan_folios() has exhausted a reclaim type. However,
this is not always sufficient. It is possible for scanned > 0,
while the oldest reclaimable generation is exhausted after the
first scan_folios() call.
Add an exhausted output argument to scan_folios() so it can
explicitly report whether the reclaimable lists for the current
type have been exhausted.
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
mm/vmscan.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 0038f33aa318..2e7fef6975b6 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4707,7 +4707,8 @@ static bool isolate_folio(struct lruvec *lruvec, struct folio *folio, struct sca
static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
struct scan_control *sc, int type, int tier,
- struct list_head *list, int *isolatedp)
+ struct list_head *list, int *isolatedp,
+ bool *exhausted)
{
int i;
int gen;
@@ -4723,8 +4724,10 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
VM_WARN_ON_ONCE(!list_empty(list));
- if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
+ if (get_nr_gens(lruvec, type) == MIN_NR_GENS) {
+ *exhausted = true;
return 0;
+ }
next_gen:
gen = lru_gen_from_seq(min_seq);
@@ -4785,6 +4788,11 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
scanned, skipped, isolated,
type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
+ /*
+ * This scan exhausted the reclaimable lists before reaching the
+ * scan target or accumulating enough isolated folios.
+ */
+ *exhausted = remaining > 0 && isolated < MIN_LRU_BATCH;
*isolatedp = isolated;
return scanned;
}
@@ -4842,12 +4850,12 @@ static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
bool single_type = is_single_type_reclaim(swappiness);
int type = get_type_to_scan(lruvec, swappiness);
int total_scanned = 0, scanned, tier;
- bool tried = false;
+ bool tried = false, exhausted;
retry:
tier = get_tier_idx(lruvec, type);
scanned = scan_folios(nr_to_scan, lruvec, sc,
- type, tier, list, isolated);
+ type, tier, list, isolated, &exhausted);
total_scanned += scanned;
if (*isolated) {
@@ -4860,7 +4868,7 @@ static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
* We are running out of the current reclaim type. Fall back to
* the other type if allowed.
*/
- if (!tried && !scanned && !single_type) {
+ if (!tried && exhausted && !single_type) {
type = !type;
tried = true;
goto retry;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH v3 5/6] mm: mglru: run aging when pages are severely imbalanced across gens
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
` (3 preceding siblings ...)
2026-07-31 8:38 ` [RFC PATCH v3 4/6] mm: mglru: improve scan_folios() exhaustion detection Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
2026-07-31 8:38 ` [RFC PATCH v3 6/6] mm: mglru: run aging if the preferred type has no folios in reclaimable gens Barry Song (Xiaomi)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song
From: lyugaofei <lyugaofei@xiaomi.com>
This partially restores the reclaim behavior introduced in Yu
Zhao's initial MGLRU commit, ac35a4902370 ("mm: multi-gen LRU:
minimal implementation"):
/*
* It's also ideal to spread pages out evenly, i.e., 1/(MIN_NR_GENS+1)
* of the total number of pages for each generation. A reasonable range
* for this average portion is [1/MIN_NR_GENS, 1/(MIN_NR_GENS+2)]. The
* aging cares about the upper bound of hot pages, while the eviction
* cares about the lower bound of cold pages.
*/
if (young * MIN_NR_GENS > total)
return true;
if (old * (MIN_NR_GENS + 2) < total)
return true;
But with a stricter condition: the younger generations must
contain at least old_ratio times as many folios as the older
generations. The old_ratio is derived from the lruvec size, so
larger lruvecs use a higher old_ratio. This allows aging to
keep folios of the preferred type distributed across the
reclaimable generations.
Also, imbalanced aging is only applied to non-balanced swappiness
values (for example, < 60 or > 140). For balanced swappiness values
near the middle, the swappiness bias is less of a concern, while the
slightly increased aging overhead might be unacceptable.
Signed-off-by: lyugaofei <lyugaofei@xiaomi.com>
Co-developed-by: Barry Song (Xiaomi) <baohua@kernel.org>
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
mm/vmscan.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 2e7fef6975b6..98fd2fac90ac 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4973,9 +4973,62 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
return scanned;
}
+static inline bool swappiness_is_balanced(int swappiness)
+{
+ int range = MAX_SWAPPINESS - MIN_SWAPPINESS;
+ int middle = MIN_SWAPPINESS + range / 2;
+
+ return swappiness >= middle - range / 5 &&
+ swappiness <= middle + range / 5;
+}
+
+static bool lru_gen_imbalanced(struct lruvec *lruvec, int type,
+ unsigned long max_seq, unsigned long min_seq,
+ struct scan_control *sc, int swappiness)
+{
+ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ unsigned long young = 0, old = 0, seq;
+ unsigned long old_ratio, gb;
+
+ /* Skip swappiness bias for single-type reclaim or balanced swappiness */
+ if (is_single_type_reclaim(swappiness) || swappiness_is_balanced(swappiness))
+ return false;
+
+ /* More than two generations remain to reclaim */
+ if (min_seq + MIN_NR_GENS < max_seq)
+ return false;
+
+ /*
+ * Run aging if the only remaining reclaimable generation
+ * has few folios.
+ */
+ for (seq = min_seq; seq <= max_seq; seq++) {
+ int gen = lru_gen_from_seq(seq);
+ unsigned long size = 0;
+ int zone;
+
+ for (zone = 0; zone < MAX_NR_ZONES; zone++)
+ size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+
+ if (seq + MIN_NR_GENS > max_seq)
+ young += size;
+ else
+ old += size;
+ }
+ /*
+ * Copied from inactive_is_low(), but uses a higher old_ratio to
+ * make aging less aggressive.
+ */
+ gb = (young + old) >> (30 - PAGE_SHIFT);
+ old_ratio = gb ? int_sqrt(10 * gb) : 1;
+ old_ratio *= MAX_NR_GENS;
+ return young > old * old_ratio;
+}
+
static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
struct scan_control *sc, int swappiness)
{
+ int type = get_type_to_scan(lruvec, swappiness);
DEFINE_MIN_SEQ(lruvec);
/* have to run aging, since eviction is not possible anymore */
@@ -4987,7 +5040,11 @@ static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
return false;
/* better to run aging even though eviction is still possible */
- return evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS == max_seq;
+ if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS == max_seq)
+ return true;
+
+ /* Run aging if the preferred type is severely imbalanced across gens */
+ return lru_gen_imbalanced(lruvec, type, max_seq, min_seq[type], sc, swappiness);
}
static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH v3 6/6] mm: mglru: run aging if the preferred type has no folios in reclaimable gens
2026-07-31 8:38 [RFC PATCH v3 0/6] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
` (4 preceding siblings ...)
2026-07-31 8:38 ` [RFC PATCH v3 5/6] mm: mglru: run aging when pages are severely imbalanced across gens Barry Song (Xiaomi)
@ 2026-07-31 8:38 ` Barry Song (Xiaomi)
5 siblings, 0 replies; 7+ messages in thread
From: Barry Song (Xiaomi) @ 2026-07-31 8:38 UTC (permalink / raw)
To: akpm, linux-mm
Cc: axelrasmussen, david, hannes, kasong, linux-kernel, ljs,
lyugaofei, mhocko, qi.zheng, shakeel.butt, stevensd, weixugc,
yuanchu, chenridong, zhangbo56, wangzicheng, lianux.mm,
Barry Song (Xiaomi)
Respect the type selected by positive_ctrl_err(). If there are no
reclaimable generations left for that type, run aging.
For balanced swappiness values near the middle, still allow gentle
reclaim since the swappiness bias is less of a concern, while the
slightly increased aging overhead might become a problem.
Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
---
mm/vmscan.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 98fd2fac90ac..902d85afc5f6 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5035,6 +5035,10 @@ static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS > max_seq)
return true;
+ /* run aging if the preferred type is exhausted for extreme swappiness */
+ if (min_seq[type] + MIN_NR_GENS > max_seq && !swappiness_is_balanced(swappiness))
+ return true;
+
/* try to avoid aging, do gentle reclaim at the default priority */
if (sc->priority == DEF_PRIORITY)
return false;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread