From: Mike Snitzer <snitzer@kernel.org>
To: Matthew Wilcox <willy@infradead.org>, Jan Kara <jack@suse.cz>,
linux-mm@kvack.org, Christian Brauner <brauner@kernel.org>,
linux-fsdevel@vger.kernel.org
Cc: Mike Snitzer <snitzer@kernel.org>, Chuck Lever <cel@kernel.org>,
Jeff Layton <jlayton@kernel.org>,
linux-nfs@vger.kernel.org
Subject: [PATCH v12 5/7] mm, iomap: keep a partially covered page until a DONTCACHE write completes it
Date: Thu, 12 Nov 2026 14:09:08 -0500 [thread overview]
Message-ID: <20261112190911.84702-6-snitzer@kernel.org> (raw)
In-Reply-To: <20261112190911.84702-1-snitzer@kernel.org>
An RWF_DONTCACHE buffered write goes through the page cache like any
buffered write. It marks every folio it creates dropbehind, so
writeback drops the folio as soon as the data of the write is on disk.
A write that starts or ends inside a folio covers that folio only
partially. Usually another write covers the rest of the folio. An
example is the next record of a file written in records that are not a
multiple of the page size. Writeback can drop the folio before the
other write writes its part. Then the other write must first read the
folio back from disk: one read per misaligned write.
Add FGP_DONTCACHE_EDGE for the lookup of such a folio. iomap sets it
when a write starts or ends inside the smallest folio the mapping
allows. A folio created for an edge is not marked dropbehind. A folio
found uptodate already holds the data of the other write, so this write
completes it. Mark it with folio_mark_dropbehind() under the folio
lock, and the writeback that cleans the data of both writes drops it.
With blocks smaller than the folio, the edge is not uptodate yet, so
folio_dontcache_edge_written() also accepts a folio whose blocks outside
this write are uptodate. A larger folio, such as one from readahead, is
left alone.
The kernel cannot know if another write will come, and this commit does
not assume it. It reads only the uptodate state of the folio. An edge
that no other write completes, such as the last page of a file, stays
cached until reclaim. Writeback drops all other folios of the write, as
before.
With this commit, 32 threads write interleaved 47008-byte records to
one XFS file with RWF_DONTCACHE. Device reads per write fell from 0.97
to 0.0005, and throughput rose from 831 to 2281 MiB/s. 47008 bytes is
the transfer size of IO500's ior-hard, in which each rank writes
interleaved records to one shared file. Any record size that is not a
multiple of the page size has the same problem. With 6000-byte records,
device reads per write fell from 0.97 to 0.0001, and throughput rose
from 121 to 488 MiB/s.
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
fs/iomap/buffered-io.c | 26 +++++++++++++++---
include/linux/pagemap.h | 5 ++++
mm/filemap.c | 59 ++++++++++++++++++++++++++++++++++++++++-
3 files changed, 86 insertions(+), 4 deletions(-)
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index d6e2f1ded5ca6..28431192fb2e3 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -798,16 +798,36 @@ EXPORT_SYMBOL_GPL(iomap_is_partially_uptodate);
*/
struct folio *iomap_get_folio(struct iomap_iter *iter, loff_t pos, size_t len)
{
+ struct address_space *mapping = iter->inode->i_mapping;
fgf_t fgp = FGP_WRITEBEGIN;
+ struct folio *folio;
if (iter->flags & IOMAP_NOWAIT)
fgp |= FGP_NOWAIT;
- if (iter->flags & IOMAP_DONTCACHE)
+ if (iter->flags & IOMAP_DONTCACHE) {
+ size_t unit = mapping_min_folio_nrbytes(mapping);
+ size_t off = pos & (unit - 1);
+
fgp |= FGP_DONTCACHE;
+ /* Another write may cover the rest of this unit. */
+ if (off || iter->len < unit) {
+ fgp |= FGP_DONTCACHE_EDGE;
+ len = min_t(size_t, len, unit - off);
+ }
+ }
fgp |= fgf_set_order(len);
- return __filemap_get_folio(iter->inode->i_mapping, pos >> PAGE_SHIFT,
- fgp, mapping_gfp_mask(iter->inode->i_mapping));
+ folio = __filemap_get_folio(mapping, pos >> PAGE_SHIFT, fgp,
+ mapping_gfp_mask(mapping));
+ /*
+ * With blocks smaller than the folio, an edge is not uptodate
+ * until this write completes it.
+ */
+ if ((fgp & FGP_DONTCACHE_EDGE) &&
+ !IS_ERR(folio) && !folio_test_uptodate(folio) &&
+ folio_dontcache_edge_written(folio, pos, len))
+ folio_mark_dropbehind(folio);
+ return folio;
}
EXPORT_SYMBOL_GPL(iomap_get_folio);
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 3f5295d6fd73b..07f7f5190c24c 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -698,6 +698,9 @@ pgoff_t page_cache_prev_miss(struct address_space *mapping,
* * %FGP_NOWAIT - Don't block on the folio lock.
* * %FGP_STABLE - Wait for the folio to be stable (finished writeback)
* * %FGP_DONTCACHE - Uncached buffered IO
+ * * %FGP_DONTCACHE_EDGE - With %FGP_DONTCACHE: the write covers the folio
+ * only partially. A new folio is not marked dropbehind, one found
+ * uptodate is.
* * %FGP_WRITEBEGIN - The flags to use in a filesystem write_begin()
* implementation.
*/
@@ -712,6 +715,7 @@ typedef unsigned int __bitwise fgf_t;
#define FGP_FOR_MMAP ((__force fgf_t)0x00000040)
#define FGP_STABLE ((__force fgf_t)0x00000080)
#define FGP_DONTCACHE ((__force fgf_t)0x00000100)
+#define FGP_DONTCACHE_EDGE ((__force fgf_t)0x00000200)
#define FGF_GET_ORDER(fgf) (((__force unsigned)fgf) >> 26) /* top 6 bits */
#define FGP_WRITEBEGIN (FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_STABLE)
@@ -1307,6 +1311,7 @@ void folio_end_writeback(struct folio *folio);
void folio_end_writeback_no_dropbehind(struct folio *folio);
void folio_end_dropbehind(struct folio *folio);
void folio_mark_dropbehind(struct folio *folio);
+bool folio_dontcache_edge_written(struct folio *folio, loff_t pos, size_t len);
void folio_wait_stable(struct folio *folio);
void __folio_mark_dirty(struct folio *folio, struct address_space *, int warn);
void folio_account_cleaned(struct folio *folio, struct bdi_writeback *wb);
diff --git a/mm/filemap.c b/mm/filemap.c
index 196c385f29801..6740b860939e7 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1696,6 +1696,52 @@ void folio_mark_dropbehind(struct folio *folio)
}
EXPORT_SYMBOL_GPL(folio_mark_dropbehind);
+/* The unit two writes share is the smallest folio the mapping allows. */
+static bool filemap_dontcache_edge_unit(struct address_space *mapping,
+ struct folio *folio)
+{
+ return folio_nr_pages(folio) == mapping_min_folio_nrpages(mapping);
+}
+
+/**
+ * folio_dontcache_edge_written - has another write written the rest?
+ * @folio: locked edge folio
+ * @pos: file offset of the first byte this write covers in @folio
+ * @len: bytes this write covers in @folio
+ *
+ * Return: true if @folio is an edge unit that is uptodate, or whose blocks
+ * that this write does not cover in full are uptodate.
+ */
+bool folio_dontcache_edge_written(struct folio *folio, loff_t pos, size_t len)
+{
+ struct address_space *mapping = folio->mapping;
+ size_t size = folio_size(folio);
+ size_t from, to, bs;
+
+ VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
+
+ if (!mapping || !filemap_dontcache_edge_unit(mapping, folio))
+ return false;
+ if (folio_test_uptodate(folio))
+ return true;
+ if (!mapping->a_ops->is_partially_uptodate)
+ return false;
+
+ /* The blocks this write covers in full are [from, to). */
+ bs = i_blocksize(mapping->host);
+ from = offset_in_folio(folio, pos);
+ to = min(from + len, size);
+ from = round_up(from, bs);
+ to = round_down(to, bs);
+ if (from && !mapping->a_ops->is_partially_uptodate(folio, 0, from))
+ return false;
+ if (to < size &&
+ !mapping->a_ops->is_partially_uptodate(folio, to, size - to))
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(folio_dontcache_edge_written);
+
/**
* folio_end_writeback_no_dropbehind - End writeback against a folio.
* @folio: The folio.
@@ -2088,6 +2134,15 @@ struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
if (fgp_flags & FGP_STABLE)
folio_wait_stable(folio);
+
+ /*
+ * An uptodate edge holds the part of the other write, so this write
+ * completes it.
+ */
+ if ((fgp_flags & FGP_DONTCACHE) && (fgp_flags & FGP_DONTCACHE_EDGE) &&
+ (fgp_flags & FGP_LOCK) && folio_test_uptodate(folio) &&
+ filemap_dontcache_edge_unit(mapping, folio))
+ folio_mark_dropbehind(folio);
no_page:
if (!folio && (fgp_flags & FGP_CREAT)) {
unsigned int min_order = mapping_min_folio_order(mapping);
@@ -2125,7 +2180,9 @@ struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
/* Init accessed so avoid atomic mark_page_accessed later */
if (fgp_flags & FGP_ACCESSED)
__folio_set_referenced(folio);
- if (fgp_flags & FGP_DONTCACHE)
+ /* A new edge waits for the write that completes it. */
+ if ((fgp_flags & FGP_DONTCACHE) &&
+ !(fgp_flags & FGP_DONTCACHE_EDGE))
__folio_set_dropbehind(folio);
err = filemap_add_folio(mapping, folio, index, gfp);
--
2.52.0
next prev parent reply other threads:[~2026-11-12 19:09 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 19:09 [PATCH v12 0/7] mm, iomap, NFSD: stop DONTCACHE writes reading back shared pages Mike Snitzer
2026-11-12 19:09 ` [PATCH v12 1/7] mm: move clearing the dropbehind flag of a found folio into a helper Mike Snitzer
2026-11-12 19:09 ` [PATCH v12 2/7] mm: take a folio off WB_DONTCACHE_DIRTY only if it was counted there Mike Snitzer
2026-11-12 19:09 ` [PATCH v12 3/7] mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats Mike Snitzer
2026-11-12 19:09 ` [PATCH v12 4/7] mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it Mike Snitzer
2026-11-12 19:09 ` Mike Snitzer [this message]
2026-11-12 19:09 ` [PATCH v12 6/7] mm, iomap: let a DONTCACHE writer complete its shared pages itself Mike Snitzer
2026-11-12 19:09 ` [PATCH v12 7/7] NFSD: drop a shared boundary page only once both WRITEs have written it Mike Snitzer
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=20261112190911.84702-6-snitzer@kernel.org \
--to=snitzer@kernel.org \
--cc=brauner@kernel.org \
--cc=cel@kernel.org \
--cc=jack@suse.cz \
--cc=jlayton@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-nfs@vger.kernel.org \
--cc=willy@infradead.org \
/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