All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ridong Chen <ridong.chen@linux.dev>
To: Andrew Morton <akpm@linux-foundation.org>,
	Johannes Weiner <hannes@cmpxchg.org>
Cc: David Hildenbrand <david@kernel.org>,
	Michal Hocko <mhocko@kernel.org>, Qi Zheng <qi.zheng@linux.dev>,
	Shakeel Butt <shakeel.butt@linux.dev>,
	Lorenzo Stoakes <ljs@kernel.org>,
	Kairui Song <kasong@tencent.com>, Barry Song <baohua@kernel.org>,
	Axel Rasmussen <axelrasmussen@google.com>,
	Yuanchu Xie <yuanchu@google.com>, Wei Xu <weixugc@google.com>,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	Ridong Chen <ridong.chen@linux.dev>,
	Ridong Chen <chenridong@xiaomi.com>
Subject: [RFC PATCH 2/4] mm/vmscan: only reclaim file pages in node reclaim when over min_unmapped_pages
Date: Fri, 21 Aug 2026 16:17:39 +0800	[thread overview]
Message-ID: <20260821081741.1340277-3-ridong.chen@linux.dev> (raw)
In-Reply-To: <20260821081741.1340277-1-ridong.chen@linux.dev>

From: Ridong Chen <chenridong@xiaomi.com>

Node reclaim enters shrink_node() when unmapped page cache is over
min_unmapped_pages OR reclaimable slab is over min_slab_pages, but the
threshold only decides whether to enter shrink_node(), not what it
reclaims.

min_unmapped_pages is documented to keep a small amount of unmapped page
cache around so that file I/O is not immediately thrown out: "Zone
reclaim will only occur if more than this percentage of pages are in a
state that zone_reclaim_mode allows to be reclaimed."  Yet once slab
alone trips the gate, shrink_node() still reclaims file pages and can
drive unmapped page cache below min_unmapped_pages, defeating the
protection.

Carry the decision into the reclaim path via a scan_control flag set
only on the node reclaim path, and honour it on both reclaim
implementations:

 - traditional LRU: get_scan_count() forces SCAN_ANON, or scans nothing
   when anon cannot be reclaimed (e.g. no swap), rather than falling
   back to SCAN_FILE and breaching the floor;

 - MGLRU: scan_folios() leaves the file type alone, and isolate_folios()
   falls back to anon.

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@xiaomi.com>
---
 mm/vmscan.c | 38 +++++++++++++++++++++++++++++++++++---
 1 file changed, 35 insertions(+), 3 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 7e65d0ba4a96..1e56973ceb73 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -120,6 +120,12 @@ struct scan_control {
 	 */
 	unsigned int skip_slab_reclaim:1;
 
+	/*
+	 * When set, file pages are not reclaimed because unmapped page cache
+	 * is already at or below min_unmapped_pages.
+	 */
+	unsigned int skip_file_reclaim:1;
+
 	/* Not allow cache_trim_mode to be turned on as part of reclaim? */
 	unsigned int no_cache_trim_mode:1;
 
@@ -2581,6 +2587,21 @@ static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
 		goto out;
 	}
 
+	/*
+	 * node_reclaim protects unmapped page cache down to
+	 * min_unmapped_pages: skip file pages and reclaim anon only.  As with
+	 * the anon-only case above, if anon cannot be reclaimed there is
+	 * nothing to do without breaching the floor, so scan nothing.
+	 */
+	if (sc->skip_file_reclaim) {
+		if (!can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
+			memset(nr, 0, sizeof(*nr) * NR_LRU_LISTS);
+			return;
+		}
+		scan_balance = SCAN_ANON;
+		goto out;
+	}
+
 	/* If we have no swap space, do not bother scanning anon folios. */
 	if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
 		scan_balance = SCAN_FILE;
@@ -4749,6 +4770,14 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
 	VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
 	VM_WARN_ON_ONCE(!list_empty(list));
 
+	/*
+	 * node_reclaim protects unmapped page cache down to min_unmapped_pages,
+	 * so leave the file type alone; isolate_folios() then falls back to
+	 * anon.
+	 */
+	if (sc->skip_file_reclaim && type == LRU_GEN_FILE)
+		return 0;
+
 	if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
 		return 0;
 
@@ -7949,13 +7978,16 @@ unsigned long node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned i
 		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.
+	 * Each limit only gates its own type of reclaim.  When reclaimable
+	 * slab or unmapped page cache is already at or below its limit, leave
+	 * that type alone even if the other type tripped the gate and brought
+	 * us into node reclaim.
 	 */
 	sc.skip_slab_reclaim =
 		node_page_state_pages(pgdat, NR_SLAB_RECLAIMABLE_B) <=
 		pgdat->min_slab_pages;
+	sc.skip_file_reclaim =
+		node_pagecache_reclaimable(pgdat) <= pgdat->min_unmapped_pages;
 
 	ret = __node_reclaim(pgdat, nr_pages, &sc);
 	clear_bit_unlock(PGDAT_RECLAIM_LOCKED, &pgdat->flags);
-- 
2.34.1



  parent reply	other threads:[~2026-08-21  8:18 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-21  8:17 [RFC PATCH 0/4] mm/vmscan: honour node reclaim limits per type Ridong Chen
2026-08-21  8:17 ` [RFC PATCH 1/4] mm/vmscan: only reclaim slab in node reclaim when over min_slab_pages Ridong Chen
2026-08-21  8:17 ` Ridong Chen [this message]
2026-08-21  8:17 ` [RFC PATCH 3/4] mm/vmscan: drop the combined limit gate in __node_reclaim() Ridong Chen
2026-08-21 12:49   ` Johannes Weiner
2026-08-21  8:17 ` [RFC PATCH 4/4] mm/vmscan: do not skip node reclaim when only anon is reclaimable Ridong Chen
2026-08-21  8:31 ` [RFC PATCH 0/4] mm/vmscan: honour node reclaim limits per type Michal Hocko
2026-08-21  8:58   ` Lorenzo Stoakes (ARM)
2026-08-21  9:21     ` Michal Hocko
2026-08-21 11:16       ` Lorenzo Stoakes (ARM)
2026-08-21 11:26         ` Michal Hocko
2026-08-21 13:43           ` Lorenzo Stoakes (ARM)
2026-08-21  9:07   ` Ridong Chen
2026-08-21  9:24     ` Michal Hocko
2026-08-21 10:52       ` Ridong Chen
2026-08-21 11:02         ` Michal Hocko
2026-08-21 11:10           ` Ridong Chen
2026-08-21 11:20             ` Michal Hocko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260821081741.1340277-3-ridong.chen@linux.dev \
    --to=ridong.chen@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=chenridong@xiaomi.com \
    --cc=david@kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=kasong@tencent.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@kernel.org \
    --cc=qi.zheng@linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=weixugc@google.com \
    --cc=yuanchu@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.