Re: [RFC PATCH v2] mm: vmscan: avoid anon scanning for GFP_NOIO with low swapcache in MGLRU

From: Barry Song

Date: Thu Oct 22 2026 - 17:20:21 EST


On Thu, Oct 22, 2026 at 7:26 PM Baolin Wang
<baolin.wang@xxxxxxxxxxxxxxxxx> wrote:
[...]

> >> }
> >>
> >> if (should_run_aging(lruvec, max_seq, sc, swappiness)) {
> >> - if (try_to_inc_max_seq(lruvec, max_seq,
> >> swappiness, false))
> >
> > This is quite tricky. If we pass swappiness=0, anon aging will be
> > skipped. Do we want that for GFP_NOIO cases? I mean, these are not
> > real cases where anon aging should be disabled.
>
> No, we have real use cases where 'vm_swappiness' is set to 0, which
> means the workload never reclaims anon folios. So in this case we don't
> need to age anon folios.

My concern is that setting swappiness to 0 for GFP_NOIO also skips
anon aging, making it behave the same as a real swappiness=0.
However, GFP_NOIO is only a temporary state, and subsequent
allocations may not use GFP_NOIO.

> > static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *args)
> > {
> > ...
> > if (vma == get_gate_vma(vma->vm_mm))
> > return true;
> > ...
> > }
> >
> >> + bool force_anon_aging = is_force_anon_aging(sc,
> >> lruvec, swappiness);
> >
> > Adding a force_anon_aging argument to inc_max_seq() seems like
> > quite a burden. Maybe a cleaner approach is to pass sc all the
> > way to inc_max_seq() and let it detect GFP_NOIO cases itself?
>
> OK, still look good to me.
>
> > BTW, I guess we don't need to worry about swappiness at all. We can
> > simply bypass anon reclaim?
> >
> > diff --git a/mm/vmscan.c b/mm/vmscan.c
> > index fc823fe3bd1f..fa6d9860156d 100644
> > --- a/mm/vmscan.c
> > +++ b/mm/vmscan.c
> > @@ -4903,6 +4903,8 @@ 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;
> > + struct pglist_data *pgdat = lruvec_pgdat(lruvec);
> > + struct mem_cgroup *memcg = lruvec_memcg(lruvec);
> >
> > VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
> > VM_WARN_ON_ONCE(!list_empty(list));
> > @@ -4910,6 +4912,9 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> > if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
> > return 0;
> >
> > + if (!type && reclaimable_anon_is_low(memcg, pgdat->node_id, sc))
> > + return 0;
> > +
> > gen = lru_gen_from_seq(lrugen->min_seq[type]);
> >
> > for (i = MAX_NR_ZONES; i > 0; i--) {
> > @@ -5164,12 +5169,19 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> > static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
> > struct scan_control *sc, int swappiness)
> > {
> > + struct pglist_data *pgdat = lruvec_pgdat(lruvec);
> > + struct mem_cgroup *memcg = lruvec_memcg(lruvec);
> > DEFINE_MIN_SEQ(lruvec);
> >
> > /* have to run aging, since eviction is not possible anymore */
> > if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS > max_seq)
> > return true;
> >
> > + /* run aging when GFP_NOIO limits anon reclaim with few swapcache folios */
> > + if (swappiness && min_seq[LRU_GEN_FILE] + MIN_NR_GENS > max_seq &&
> > + reclaimable_anon_is_low(memcg, pgdat->node_id, sc))
> > + return true;
>
> I've considered this, but we'd still need to add some code in
> get_nr_to_scan()/lruvec_is_sizable() to filter out the anon folios for
> GFP_NOIO case. Rather than spreading the logic across multiple places,
> I'd prefer adding a helper with proper comments to handle the aging
> issue, like the sample code I showed.
>

I'd rather we avoid hacking get_swappiness() any further, TBH.
MGLRU's get_swappiness() is quite weird, and CLRU doesn't have it.
I hope we can remove it someday and use the real swappiness
everywhere, while handling special cases based on system state,
just like CLRU does.

Best Regards
Barry