From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
To: Andrew Morton <akpm@linux-foundation.org>,
Linus Torvalds <torvalds@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Matthew Wilcox <willy@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>, Jan Kara <jack@suse.cz>,
Hugh Dickins <hughd@google.com>,
Christian Brauner <brauner@kernel.org>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Harry Yoo <harry@kernel.org>
Cc: Miklos Szeredi <miklos@szeredi.hu>,
Miaohe Lin <linmiaohe@huawei.com>,
Naoya Horiguchi <nao.horiguchi@gmail.com>,
Oscar Salvador <osalvador@suse.de>,
"Liam R. Howlett" <liam@infradead.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Brendan Jackman <brendan.jackman@linux.dev>,
Johannes Weiner <hannes@cmpxchg.org>, Zi Yan <ziy@nvidia.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Nico Pache <nico.pache@linux.dev>,
Ryan Roberts <ryan.roberts@arm.com>, Dev Jain <dev.jain@arm.com>,
Barry Song <baohua@kernel.org>, Lance Yang <lance.yang@linux.dev>,
Usama Arif <usama.arif@linux.dev>,
Kairui Song <kasong@tencent.com>, Qi Zheng <qi.zheng@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Axel Rasmussen <axelrasmussen@google.com>,
Yuanchu Xie <yuanchu@google.com>, Wei Xu <weixugc@google.com>,
Baoquan He <baoquan.he@linux.dev>,
"Paul E. McKenney" <paulmck@kernel.org>,
Pedro Falcato <pfalcato@suse.de>,
Luis Chamberlain <mcgrof@kernel.org>,
Ilya Gladyshev <ilya.gladyshev@linux.dev>,
Jaegeuk Kim <jaegeuk@kernel.org>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org,
linux-fsdevel@vger.kernel.org, fuse-devel@lists.linux.dev,
kernel-team@meta.com, "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Subject: [RFC PATCH v2 13/18] mm/filemap: read from the page cache without folio references
Date: Wed, 4 Nov 2026 13:01:09 +0000 [thread overview]
Message-ID: <20261104130118.2507513-14-kas@kernel.org> (raw)
In-Reply-To: <20261104130118.2507513-1-kas@kernel.org>
From: Linus Torvalds <torvalds@linux-foundation.org>
Small reads of one file from many CPUs all take and drop a reference on
the same folio, and its refcount bounces between the CPUs.
Before taking a reference, copy what can be copied under rcu_read_lock()
from folios with PG_rcu_free, with page faults disabled. A flagged
folio is not reused before rcu_read_unlock(), so a folio still in its
slot after the flag test is this file's page until then. A folio that
leaves the page cache during the copy reads as the file did when it
left. Read i_size between the flag and the second look at the slot: a
write that extends the file fills the slot before it moves i_size.
Leave to the slow path a folio that is not uptodate, needs readahead or
dropbehind, is not hot, or has a poisoned page. Leave large folios
there too: looking one up for every page is slower than one reference
for all of its pages. A split clears PG_head before the new folios
replace the old one in their slots, so copy an order-0 folio only at its
own index. Copy at most 512 KiB per rcu_read_lock() section, and skip
files that never had a folio flagged or that are writably mapped where
flush_dcache_folio() is needed.
Declare the folio batch in the loop: with stack auto-initialisation,
the compiler zeroes it where it is declared, and the copy without
references does not use it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[kas: i_size before the second look at the slot, poisoned folio check,
large folios left to the slow path, 512 KiB sections, folio batch in
the loop, shorter comments and changelog]
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
mm/filemap.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 175 insertions(+), 3 deletions(-)
diff --git a/mm/filemap.c b/mm/filemap.c
index b732ecaa78a9..e99705a66c1d 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2821,6 +2821,168 @@ static void filemap_end_dropbehind_read(struct folio *folio)
}
}
+/*
+ * Whether filemap_read_rcu_batch() can copy from @folio, or has to leave it
+ * to the slow path, which holds a reference for readahead, dropbehind and
+ * folio_mark_accessed(), and deals with poisoned pages. @last_index is the
+ * page the previous copy ended in.
+ */
+static bool filemap_read_rcu_can_copy(struct folio *folio, pgoff_t index,
+ pgoff_t last_index)
+{
+ if (!folio_test_uptodate(folio))
+ return false;
+ if (folio_test_readahead(folio) || folio_test_dropbehind(folio))
+ return false;
+ /* One reference covers all of a large folio's pages */
+ if (folio_test_large(folio))
+ return false;
+ /* A split clears PG_head before it puts the new folios in their slots */
+ if (folio->index != index)
+ return false;
+ /* memory_failure() waits only for readers that got past this */
+ if (folio_test_hwpoison(folio))
+ return false;
+
+ /*
+ * The slow path does not call folio_mark_accessed() on the folio that
+ * the previous read ended in, so copying from it skips nothing. The
+ * folio's size can change without a reference: compare pages instead.
+ */
+ if (index == last_index)
+ return true;
+
+ /* Otherwise only if folio_mark_accessed() would leave it as it is */
+ if (lru_gen_enabled()) {
+ unsigned long all = LRU_REFS_FLAGS | BIT(PG_workingset);
+
+ return folio_test_unevictable(folio) ||
+ (READ_ONCE(folio->flags.f) & all) == all;
+ }
+ return folio_test_referenced(folio) && !folio_test_idle(folio) &&
+ (folio_test_active(folio) || folio_test_unevictable(folio));
+}
+
+/*
+ * Return the folio at @pos if it is this file's page until rcu_read_unlock(),
+ * and read i_size into @isize. A folio with PG_rcu_free is freed only after
+ * an RCU grace period. Without a reference, the folio that xas_load()
+ * returned can be freed and its memory reused as another folio before the
+ * flag is tested, so look at the slot again after the flag.
+ */
+static struct folio *filemap_read_rcu_lookup(struct xa_state *xas,
+ struct inode *inode, loff_t pos, loff_t *isize)
+{
+ struct folio *folio;
+
+ xas_set(xas, pos >> PAGE_SHIFT);
+ folio = xas_load(xas);
+ if (xas_retry(xas, folio) || !folio || xa_is_value(folio))
+ return NULL;
+
+ /* Not folio_test_rcu_free(): it may not be a folio any more */
+ if (!(READ_ONCE(folio->flags.f) & __PG_RCU_FREE))
+ return NULL;
+ /* Read i_size and slot after the flag: it implies uptodate */
+ smp_rmb();
+ *isize = i_size_read(inode);
+ if (pos >= *isize)
+ return NULL;
+ /* i_size_read() is an acquire; a write fills the slot first */
+ if (xas_reload(xas) != folio)
+ return NULL;
+ /* Read the rest of the folio only once the slot holds it */
+ smp_rmb();
+ return folio;
+}
+
+/* Bytes one RCU read section copies at most: it holds up grace periods */
+#define FILEMAP_RCU_READ_MAX SZ_512K
+
+/*
+ * Copy from folios with PG_rcu_free without taking references, at most
+ * FILEMAP_RCU_READ_MAX bytes under one rcu_read_lock(). Return true if that
+ * much was copied and the read is not done. *last_index is the page the
+ * previous copy ended in, set as the slow path sets last_pos.
+ */
+static bool filemap_read_rcu_batch(struct kiocb *iocb, struct iov_iter *iter,
+ pgoff_t *last_index, size_t *copied)
+{
+ struct address_space *mapping = iocb->ki_filp->f_mapping;
+ struct inode *inode = mapping->host;
+ XA_STATE(xas, &mapping->i_pages, 0);
+ size_t budget = FILEMAP_RCU_READ_MAX;
+ bool more = false;
+
+ /* flush_dcache_folio() is not known to be safe without a reference */
+ if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE &&
+ mapping_writably_mapped(mapping))
+ return false;
+
+ /* A user fault cannot sleep in the RCU read section: it ends the copy */
+ rcu_read_lock();
+ pagefault_disable();
+ while (iov_iter_count(iter)) {
+ loff_t pos = iocb->ki_pos;
+ pgoff_t index = pos >> PAGE_SHIFT;
+ size_t offset = offset_in_page(pos);
+ struct folio *folio;
+ size_t bytes, n;
+ loff_t isize;
+
+ if (!budget) {
+ more = true;
+ break;
+ }
+ folio = filemap_read_rcu_lookup(&xas, inode, pos, &isize);
+ if (!folio)
+ break;
+ if (!filemap_read_rcu_can_copy(folio, index, *last_index))
+ break;
+
+ bytes = min_t(loff_t, isize - pos, iov_iter_count(iter));
+ bytes = min3(bytes, budget, PAGE_SIZE - offset);
+ /* Not _nofault(): its usercopy check reads the folio's order */
+ n = copy_page_to_iter(folio_page(folio, index - folio->index),
+ offset, bytes, iter);
+ *copied += n;
+ iocb->ki_pos += n;
+ budget -= n;
+ if (n)
+ *last_index = (pos + n - 1) >> PAGE_SHIFT;
+ if (n < bytes)
+ break;
+ }
+ pagefault_enable();
+ rcu_read_unlock();
+
+ return more;
+}
+
+/*
+ * Copy from the page cache without folio references, in batches, and
+ * return how much was copied. The slow path reads the rest.
+ */
+static size_t filemap_read_rcu(struct kiocb *iocb, struct iov_iter *iter,
+ loff_t last_pos)
+{
+ /* The page the previous read ended in; ULONG_MAX if none */
+ pgoff_t last_index = (last_pos - 1) >> PAGE_SHIFT;
+ size_t copied = 0;
+
+ /* Not on an inode that never had a folio flagged */
+ if (!IS_ENABLED(CONFIG_PAGE_RCU_FREE) ||
+ !mapping_rcu_read(iocb->ki_filp->f_mapping))
+ return 0;
+
+ while (filemap_read_rcu_batch(iocb, iter, &last_index, &copied)) {
+ if (fatal_signal_pending(current))
+ break;
+ cond_resched();
+ }
+ return copied;
+}
+
/**
* filemap_read - Read data from the page cache.
* @iocb: The iocb to read.
@@ -2841,11 +3003,11 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
struct file_ra_state *ra = &filp->f_ra;
struct address_space *mapping = filp->f_mapping;
struct inode *inode = mapping->host;
- struct folio_batch fbatch;
int i, error = 0;
bool writably_mapped;
loff_t isize, end_offset;
loff_t last_pos = ra->prev_pos;
+ size_t fast_read;
if (unlikely(iocb->ki_pos < 0))
return -EINVAL;
@@ -2855,9 +3017,19 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
return 0;
iov_iter_truncate(iter, inode->i_sb->s_maxbytes - iocb->ki_pos);
- folio_batch_init(&fbatch);
+
+ fast_read = filemap_read_rcu(iocb, iter, last_pos);
+ if (fast_read) {
+ already_read += fast_read;
+ last_pos = iocb->ki_pos;
+ if (!iov_iter_count(iter))
+ goto out;
+ }
do {
+ struct folio_batch fbatch;
+
+ folio_batch_init(&fbatch);
cond_resched();
/*
@@ -2931,9 +3103,9 @@ ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
filemap_end_dropbehind_read(folio);
folio_put(folio);
}
- folio_batch_init(&fbatch);
} while (iov_iter_count(iter) && iocb->ki_pos < isize && !error);
+out:
file_accessed(filp);
ra->prev_pos = last_pos;
return already_read ? already_read : error;
--
2.54.0
next prev parent reply other threads:[~2026-11-04 13:02 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 13:00 [RFC PATCH v2 00/18] mm: read the page cache without folio references Kiryl Shutsemau (Meta)
2026-11-04 13:00 ` [RFC PATCH v2 01/18] mm: free PG_rcu_free folios only after an RCU grace period Kiryl Shutsemau (Meta)
2026-11-05 20:37 ` David Hildenbrand (Arm)
2026-11-04 13:00 ` [RFC PATCH v2 02/18] splice: don't steal page cache folios Kiryl Shutsemau (Meta)
2026-11-04 16:05 ` David Hildenbrand (Arm)
2026-11-05 14:55 ` Kiryl Shutsemau
2026-11-04 13:00 ` [RFC PATCH v2 03/18] fuse: don't move a folio with PG_rcu_free into a fuse file Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 04/18] mm: count the pages waiting for a grace period Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 05/18] mm: expedite the grace period when a CPU has many deferred frees Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 06/18] mm: free the pages waiting for a grace period before OOM Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 07/18] mm: free the pages waiting for a grace period before isolation fails Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 08/18] mm/compaction: free the migrated folios that wait for a grace period Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 09/18] mm/memory-failure: handle folios with PG_rcu_free Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 10/18] mm: have folio_mark_accessed() return whether the folio is hot Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 11/18] mm/filemap: mark a folio accessed in one place in filemap_read() Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 12/18] mm/filemap: set PG_rcu_free on the folios that reads keep coming back to Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` Kiryl Shutsemau (Meta) [this message]
2026-11-04 13:01 ` [RFC PATCH v2 14/18] mm/filemap: read large folios without folio references Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 15/18] mm/filemap: don't store f_ra.prev_pos for FMODE_RANDOM files Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 16/18] mm/shmem: count accesses to a folio the way filemap_read() does Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 17/18] mm/shmem: read without folio references Kiryl Shutsemau (Meta)
2026-11-05 13:03 ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 18/18] mm/filemap: add a debugfs switch for lockless page cache reads Kiryl Shutsemau (Meta)
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=20261104130118.2507513-14-kas@kernel.org \
--to=kas@kernel.org \
--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=brauner@kernel.org \
--cc=brendan.jackman@linux.dev \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=fuse-devel@lists.linux.dev \
--cc=hannes@cmpxchg.org \
--cc=harry@kernel.org \
--cc=hughd@google.com \
--cc=ilya.gladyshev@linux.dev \
--cc=jack@suse.cz \
--cc=jaegeuk@kernel.org \
--cc=kasong@tencent.com \
--cc=kernel-team@meta.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linmiaohe@huawei.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mcgrof@kernel.org \
--cc=mhocko@suse.com \
--cc=miklos@szeredi.hu \
--cc=nao.horiguchi@gmail.com \
--cc=nico.pache@linux.dev \
--cc=osalvador@suse.de \
--cc=paulmck@kernel.org \
--cc=pfalcato@suse.de \
--cc=qi.zheng@linux.dev \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=torvalds@linux-foundation.org \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=weixugc@google.com \
--cc=willy@infradead.org \
--cc=yuanchu@google.com \
--cc=ziy@nvidia.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.