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

From: Baolin Wang

Date: Thu Oct 22 2026 - 04:53:17 EST




On 10/22/26 5:55 AM, Barry Song wrote:
On Wed, Oct 21, 2026 at 3:28 PM Baolin Wang
<baolin.wang@xxxxxxxxxxxxxxxxx> wrote:
[...]

static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
{
unsigned long total;
int swappiness = get_swappiness(lruvec, sc);
struct mem_cgroup *memcg = lruvec_memcg(lruvec);

+ swappiness = get_scan_swappiness(lruvec, sc, swappiness);
+
total = lruvec_evictable_size(lruvec, swappiness);

/* whether the size is big enough to be helpful */
@@ -5249,9 +5265,10 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
bool need_rotate = false, should_age = false;
long nr_batch, nr_to_scan;
int swappiness = get_swappiness(lruvec, sc);
+ int scan_swappiness = get_scan_swappiness(lruvec, sc, swappiness);

IMO, 'swappiness' and 'scan_swappiness' are really confusing. Why move
this logic into get_swappiness()? In get_swappiness(), we already have
some similar logic to adjust swappiness when there is no available swap
space.


Yes. It is very confusing. we can't do that in get_swappiness() as otherwise,
we will get cold/hot inversion due to GFP_NOIO. for a real swappiness=0,
we allow cold/hot inversion for anon, but for GFP_NOIO, this is not the
case.

But can_reclaim_anon_pages() doesn't only handle the GFP_NOIO case, it also covers the swap space limitation scenario. The latter is what concerns me more.

If we're only talking about the GFP_NOIO case, I'd agree with your concern. However, we could introduce a helper to force anon aging and add comments explaining why we need this. That would look more maintainable than 2 confusing swappiness variables, no?

Just my 2 cents.

Something like below (untested):

diff --git a/mm/vmscan.c b/mm/vmscan.c
index daa7e37c2236..7c1d26e6b9d7 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2890,7 +2890,7 @@ static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
if (swappiness == SWAPPINESS_ANON_ONLY)
return swappiness;

- if (!sc->may_swap)
+ if (!sc->may_swap || !(sc->gfp_mask & __GFP_IO))
return 0;

if (!can_demote(pgdat->node_id, sc, memcg) &&
@@ -4049,7 +4049,7 @@ static inline void flush_lru_batch(struct list_head *head, struct list_head **ba
*batch_end = NULL;
}

-static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)
+static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness, bool force_anon_aging)
{
int zone;
int remaining = MAX_LRU_BATCH;
@@ -4063,7 +4063,7 @@ static bool inc_min_seq(struct lruvec *lruvec, int type, int swappiness)
goto done;

/* For anon type, skip the check if swappiness is zero (file only) */
- if (!type && !swappiness)
+ if (!type && !swappiness && !force_anon_aging)
goto done;

VM_WARN_ON_ONCE(get_nr_gens(lruvec, type) != MAX_NR_GENS);
@@ -4183,7 +4183,20 @@ static void try_to_inc_min_seq(struct lruvec *lruvec, int swappiness)
}
}

-static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness)
+static bool is_force_anon_aging(struct scan_control *sc, struct lruvec *lruvec,
+ int swappiness)
+{
+ if (swappiness || (sc->gfp_mask & __GFP_IO))
+ return false;
+
+ /*
+ * Explain why we neeed to force increase anon min seq.
+ * xxxxxx
+ */
+ return swappiness != sc_swappiness(sc, lruvec_memcg(lruvec));
+}
+
+static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness, bool force_anon_aging)
{
bool success;
int prev, next;
@@ -4205,7 +4218,7 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness
if (get_nr_gens(lruvec, type) != MAX_NR_GENS)
continue;

- if (inc_min_seq(lruvec, type, swappiness))
+ if (inc_min_seq(lruvec, type, swappiness, force_anon_aging))
continue;

lruvec_unlock_irq(lruvec);
@@ -4249,7 +4262,7 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness
}

static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,
- int swappiness, bool force_scan)
+ int swappiness, bool force_scan, bool force_anon_aging)
{
bool success;
struct lru_gen_mm_walk *walk;
@@ -4260,7 +4273,7 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,
VM_WARN_ON_ONCE(seq > READ_ONCE(lrugen->max_seq));

if (!mm_state)
- return inc_max_seq(lruvec, seq, swappiness);
+ return inc_max_seq(lruvec, seq, swappiness, force_anon_aging);

/* see the comment in iterate_mm_list() */
if (seq <= READ_ONCE(mm_state->seq))
@@ -4295,7 +4308,7 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long seq,
} while (mm);
done:
if (success) {
- success = inc_max_seq(lruvec, seq, swappiness);
+ success = inc_max_seq(lruvec, seq, swappiness, force_anon_aging);
WARN_ON_ONCE(!success);
}

@@ -5262,7 +5275,9 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
}

if (should_run_aging(lruvec, max_seq, sc, swappiness)) {
- if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
+ bool force_anon_aging = is_force_anon_aging(sc, lruvec, swappiness);
+
+ if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false, force_anon_aging))
need_rotate = true;
should_age = true;
}