mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Zhang Peng <zippermonkey@icloud.com>
To: Andrew Morton <akpm@linux-foundation.org>,
	 Johannes Weiner <hannes@cmpxchg.org>,
	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>,
	Baoquan He <baoquan.he@linux.dev>,
	 Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	 Zhang Peng <bruzzhang@tencent.com>
Subject: [PATCH v6 5/5] mm/vmscan: batch dirty TLB flushes before pageout
Date: Sat, 31 Oct 2026 18:35:12 +0800	[thread overview]
Message-ID: <20261031-batch-tlb-flush-v6-5-190539839eec@tencent.com> (raw)
In-Reply-To: <20261031-batch-tlb-flush-v6-0-190539839eec@tencent.com>

Currently reclaim flushes the TLB for every dirty folio before pageout.
On systems with many CPUs, the resulting IPIs can be a significant
bottleneck.

Collect dirty folios in a folio_batch instead. When the batch is full,
lock and revalidate each folio, flush pending writable TLB entries once,
and page out the folios that remain eligible. Flush a final partial batch
at the end of the scan.

Folios are unlocked while waiting in the batch so swap faults can make
progress. After reacquiring the folio lock, recheck writeback, mappings,
and DMA pins; a concurrent swapin may have installed a PTE and allowed a
parallel GUP pin while the folio was unlocked. Keep each unmapped folio
locked across the TLB flush and pageout to prevent swap faults from
remapping it before I/O starts. The folio lock alone does not exclude GUP.

Save the original batch count and reinitialize the batch, then add back
folios that pass validation. Reinitialization leaves the array intact,
and each add writes at or before the current read index. This reuses
the batch without overwriting unread entries or needing a second array.

Suggested-by: Kairui Song <kasong@tencent.com>
Signed-off-by: Zhang Peng <bruzzhang@tencent.com>
---
 mm/vmscan.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 101 insertions(+), 14 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 061414ff45bb..1150361e7d52 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1334,6 +1334,92 @@ static enum folio_pageout_result folio_try_pageout(struct folio *folio,
 	unreachable();
 }
 
