[RFC PATCH 1/4] mm/vmscan: only reclaim slab in node reclaim when over min_slab_pages

From: Ridong Chen

Date: Fri Aug 21 2026 - 04:23:39 EST


From: Ridong Chen <chenridong@xxxxxxxxxx>

Since commit d8ff6fde8e88 ("mm/vmscan: take min_slab_pages into account
when try to call shrink_node"), node reclaim enters shrink_node() when
reclaimable slab is over min_slab_pages OR unmapped page cache is over
min_unmapped_pages.

But the threshold only decides whether to enter shrink_node(), not what
it reclaims. min_slab_pages is documented to gate slab reclaim alone:
"On Zone reclaim slabs will be reclaimed if more than this percentage of
pages in a zone are reclaimable slab pages". Yet once unmapped page
cache alone trips the gate, shrink_node() still invokes the slab
shrinkers and can drive reclaimable slab below min_slab_pages.

Carry the decision into the reclaim path via a scan_control flag and
skip the slab shrinkers when reclaimable slab is already at or below
min_slab_pages, so the limit gates slab reclaim as documented. The flag
defaults to zero, so kswapd, direct reclaim, memcg reclaim, proactive
reclaim and drop_caches are unaffected; only the node reclaim path sets
it.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Ridong Chen <chenridong@xxxxxxxxxx>
---
mm/vmscan.c | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index c17ac77b08a4..7e65d0ba4a96 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -114,6 +114,12 @@ struct scan_control {
/* zone_reclaim_mode, boost reclaim, cgroup restrictions */
unsigned int may_swap:1;

+ /*
+ * When set, the slab shrinkers are not invoked because reclaimable
+ * slab is already at or below min_slab_pages.
+ */
+ unsigned int skip_slab_reclaim:1;
+
/* Not allow cache_trim_mode to be turned on as part of reclaim? */
unsigned int no_cache_trim_mode:1;

@@ -5120,7 +5126,8 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)

need_rotate = try_to_shrink_lruvec(lruvec, sc);

- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority);
+ if (!sc->skip_slab_reclaim)
+ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority);

if (!sc->proactive)
vmpressure(sc->gfp_mask, sc->order, memcg, false,
@@ -6237,8 +6244,9 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc)

shrink_lruvec(lruvec, sc);

- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg,
- sc->priority);
+ if (!sc->skip_slab_reclaim)
+ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg,
+ sc->priority);

/* Record the group's reclaim efficiency */
if (!sc->proactive)
@@ -7940,6 +7948,15 @@ unsigned long node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned i
if (test_and_set_bit_lock(PGDAT_RECLAIM_LOCKED, &pgdat->flags))
return 0;

+ /*
+ * min_slab_pages only gates slab reclaim: when reclaimable slab is
+ * already at or below the limit, leave the shrinkers alone even if we
+ * entered node reclaim to trim unmapped page cache.
+ */
+ sc.skip_slab_reclaim =
+ node_page_state_pages(pgdat, NR_SLAB_RECLAIMABLE_B) <=
+ pgdat->min_slab_pages;
+
ret = __node_reclaim(pgdat, nr_pages, &sc);
clear_bit_unlock(PGDAT_RECLAIM_LOCKED, &pgdat->flags);

--
2.34.1