[RFC PATCH v2 14/18] mm/filemap: read large folios without folio references

From: Kiryl Shutsemau (Meta)

Date: Wed Nov 04 2026 - 08:09:59 EST


A large folio is in all the slots of its range or in none. Once the
slot of the last page to copy holds it too, the pages in between are
this folio's: copy them after one lookup. The folio's size is read
without a reference and only picks the page to check; a split after the
check copies PG_rcu_free to the new folios. A split can also change the
order and PG_head under the reader: clamp the order, and read
PG_has_hwpoisoned without folio_test_has_hwpoisoned(), which asserts
PG_head.

Copy page by page with _copy_to_iter(): the hardened usercopy check of
copy_to_iter() reads the folio's order, which a split can change.

Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
---
mm/filemap.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 64 insertions(+), 8 deletions(-)

diff --git a/mm/filemap.c b/mm/filemap.c
index e99705a66c1d..182ae7d5db56 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2834,15 +2834,16 @@ static bool filemap_read_rcu_can_copy(struct folio *folio, pgoff_t index,
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)
+ if (!folio_test_large(folio) && folio->index != index)
return false;
/* memory_failure() waits only for readers that got past this */
if (folio_test_hwpoison(folio))
return false;
+ /* Not folio_test_has_hwpoisoned(): it asserts PG_head, a split clears it */
+ if (folio_test_large(folio) &&
+ test_bit(PG_has_hwpoisoned, &folio->_flags_1))
+ return false;

/*
* The slow path does not call folio_mark_accessed() on the folio that
@@ -2896,6 +2897,62 @@ static struct folio *filemap_read_rcu_lookup(struct xa_state *xas,
return folio;
}

+/*
+ * How much of @bytes, starting @offset into page @index, can be copied from
+ * @folio. A folio is in all the slots of its range or in none: when the slot
+ * of the last page holds it too, so do the slots in between, and the pages
+ * are this folio's.
+ */
+static size_t filemap_read_rcu_size(struct xa_state *xas, struct folio *folio,
+ pgoff_t index, size_t offset, size_t bytes)
+{
+ pgoff_t end = index + DIV_ROUND_UP(offset + bytes, PAGE_SIZE);
+ unsigned int order;
+ pgoff_t next;
+
+ if (end - index > 1 && folio_test_large(folio)) {
+ /* A split can change it under us: it only picks the page to check */
+ order = min(folio_large_order(folio), MAX_PAGECACHE_ORDER);
+ next = folio->index + (1UL << order);
+ if (next > index && next < end)
+ end = next;
+ xas_set(xas, end - 1);
+ /* A split after this copies PG_rcu_free to the new folios */
+ if (xas_load(xas) == folio)
+ return min(bytes, (end - index) * PAGE_SIZE - offset);
+ }
+ return min(bytes, PAGE_SIZE - offset);
+}
+
+/*
+ * Copy @bytes from @folio, starting @offset into its page at @index. Page
+ * by page, as copy_page_to_iter() does, without its checks for each call.
+ * Not copy_to_iter(): its usercopy check reads the folio's order, which a
+ * split can change.
+ */
+static size_t filemap_read_rcu_copy(struct folio *folio, pgoff_t index,
+ size_t offset, size_t bytes, struct iov_iter *iter)
+{
+ struct page *page = folio_page(folio, index - folio->index);
+ size_t n = 0;
+
+ while (n < bytes) {
+ size_t len = min(bytes - n, PAGE_SIZE - offset);
+ size_t done;
+ void *kaddr;
+
+ kaddr = kmap_local_page(page);
+ done = _copy_to_iter(kaddr + offset, len, iter);
+ kunmap_local(kaddr);
+ n += done;
+ if (done < len)
+ break;
+ offset = 0;
+ page++;
+ }
+ return n;
+}
+
/* Bytes one RCU read section copies at most: it holds up grace periods */
#define FILEMAP_RCU_READ_MAX SZ_512K

@@ -2941,10 +2998,9 @@ static bool filemap_read_rcu_batch(struct kiocb *iocb, struct iov_iter *iter,
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);
+ bytes = filemap_read_rcu_size(&xas, folio, index, offset,
+ min(bytes, budget));
+ n = filemap_read_rcu_copy(folio, index, offset, bytes, iter);
*copied += n;
iocb->ki_pos += n;
budget -= n;
--
2.54.0