Re: [PATCH] mm/mglru: preserve inactive placement when enabling MGLRU
From: Barry Song
Date: Thu Aug 20 2026 - 03:55:54 EST
On Thu, Aug 20, 2026 at 3:27 PM Ridong Chen <ridong.chen@xxxxxxxxx> wrote:
>
> From: Ridong Chen <chenridong@xxxxxxxxxx>
>
> When the LRU is switched to MGLRU (echo y > /sys/kernel/mm/lru_gen/
> enabled), fill_evictable() re-inserts every folio via
> lru_gen_add_folio(..., false). With reclaiming hardcoded to false, an
> inactive anonymous folio (no PG_active, not in the swapcache) takes the
> "gen = MIN_NR_GENS" branch in lru_gen_folio_seq() and is seeded at
> seq = max_seq - 1, which lru_gen_is_active() treats as active. Its
> inactive placement is lost and NR_INACTIVE_ANON is folded into
> NR_ACTIVE_ANON.
>
> Pass reclaiming=!active so a folio from an inactive list is seeded into
> an older generation. Folios from the active list carry PG_active and
> hit the first branch either way, so they are unchanged.
>
> reclaiming also selects the insertion end in lru_gen_add_folio():
> list_add_tail() for inactive folios, list_add() for active ones. Both
> the legacy LRU and a MGLRU generation keep the hottest folios at the
> head and the coldest at the tail, and reclaim takes from the tail. To
> preserve that order the folio must be taken from the end matching the
> insertion end, so take inactive folios from the head and active folios
> from the tail; otherwise hot/cold would be reversed within the
> generation.
>
> Tested on x86_64, next-20260812, 2G VM + 1G swap, ~1.5G anon pushed onto
> the inactive list before enabling MGLRU:
>
> Active(anon) Inactive(anon)
> before switch (legacy) 2952 1548792 kB
> after `echo y`, unpatched 1552052 0 kB
> after `echo y`, patched 15144 1536636 kB
>
> Inactive file folios stay inactive either way (NR_INACTIVE_FILE is
> preserved).
>
> Fixes: 354ed5974429 ("mm: multi-gen LRU: kill switch")
> Suggested-by: Barry Song <baohua@xxxxxxxxxx>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Ridong Chen <chenridong@xxxxxxxxxx>
> ---
> mm/vmscan.c | 29 +++++++++++++++++++++++++++--
> 1 file changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 94fc4f25e99f..7be4cec9a838 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -5311,7 +5311,23 @@ static bool fill_evictable(struct lruvec *lruvec)
>
> while (!list_empty(head)) {
> bool success;
> - struct folio *folio = lru_to_folio(head);
> + struct folio *folio;
> +
> + /*
> + * Both the legacy LRU and a MGLRU generation keep the
> + * hottest folios at the head and the coldest at the
> + * tail, and reclaim takes from the tail. To preserve
> + * that order, the end we take from must match the end
> + * lru_gen_add_folio() inserts at: inactive folios use
> + * reclaiming=true (list_add_tail), so take from the
> + * head; active folios use reclaiming=false (list_add),
> + * so take from the tail. Taking from the wrong end
> + * would reverse hot/cold within the generation.
> + */
too many words, maybe just:
lru_gen_add_folio() uses list_add_tail() rather than list_add()
when reclaiming is true. Match its ordering to avoid cold/hot
Inversion.
> + if (active)
> + folio = lru_to_folio(head);
> + else
> + folio = list_first_entry(head, struct folio, lru);
>
> VM_WARN_ON_ONCE_FOLIO(folio_test_unevictable(folio), folio);
> VM_WARN_ON_ONCE_FOLIO(folio_test_active(folio) != active, folio);
> @@ -5319,7 +5335,16 @@ static bool fill_evictable(struct lruvec *lruvec)
> VM_WARN_ON_ONCE_FOLIO(folio_lru_gen(folio) != -1, folio);
>
> lruvec_del_folio(lruvec, folio);
> - success = lru_gen_add_folio(lruvec, folio, false);
> + /*
> + * With reclaiming=false, lru_gen_folio_seq() would seed
> + * an inactive folio near max_seq, which
> + * lru_gen_is_active() reports as active, so its inactive
> + * placement would be lost. Pass reclaiming=!active to
> + * seed it into the oldest generation instead. This
> + * reuses reclaiming beyond its folio_rotate_reclaimable()
> + * meaning; it also picks list_add_tail() above.
> + */
Maybe that's too verbose. How about:
"Borrow reclaiming=true to place inactive folios in the older gens"
> + success = lru_gen_add_folio(lruvec, folio, !active);
> VM_WARN_ON_ONCE(!success);
>
> if (!--remaining)
This is admittedly a bit ugly, but it seems to be the simplest
approach. Since switching MGLRU on and off is not a common scenario,
we probably don't want to over-engineer it. So, with the above change:
Acked-by: Barry Song <baohua@xxxxxxxxxx>