From: Zhaoyang Huang This submission addresses an issue where the memcg MGLRU's min_seq is pinned by an orphan page—a page that resides in the page cache but lacks a corresponding reference count, while the number of GENs has not yet met the criteria of aging. I believe such pages should be detected and handled elsewhere rather than affecting the LRU in this way(legacy LRU could move forward by bringing this kind of folio along with). Therefore, I propose moving these folios to the next generation when isolation makes no progress at all. Signed-off-by: Zhaoyang Huang --- include/linux/mmzone.h | 6 ++++++ mm/vmscan.c | 45 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 9adb2ad21da5..b063c56ea30c 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -434,6 +434,12 @@ enum lruvec_flags { */ LRUVEC_CGROUP_CONGESTED, LRUVEC_NODE_CONGESTED, + /* + * MGLRU: eviction failed to isolate from the oldest generation once + * while the window was at MAX_NR_GENS. A second consecutive failure + * forces aging so residual folios cannot pin min_seq indefinitely. + */ + LRUVEC_MGLRU_ISOLATE_FAILED, }; #endif /* !__GENERATING_BOUNDS_H */ diff --git a/mm/vmscan.c b/mm/vmscan.c index bd1b1aa12581..c18db08c5e10 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -175,6 +175,8 @@ struct scan_control { unsigned int immediate; unsigned int file_taken; unsigned int taken; + /* folios isolated by MGLRU eviction in the current lruvec pass */ + unsigned int isolated; } nr; /* for recording the reclaimed slab by now */ @@ -4748,6 +4750,7 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec, trace_mm_vmscan_lru_isolate(sc->reclaim_idx, sc->order, scan_batch, scanned, skipped, isolated, type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON); + sc->nr.isolated += isolated; if (type == LRU_GEN_FILE) sc->nr.file_taken += isolated; /* @@ -4998,6 +5001,11 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) long nr_to_scan; unsigned long scanned = 0; int swappiness = get_swappiness(lruvec, sc); + bool attempted = false; + bool rotated = false; + + /* per-lruvec pass; sc is shared across lruvecs during reclaim */ + sc->nr.isolated = 0; while (true) { int delta; @@ -5006,6 +5014,7 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) if (nr_to_scan <= 0) break; + attempted = true; delta = evict_folios(nr_to_scan, lruvec, sc, swappiness); if (!delta) break; @@ -5020,6 +5029,40 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) cond_resched(); } + /* + * Eviction isolated nothing while the sliding window is already at + * MAX_NR_GENS. In that state should_run_aging() is false, so + * get_nr_to_scan() never calls try_to_inc_max_seq(). Residual folios + * that fail isolate_folio() can pin min_seq and hide reclaimable + * folios in younger generations. + * + * Require two consecutive isolate failures on this lruvec before + * forcing aging, so a single no-progress batch does not rotate the + * window. Successful isolation clears the sticky bit. + * + * Key off sc->nr.isolated, not nr_reclaimed: folios isolated but + * rejected by shrink_folio_list() are put back with PG_active and do + * not pin the oldest generation. + */ + if (attempted && !sc->nr.isolated) { + DEFINE_MAX_SEQ(lruvec); + DEFINE_MIN_SEQ(lruvec); + + if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS < max_seq) { + if (test_and_set_bit(LRUVEC_MGLRU_ISOLATE_FAILED, + &lruvec->flags)) { + rotated = try_to_inc_max_seq(lruvec, max_seq, + swappiness, false); + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED, + &lruvec->flags); + } + } else { + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED, &lruvec->flags); + } + } else if (sc->nr.isolated) { + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED, &lruvec->flags); + } + /* * If too many file cache in the coldest generation can't be evicted * due to being dirty, wake up the flusher. @@ -5043,7 +5086,7 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) } /* whether this lruvec should be rotated */ - return nr_to_scan < 0; + return nr_to_scan < 0 || rotated; } static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) -- 2.25.1