+static void folio_batch_pageout(struct folio_batch *batch,
+		struct reclaim_folio_state *state, struct scan_control *sc,
+		struct reclaim_stat *stat, struct swap_io_ctx *ctx,
+		struct list_head *folio_list)
+{
+	unsigned int nr = folio_batch_count(batch);
+	unsigned int i;
+
+	/*
+	 * Reuse this private batch to collect the locked folios. Reinit
+	 * leaves the array intact; nr preserves the original input count.
+	 * Adding each accepted folio writes at or before the current read
+	 * index, so entries we have not read yet cannot be overwritten.
+	 */
+	folio_batch_reinit(batch);
+
+	/*
+	 * Revalidate after taking the lock: writeback may have started, or
+	 * a swap fault may have remapped the folio and allowed GUP to pin it.
+	 * Keeping an unmapped folio locked prevents swap faults from mapping
+	 * it again before the TLB flush and pageout. The lock alone does not
+	 * exclude GUP, so existing mappings and pins must both be checked.
+	 */
+	for (i = 0; i < nr; i++) {
+		struct folio *folio = batch->folios[i];
+
+		if (!folio_trylock(folio)) {
+			list_add(&folio->lru, &state->ret_folios);
+			continue;
+		}
+
+		VM_WARN_ON_FOLIO(folio_test_lru(folio), folio);
+
+		/* The folio may have changed while it was unlocked. */
+		if (folio_test_writeback(folio) || folio_mapped(folio) ||
+		    folio_maybe_dma_pinned(folio)) {
+			folio_unlock(folio);
+			list_add(&folio->lru, &state->ret_folios);
+			continue;
+		}
+
+		folio_batch_add(batch, folio);
+	}
+	nr = folio_batch_count(batch);
+
+	if (!nr)
+		return;
+
+	/*
+	 * Flush writable TLB entries before any folio in the batch is
+	 * submitted for I/O.
+	 */
+	try_to_unmap_flush_dirty();
+
+	for (i = 0; i < nr; i++) {
+		struct folio *folio = batch->folios[i];
+
+		switch (folio_try_pageout(folio, sc, ctx, folio_list)) {
+		case FOLIO_PAGEOUT_ACTIVATE:
+			goto activate_locked;
+		case FOLIO_PAGEOUT_KEEP_LOCKED:
+			goto keep_locked;
+		case FOLIO_PAGEOUT_KEEP_UNLOCKED:
+			goto keep;
+		case FOLIO_PAGEOUT_RECLAIM:
+			break;	/* folio is locked; try to free it below */
+		}
+
+		switch (folio_try_reclaim_free(folio, state, sc)) {
+		case FOLIO_RECLAIM_ACTIVATE:
+			goto activate_locked;
+		case FOLIO_RECLAIM_KEEP:
+			goto keep_locked;
+		case FOLIO_RECLAIM_DONE:
+			continue;
+		}
+activate_locked:
+		folio_activate_locked(folio, stat);
+keep_locked:
+		folio_unlock(folio);
+keep:
+		list_add(&folio->lru, &state->ret_folios);
+	}
+	folio_batch_reinit(batch);
+}
+
 static bool folio_try_unmap(struct folio *folio, struct reclaim_stat *stat,
 		unsigned int nr_pages)
 {
@@ -1375,6 +1461,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
 		struct mem_cgroup *memcg)
 {
 	struct reclaim_folio_state state = {};
+	struct folio_batch pageout_folios;
 	LIST_HEAD(demote_folios);
 	unsigned int nr_demoted = 0;
 	unsigned int pgactivate = 0;
@@ -1383,6 +1470,7 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
 
 	INIT_LIST_HEAD(&state.ret_folios);
 	folio_batch_init(&state.free_folios);
+	folio_batch_init(&pageout_folios);
 	memset(stat, 0, sizeof(*stat));
 	cond_resched();
 	do_demote_pass = can_demote(pgdat->node_id, sc, memcg);
@@ -1679,21 +1767,16 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
 			if (!sc->may_writepage)
 				goto keep_locked;
 			/*
-			 * Folio is dirty. Flush the TLB if a writable entry
-			 * potentially exists to avoid CPU writes after I/O
-			 * starts and then write it out here.
+			 * Drop the lock so swap faults finding this folio
+			 * via swap cache lookup can make progress; the
+			 * recheck that this necessitates is documented in
+			 * folio_batch_pageout().
 			 */
-			try_to_unmap_flush_dirty();
-			switch (folio_try_pageout(folio, sc, &ctx, folio_list)) {
-			case FOLIO_PAGEOUT_ACTIVATE:
-				goto activate_locked;
-			case FOLIO_PAGEOUT_KEEP_LOCKED:
-				goto keep_locked;
-			case FOLIO_PAGEOUT_KEEP_UNLOCKED:
-				goto keep;
-			case FOLIO_PAGEOUT_RECLAIM:
-				break;	/* folio is locked; try to free it below */
-			}
+			folio_unlock(folio);
+			if (!folio_batch_add(&pageout_folios, folio))
+				folio_batch_pageout(&pageout_folios, &state,
+						    sc, stat, &ctx, folio_list);
+			continue;
 		}
 
 		switch (folio_try_reclaim_free(folio, &state, sc)) {
@@ -1721,6 +1804,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
 		VM_BUG_ON_FOLIO(folio_test_lru(folio) ||
 				folio_test_unevictable(folio), folio);
 	}
+	if (folio_batch_count(&pageout_folios)) {
+		folio_batch_pageout(&pageout_folios, &state, sc, stat,
+				    &ctx, folio_list);
+	}
 	/* 'folio_list' is always empty here */
 
 	/* Migrate folios selected for demotion */

-- 
2.56.0


      parent reply	other threads:[~2026-10-31 10:35 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-31 10:35 [PATCH v6 0/5] mm: batch TLB flushing for dirty folios in vmscan Zhang Peng
2026-10-31 10:35 ` [PATCH v6 1/5] mm/vmscan: introduce folio_activate_locked() helper Zhang Peng
2026-10-31 10:35 ` [PATCH v6 2/5] mm/vmscan: extract folio reclaim freeing from shrink_folio_list() Zhang Peng
2026-10-31 10:35 ` [PATCH v6 3/5] mm/vmscan: extract folio pageout " Zhang Peng
2026-10-31 10:35 ` [PATCH v6 4/5] mm/vmscan: extract folio unmap logic into folio_try_unmap() Zhang Peng
2026-10-31 10:35 ` Zhang Peng [this message]

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=20261031-batch-tlb-flush-v6-5-190539839eec@tencent.com \
    --to=zippermonkey@icloud.com \
    --cc=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=bruzzhang@tencent.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®