Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache
@ 2026-10-15 23:32 Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 01/10] NFSD: cut a misaligned direct-mode WRITE at page boundaries Mike Snitzer
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:32 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

Hi,

This series refines the heuristics NFSD_IO_DIRECT already uses, mostly
on the WRITE path and in one corner of the READ path: the io_cache_write
and io_cache_read modes where an aligned request goes to the filesystem
as O_DIRECT, with DONTCACHE for the parts a misaligned WRITE cannot issue
directly.

It does not knowingly undercut anything the existing upstream code is
built on.  An aligned WRITE is still one O_DIRECT write and an aligned
READ is still one O_DIRECT read.  A misaligned WRITE is still split into
a direct middle and buffered ends, and by default those ends are still
DONTCACHE where the filesystem supports it.  io_cache_read and
io_cache_write mean what they did, and nothing new is asked of the
filesystem.  What changes is where the split is cut and when it is worth
making, which READs are worth expanding, how often a synchronous WRITE
is persisted, and what happens to the page two misaligned WRITEs share.

1/10 is a stable fix.  A misaligned WRITE is split into a direct middle
and buffered ends, and the middle was cut at the filesystem's DIO offset
alignment.  Where the page is larger than that alignment (16 and 64 KiB
pages over 4096-byte blocks, or 4 KiB pages over 512-byte blocks) the
middle then starts or stops inside the page a buffered end writes, its
invalidation drops that page, and whichever of the two neighbouring
WRITEs comes second reads it back from the device.  A misaligned end is
now cut at a multiple of the larger of the alignment and the page size.
An end that is already DIO-aligned gets no buffered segment and stays
where it is, so a DIO-aligned WRITE is still issued direct on every page
size.  Such an end can share its page with a buffered neighbour, but
NFSD's direct WRITEs are synchronous and XFS holds its IOLOCK across
iomap_dio_rw(), the page cache invalidation after the write included,
so the neighbour cannot dirty that page in between and make the
invalidation fail.

2/10 also marks the direct middle IOCB_DONTCACHE, for when XFS
re-issues it as buffered I/O (-ENOTBLK) because something else has
dirtied page cache in its range, such as a local process writing the
exported file through mmap, which XFS's IOLOCK does not keep out.

3/10 splits a misaligned WRITE only for a direct middle of at least
direct_misaligned_min_bytes (debugfs, default 8192), and moves the
segment flag decisions into nfsd_write_dio_iters_init(), both from
Christoph's suggested diff.  The threshold counts bytes: with 1/10 a
split WRITE's middle is whole pages, and a threshold of two pages would
demand a 32 KiB middle on 16 KiB pages and 128 KiB on 64 KiB pages.

4/10 (Jonathan) adds direct_misaligned_dontcache (default Y), which
selects whether the parts that are not O_DIRECT are DONTCACHE or
cached.

5/10 stops expanding a READ smaller than its alignment into one or two
whole aligned device reads.  The check sits in nfsd_direct_read_ok(),
beside the last-operation-in-COMPOUND check now in nfsd-testing.

6/10 persists a FILE_SYNC or DATA_SYNC WRITE that is split, or issued
buffered, once after all of its segments, instead of once per segment:
one fsync where there were up to three.  A WRITE issued whole as
O_DIRECT keeps IOCB_DSYNC, so iomap can still complete a DATA_SYNC
overwrite with FUA writes.

7/10 is an mm patch: folio_mark_dropbehind() marks an existing folio
dropbehind and keeps WB_DONTCACHE_DIRTY balanced when the folio is
already dirty, as folio_mark_dirty() does for PG_dirty where
folio_set_dirty() only sets the flag.  Marking an already dirty folio
with folio_set_dropbehind() instead drives the counter negative, and
the DONTCACHE writeback kick then stops writing anything back for every
writer on that bdi_writeback.

8/10 is also mm: it reports WB_DONTCACHE_DIRTY in the bdi debugfs stats,
as BdiDontcacheDirty and per bdi_writeback as WbDontcacheDirty, summed
over the per-cpu batches since the flusher targets the summed value.
The counter drives the DONTCACHE writeback kick but was not reported
anywhere; this is how the balance 7/10 keeps was observed.  It carries
Fixes: for the commit that added the counter.

7/10 and 8/10 are Cc'd to linux-mm and need an mm ack.

9/10 calls folio_mark_dropbehind() to keep the page two misaligned
WRITEs share until both have written it, with no state in NFSD: 704
device reads for 42895 WRITEs where there were 45144 for 45664
(47008-byte records, 32 interleaved writers, 4 KiB pages), and on a
four-server pNFS flexfiles rig 0.003 device reads per record instead of
0.014-0.015, about 2.9 GiB of page-cache growth instead of 11 GiB, and
2.4% to 4.9% more write throughput.

10/10 adds tracepoints for how each direct-mode READ and WRITE was
serviced, and why a WRITE was not issued direct.

The series applies to cel/nfsd-testing (78c334bc5ae6) and each patch
builds with W=1 with no warnings.

Changes since v3:
- Dropped "NFSD: Enable return of an updated stable_how to NFS
  clients" and "NFSD: add direct-mode WRITE settings that persist each
  WRITE" (v3 8/9 and 9/9).  The rest of the series does not depend on
  them.
- New 1/10, NFSD: cut a misaligned direct-mode WRITE at page
  boundaries (Cc: stable).  v3 cut the direct middle where the DIO
  offset alignment falls, which is a page boundary only when that
  alignment is the page size.
- Rebased onto the current cel/nfsd-testing.  5/10's size check moved
  into nfsd_direct_read_ok().
- Descriptions, comments and nfsd-io-modes.rst corrected throughout,
  among them: 2/10 says what can still make XFS re-issue the middle
  buffered once 1/10 keeps it off the pages the buffered ends share;
  3/10 says why a WRITE misaligned in memory is not split; 4/10 no
  longer misstates what the split threshold decides.
- 3/10: the threshold is direct_misaligned_min_bytes (default 8192)
  instead of direct_misaligned_num_pages (default 2).  On 4 KiB pages
  the default is unchanged.  The debugfs file is renamed with its unit,
  so a value written to the old name is not read as bytes.
- 6/10: a WRITE issued whole as O_DIRECT keeps IOCB_DSYNC; v3 turned a
  DATA_SYNC overwrite into a plain write plus an fsync.
- New 7/10, mm: add folio_mark_dropbehind().  v3 marked an already
  dirty folio dropbehind directly; the clean that follows subtracts it
  from WB_DONTCACHE_DIRTY, which never counted it, so the counter
  drifted negative and stopped the DONTCACHE writeback kick for the
  whole bdi_writeback.
- New 8/10, mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats.  Reports
  the counter 7/10 keeps balanced; Fixes: Jeff's commit that added it.
- 9/10: claims a page only where a buffered segment covers it partly, at
  either end of any segment, so a multi-page start or end segment and a
  sub-page WRITE are handled; claims for a split WRITE at any DIO offset
  alignment, since 1/10 keeps the direct middle out of the claimed
  pages; does not claim when the mapping's minimum folio is larger than
  a page; allocates with __GFP_WRITE; does not mark a page a short
  write did not reach; a FILE_SYNC or DATA_SYNC WRITE marks its shared
  pages after its own fsync, when they are clean, so they are not
  counted as DONTCACHE dirty and do not wake the writeback kick.  The
  description no longer says marking must not be accounted or that the
  kick targets marked folios.
- 10/10: the DONTCACHE state is its own tracepoint argument instead of a
  bit ORed into the disposition (nsegs is dropped; it follows from the
  segment sizes); the redundant #include "vfs.h" is gone;
  NFSD_WRITE_DIO_NO_ALIGN is renamed NFSD_WRITE_DIO_NO_ALIGNMENT to
  match its string; mem_offset is 0 for a WRITE with no payload.

v3: https://lore.kernel.org/linux-nfs/20261001045502.48381-1-snitzer@kernel.org/
v2: https://lore.kernel.org/linux-nfs/20260929231329.22018-1-snitzer@kernel.org/
v1: https://lore.kernel.org/linux-nfs/20260929173423.16149-1-snitzer@kernel.org/

Thanks,
Mike

Jonathan Flynn (1):
  NFSD: add direct_misaligned_dontcache debugfs knob

Mike Snitzer (9):
  NFSD: cut a misaligned direct-mode WRITE at page boundaries
  NFSD: mark the direct middle of a split WRITE IOCB_DONTCACHE as well
  NFSD: only split a direct-mode WRITE for a worthwhile direct middle
  NFSD: do not use direct I/O for a READ smaller than its alignment
  NFSD: persist a synchronous direct-mode WRITE once, after all of its segments
  mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it
  mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats
  NFSD: keep boundary page of a split direct-mode WRITE until both writers complete
  NFSD: add tracing for how direct-mode READ and WRITE are serviced

 .../filesystems/nfs/nfsd-io-modes.rst         |  93 +++++-
 fs/nfsd/debugfs.c                             |  25 ++
 fs/nfsd/nfsd.h                                |   2 +
 fs/nfsd/trace.h                               |  77 +++++
 fs/nfsd/vfs.c                                 | 265 +++++++++++++++---
 fs/nfsd/vfs.h                                 |   9 +
 include/linux/pagemap.h                       |   1 +
 mm/backing-dev.c                              |  11 +
 mm/filemap.c                                  |  31 ++
 9 files changed, 473 insertions(+), 41 deletions(-)


base-commit: 78c334bc5ae61d23ca1cc2e43079c620f1d40866
-- 
2.52.0



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v4 01/10] NFSD: cut a misaligned direct-mode WRITE at page boundaries
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 02/10] NFSD: mark the direct middle of a split WRITE IOCB_DONTCACHE as well Mike Snitzer
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

nfsd_write_dio_iters_init() splits a misaligned direct-mode WRITE into a
direct middle and buffered ends so that as little of it as possible goes
through the page cache.  That only works if direct and buffered I/O never
meet inside a page: the direct middle invalidates the page cache over its
range, so a middle that starts or stops inside the page a buffered end
writes has that page dropped, and whichever of the two neighbouring
WRITEs comes second has to read it back from the device.

The middle was cut at the file system's direct I/O offset alignment:

	prefix_end = round_up(offset, offset_align);
	middle_end = round_down(orig_end, offset_align);

On 4 KiB pages over 4096-byte blocks that alignment is the page size,
the cut is a page boundary, and the problem cannot arise.  It is broken
wherever the page is larger than the alignment: 16 and 64 KiB pages over
4096-byte blocks, and 4 KiB pages over 512-byte blocks.  There a
misaligned WRITE costs a read per shared page on top of its three I/Os.

Cut each end of a WRITE on its own:

- an end that is already an offset_align multiple needs no buffered
  segment, and the middle starts or stops there, mid-page or not.  A
  WRITE aligned to the device stays entirely direct at every page size;
- a misaligned end needs a buffered segment whatever is done, and that
  segment now runs to the nearest page boundary inside the WRITE.

That is one rule: a misaligned end is cut at a multiple of the larger of
offset_align and PAGE_SIZE, which are both powers of two.

The segments still tile exactly the bytes of the WRITE: a buffered end
grows inward, at the middle's expense, never outward.  A misaligned WRITE
that holds no whole page has no middle and is issued as one buffered
segment.  Where the offset alignment is a page-size multiple nothing
changes.

nfsd-io-modes.rst said that DONTCACHE is not used for the misaligned
segments, which the write loop already contradicted: every segment
without IOCB_DIRECT gets IOCB_DONTCACHE when the file system supports
it.  Replace that sentence with one that says so, next to one that
describes the new cut.

Fixes: 06c5c97293e3 ("NFSD: Implement NFSD_IO_DIRECT for NFS WRITE")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 Documentation/filesystems/nfs/nfsd-io-modes.rst |  9 ++++++---
 fs/nfsd/vfs.c                                   | 13 +++++++++++++
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index 0fd6e82478fe6..1916b2eee58ad 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -126,9 +126,12 @@ Misaligned WRITE:
     middle and end as needed. The large middle segment is DIO-aligned
     and the start and/or end are misaligned. Buffered IO is used for the
     misaligned segments and O_DIRECT is used for the middle DIO-aligned
-    segment. DONTCACHE buffered IO is _not_ used for the misaligned
-    segments because using normal buffered IO offers significant RMW
-    performance benefit when handling streaming misaligned WRITEs.
+    segment. A misaligned start or end segment runs to the nearest page
+    boundary inside the WRITE, not just to the nearest DIO-aligned
+    offset, so the O_DIRECT segment never shares a page with a buffered
+    one; a start or end that is already DIO-aligned gets no buffered
+    segment at all. If the filesystem supports FOP_DONTCACHE, the
+    misaligned segments are marked DONTCACHE.
 
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index f4069da853725..ed5903c3becac 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1324,6 +1324,7 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	size_t prefix, middle, suffix;
 	loff_t offset = iocb->ki_pos;
 	unsigned int nsegs = 0;
+	u32 cut;
 
 	/*
 	 * Check if direct I/O is feasible for this write request.
@@ -1338,6 +1339,18 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	orig_end = offset + total;
 	middle_end = round_down(orig_end, offset_align);
 
+	/*
+	 * The direct middle invalidates the page cache over its range, so it
+	 * must not share a page with a buffered end.  An aligned end needs no
+	 * buffered segment; a misaligned one is cut on a page boundary even
+	 * where the offset alignment is finer than a page.
+	 */
+	cut = max_t(u32, offset_align, PAGE_SIZE);
+	if (prefix_end != offset)
+		prefix_end = min(round_up(offset, cut), orig_end);
+	if (middle_end != orig_end)
+		middle_end = max(round_down(orig_end, cut), prefix_end);
+
 	prefix = prefix_end - offset;
 	middle = middle_end - prefix_end;
 	suffix = orig_end - middle_end;
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 02/10] NFSD: mark the direct middle of a split WRITE IOCB_DONTCACHE as well
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 01/10] NFSD: cut a misaligned direct-mode WRITE at page boundaries Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 03/10] NFSD: only split a direct-mode WRITE for a worthwhile direct middle Mike Snitzer
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

nfsd_write_dio_iters_init() marks the DIO-aligned middle of a misaligned
WRITE IOCB_DIRECT, and nfsd_direct_write() issues it that way.  A file
system may service that request with buffered I/O instead:
xfs_file_write_iter() retries through xfs_file_buffered_write() with the
same kiocb when xfs_file_dio_write() returns -ENOTBLK, which
iomap_dio_rw() does when it cannot invalidate page cache overlapping the
range.

Since the previous commit, the middle of a split WRITE shares no page
with the buffered ends of its neighbours, so a streaming misaligned
WRITE workload no longer causes this by itself.  It still happens
whenever something dirties a page in the middle's range between its
writeback and its invalidation without taking the lock the file system
holds across a direct write: a local process writing the exported file
through mmap, which XFS's IOLOCK does not keep out, or, on a file system
that does not hold such a lock for the length of a direct write, an
overlapping WRITE that is issued buffered as a whole (too small to
split, or its payload misaligned in memory) or a local buffered writer.
All of the middle, nearly the whole WRITE, is then re-issued as a cached
buffered write that nothing ever drops.

Set IOCB_DONTCACHE on the middle segment alongside IOCB_DIRECT when
the file system supports FOP_DONTCACHE.  The direct path ignores the
flag; a buffered fallback now drops its pages once written back.  Say
so in nfsd-io-modes.rst.

Fixes: 06c5c97293e3 ("NFSD: Implement NFSD_IO_DIRECT for NFS WRITE")
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 Documentation/filesystems/nfs/nfsd-io-modes.rst | 4 ++++
 fs/nfsd/vfs.c                                   | 3 +++
 2 files changed, 7 insertions(+)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index 1916b2eee58ad..f9b63f1a33990 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -132,6 +132,10 @@ Misaligned WRITE:
     one; a start or end that is already DIO-aligned gets no buffered
     segment at all. If the filesystem supports FOP_DONTCACHE, the
     misaligned segments are marked DONTCACHE.
+    So is the O_DIRECT segment, where the flag has no effect unless the
+    filesystem services it with buffered IO instead, as XFS does when it
+    cannot invalidate page cache that overlaps the segment. The
+    iomap_dio_invalidate_fail trace event reports such a fallback.
 
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index ed5903c3becac..bd3dab36e2962 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1375,6 +1375,9 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	if (iov_iter_bvec_offset(&segments[nsegs].iter) & (mem_align - 1))
 		goto no_dio;
 	segments[nsegs].flags |= IOCB_DIRECT;
+	/* In case the file system falls back to buffered I/O (-ENOTBLK). */
+	if (nf->nf_file->f_op->fop_flags & FOP_DONTCACHE)
+		segments[nsegs].flags |= IOCB_DONTCACHE;
 	nsegs++;
 
 	if (suffix)
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 03/10] NFSD: only split a direct-mode WRITE for a worthwhile direct middle
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 01/10] NFSD: cut a misaligned direct-mode WRITE at page boundaries Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 02/10] NFSD: mark the direct middle of a split WRITE IOCB_DONTCACHE as well Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 04/10] NFSD: add direct_misaligned_dontcache debugfs knob Mike Snitzer
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm,
	Christoph Hellwig

Splitting a misaligned direct-mode WRITE issues it as two or three
writes instead of one, and the direct write first writes back and
invalidates the page cache over its range.  When the DIO-aligned middle
is small, the WRITE is mostly its buffered prefix and suffix anyway, and
the direct middle only adds that overhead.

Add direct_misaligned_min_bytes (debugfs, default 8192) and do not split
a WRITE that has a misaligned start or end and a middle smaller than that
many bytes.  Such a WRITE is issued as a single buffered segment, like
the other WRITEs NFSD does not split.

The gate counts bytes, not pages.  What it weighs, the cost of three I/Os
against one, does not change with the page size, and since a misaligned
WRITE's middle is cut at page boundaries, the middle of such a WRITE is
whole pages: a gate of two pages would demand a 32 KiB middle on 16 KiB
pages and 128 KiB on 64 KiB pages before splitting at all.  On 4 KiB
pages the default is two pages.

Also decide each segment's IOCB_* flags in nfsd_write_dio_iters_init()
instead of in the write loop of nfsd_direct_write().  The flags do not
change: the loop already set IOCB_DONTCACHE on every segment without
IOCB_DIRECT when the file system supports FOP_DONTCACHE.

The threshold, counted there in pages, and the move of the flag
decisions come from Christoph's suggested diff [1].  Two of its choices
are not taken.  It issued the prefix, the suffix and any WRITE it did
not split, whether smaller than its alignment or with a middle below the
threshold, as cached buffered I/O, to leave partial pages in place for
read-modify-write; this patch keeps the DONTCACHE policy already in the
tree, and a later commit makes that policy selectable.  It also split a
WRITE whose payload is misaligned in memory and issued the middle as
DONTCACHE buffered I/O, so that a large middle stayed out of the page
cache while the cached ends stayed in it.  Here the prefix and suffix
are DONTCACHE as well, so that split would issue the same uncached
buffered I/O as two or three writes instead of one; such a WRITE is
issued whole, like every other WRITE NFSD does not split.

Document in nfsd-io-modes.rst when NFSD does not split a WRITE.

Link: https://lore.kernel.org/linux-nfs/aRShjU_Ti7f2Ci7I@infradead.org/ [1]
Suggested-by: Christoph Hellwig <hch@infradead.org>
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 .../filesystems/nfs/nfsd-io-modes.rst         | 13 ++++++
 fs/nfsd/debugfs.c                             | 12 +++++
 fs/nfsd/nfsd.h                                |  1 +
 fs/nfsd/vfs.c                                 | 46 +++++++++++--------
 4 files changed, 53 insertions(+), 19 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index f9b63f1a33990..eab35a9bbdb93 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -137,6 +137,19 @@ Misaligned WRITE:
     cannot invalidate page cache that overlaps the segment. The
     iomap_dio_invalidate_fail trace event reports such a fallback.
 
+    If NFSD does not split a misaligned WRITE, it issues the whole WRITE
+    as a single DONTCACHE buffered IO (normal buffered IO if the
+    filesystem lacks FOP_DONTCACHE). NFSD does not split a WRITE when:
+
+    - the filesystem advertises no DIO alignment;
+    - the WRITE is smaller than the larger of the offset and memory
+      alignments;
+    - the WRITE has a misaligned start or end and its DIO-aligned middle
+      is smaller than /sys/kernel/debug/nfsd/direct_misaligned_min_bytes
+      bytes (default 8192);
+    - the WRITE payload is not aligned in memory to the block device's
+      dma_alignment, so the middle cannot be O_DIRECT either.
+
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
     misaligned READ to the next DIO-aligned block (on either end of the
diff --git a/fs/nfsd/debugfs.c b/fs/nfsd/debugfs.c
index 386fd1c54f527..02b0369f17648 100644
--- a/fs/nfsd/debugfs.c
+++ b/fs/nfsd/debugfs.c
@@ -140,6 +140,18 @@ void nfsd_debugfs_init(void)
 
 	debugfs_create_file("io_cache_write", 0644, nfsd_top_dir, NULL,
 			    &nfsd_io_cache_write_fops);
+
+	/*
+	 * /sys/kernel/debug/nfsd/direct_misaligned_min_bytes
+	 *
+	 * Minimum size, in bytes, of the DIO-aligned middle segment for NFSD
+	 * to split a misaligned direct-mode WRITE.  A WRITE with a misaligned
+	 * start or end and a smaller middle is issued as a single buffered
+	 * segment.  It is in bytes so that it means the same on every page
+	 * size.  The default value of this setting is 8192.
+	 */
+	debugfs_create_u32("direct_misaligned_min_bytes", 0644, nfsd_top_dir,
+			   &nfsd_direct_misaligned_min_bytes);
 #ifdef CONFIG_NFSD_V4
 	debugfs_create_bool("delegated_timestamps", 0644, nfsd_top_dir,
 			    &nfsd_delegts_enabled);
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index d824d2c390294..db81620bd5168 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -142,6 +142,7 @@ enum {
 
 extern u64 nfsd_io_cache_read __read_mostly;
 extern u64 nfsd_io_cache_write __read_mostly;
+extern u32 nfsd_direct_misaligned_min_bytes __read_mostly;
 
 bool nfsd_v4client(struct svc_rqst *rqstp);
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index bd3dab36e2962..a419eee401d9d 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -54,6 +54,7 @@
 bool nfsd_disable_splice_read __read_mostly;
 u64 nfsd_io_cache_read __read_mostly = NFSD_IO_BUFFERED;
 u64 nfsd_io_cache_write __read_mostly = NFSD_IO_BUFFERED;
+u32 nfsd_direct_misaligned_min_bytes __read_mostly = 8192;
 
 /**
  * nfserrno - Map Linux errnos to NFS errnos
@@ -1323,9 +1324,13 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	u32 mem_align = nf->nf_dio_mem_align;
 	size_t prefix, middle, suffix;
 	loff_t offset = iocb->ki_pos;
+	unsigned int dontcache_flags = 0;
 	unsigned int nsegs = 0;
 	u32 cut;
 
+	if (nf->nf_file->f_op->fop_flags & FOP_DONTCACHE)
+		dontcache_flags = IOCB_DONTCACHE;
+
 	/*
 	 * Check if direct I/O is feasible for this write request.
 	 * If alignments are not available, the write is too small,
@@ -1355,12 +1360,16 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	middle = middle_end - prefix_end;
 	suffix = orig_end - middle_end;
 
-	if (!middle)
+	if (!middle ||
+	    ((prefix || suffix) &&
+	     middle < nfsd_direct_misaligned_min_bytes))
 		goto no_dio;
 
-	if (prefix)
-		nfsd_write_dio_seg_init(&segments[nsegs++], bvec,
+	if (prefix) {
+		nfsd_write_dio_seg_init(&segments[nsegs], bvec,
 					nvecs, total, 0, prefix, iocb);
+		segments[nsegs++].flags |= dontcache_flags;
+	}
 
 	nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs,
 				total, prefix, middle, iocb);
@@ -1374,22 +1383,28 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	 */
 	if (iov_iter_bvec_offset(&segments[nsegs].iter) & (mem_align - 1))
 		goto no_dio;
-	segments[nsegs].flags |= IOCB_DIRECT;
-	/* In case the file system falls back to buffered I/O (-ENOTBLK). */
-	if (nf->nf_file->f_op->fop_flags & FOP_DONTCACHE)
-		segments[nsegs].flags |= IOCB_DONTCACHE;
-	nsegs++;
+	/*
+	 * IOCB_DONTCACHE matters only if the file system falls back to
+	 * buffered I/O (-ENOTBLK).
+	 */
+	segments[nsegs++].flags |= IOCB_DIRECT | dontcache_flags;
 
-	if (suffix)
-		nfsd_write_dio_seg_init(&segments[nsegs++], bvec, nvecs, total,
+	if (suffix) {
+		nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs, total,
 					prefix + middle, suffix, iocb);
+		segments[nsegs++].flags |= dontcache_flags;
+	}
 
 	return nsegs;
 
 no_dio:
-	/* No DIO alignment possible - pack into single non-DIO segment. */
+	/*
+	 * Issue the whole WRITE as a single buffered segment, uncached when
+	 * the file system supports FOP_DONTCACHE.
+	 */
 	nfsd_write_dio_seg_init(&segments[0], bvec, nvecs, total, 0,
 				total, iocb);
+	segments[0].flags |= dontcache_flags;
 	return 1;
 }
 
@@ -1413,16 +1428,9 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		if (kiocb->ki_flags & IOCB_DIRECT)
 			trace_nfsd_write_direct(rqstp, fhp, kiocb->ki_pos,
 						segments[i].iter.count);
-		else {
+		else
 			trace_nfsd_write_vector(rqstp, fhp, kiocb->ki_pos,
 						segments[i].iter.count);
-			/*
-			 * Mark the I/O buffer as evict-able to reduce
-			 * memory contention.
-			 */
-			if (nf->nf_file->f_op->fop_flags & FOP_DONTCACHE)
-				kiocb->ki_flags |= IOCB_DONTCACHE;
-		}
 
 		expected = iov_iter_count(&segments[i].iter);
 
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 04/10] NFSD: add direct_misaligned_dontcache debugfs knob
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (2 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 03/10] NFSD: only split a direct-mode WRITE for a worthwhile direct middle Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 05/10] NFSD: do not use direct I/O for a READ smaller than its alignment Mike Snitzer
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm,
	Jonathan Flynn

From: Jonathan Flynn <jonathan.flynn@hammerspace.com>

The parts of a direct-mode WRITE that are not O_DIRECT (the misaligned
start and end of a split WRITE, or the whole of a WRITE that is not
split) are issued IOCB_DONTCACHE when the file system sets
FOP_DONTCACHE.  That suits the workloads a direct mode is chosen for,
but it is a policy, not a requirement: a workload that reads back or
rewrites what it just wrote is better served by those pages staying
cached.

Add /sys/kernel/debug/nfsd/direct_misaligned_dontcache (default Y).  Set
to N, those parts are issued as normal buffered I/O.  The O_DIRECT
middle keeps IOCB_DONTCACHE either way.  With N, a WRITE that is not
split, including one whose payload is misaligned in memory, is cached in
full.  The knob sits beside direct_misaligned_min_bytes, which decides
whether a misaligned WRITE is split at all; this knob decides how the
parts that are not O_DIRECT are cached.

Signed-off-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
[snitzer: switched from a module parameter to a debugfs knob, moved
next to direct_misaligned_min_bytes, documented in nfsd-io-modes.rst]
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 .../filesystems/nfs/nfsd-io-modes.rst         | 19 +++++++++++++------
 fs/nfsd/debugfs.c                             | 13 +++++++++++++
 fs/nfsd/nfsd.h                                |  1 +
 fs/nfsd/vfs.c                                 | 12 ++++++++----
 4 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index eab35a9bbdb93..2679d85152dfc 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -131,15 +131,17 @@ Misaligned WRITE:
     offset, so the O_DIRECT segment never shares a page with a buffered
     one; a start or end that is already DIO-aligned gets no buffered
     segment at all. If the filesystem supports FOP_DONTCACHE, the
-    misaligned segments are marked DONTCACHE.
-    So is the O_DIRECT segment, where the flag has no effect unless the
-    filesystem services it with buffered IO instead, as XFS does when it
-    cannot invalidate page cache that overlaps the segment. The
-    iomap_dio_invalidate_fail trace event reports such a fallback.
+    O_DIRECT segment is marked DONTCACHE, and so are the misaligned
+    segments unless direct_misaligned_dontcache is N (below). The flag
+    has no effect on the O_DIRECT segment unless the filesystem services
+    it with buffered IO instead, as XFS does when it cannot invalidate
+    page cache that overlaps the segment. The iomap_dio_invalidate_fail
+    trace event reports such a fallback.
 
     If NFSD does not split a misaligned WRITE, it issues the whole WRITE
     as a single DONTCACHE buffered IO (normal buffered IO if the
-    filesystem lacks FOP_DONTCACHE). NFSD does not split a WRITE when:
+    filesystem lacks FOP_DONTCACHE or direct_misaligned_dontcache is N).
+    NFSD does not split a WRITE when:
 
     - the filesystem advertises no DIO alignment;
     - the WRITE is smaller than the larger of the offset and memory
@@ -150,6 +152,11 @@ Misaligned WRITE:
     - the WRITE payload is not aligned in memory to the block device's
       dma_alignment, so the middle cannot be O_DIRECT either.
 
+    Writing N to /sys/kernel/debug/nfsd/direct_misaligned_dontcache
+    (default Y) issues the start and end segments, and a WRITE that is
+    not split, as normal buffered IO instead of DONTCACHE, which suits a
+    workload that reads back or rewrites what it just wrote.
+
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
     misaligned READ to the next DIO-aligned block (on either end of the
diff --git a/fs/nfsd/debugfs.c b/fs/nfsd/debugfs.c
index 02b0369f17648..80f53a4ada7bf 100644
--- a/fs/nfsd/debugfs.c
+++ b/fs/nfsd/debugfs.c
@@ -152,6 +152,19 @@ void nfsd_debugfs_init(void)
 	 */
 	debugfs_create_u32("direct_misaligned_min_bytes", 0644, nfsd_top_dir,
 			   &nfsd_direct_misaligned_min_bytes);
+
+	/*
+	 * /sys/kernel/debug/nfsd/direct_misaligned_dontcache
+	 *
+	 *   Y: the start and end segments of a split direct-mode WRITE, and
+	 *      a WRITE that is not split, use DONTCACHE buffered IO when the
+	 *      file system supports FOP_DONTCACHE
+	 *   N: those segments use normal buffered IO
+	 *
+	 * The default value of this setting is Y.
+	 */
+	debugfs_create_bool("direct_misaligned_dontcache", 0644, nfsd_top_dir,
+			    &nfsd_direct_misaligned_dontcache);
 #ifdef CONFIG_NFSD_V4
 	debugfs_create_bool("delegated_timestamps", 0644, nfsd_top_dir,
 			    &nfsd_delegts_enabled);
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index db81620bd5168..b520b51692b09 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -143,6 +143,7 @@ enum {
 extern u64 nfsd_io_cache_read __read_mostly;
 extern u64 nfsd_io_cache_write __read_mostly;
 extern u32 nfsd_direct_misaligned_min_bytes __read_mostly;
+extern bool nfsd_direct_misaligned_dontcache __read_mostly;
 
 bool nfsd_v4client(struct svc_rqst *rqstp);
 
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index a419eee401d9d..b596bd61f8cb7 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -55,6 +55,7 @@ bool nfsd_disable_splice_read __read_mostly;
 u64 nfsd_io_cache_read __read_mostly = NFSD_IO_BUFFERED;
 u64 nfsd_io_cache_write __read_mostly = NFSD_IO_BUFFERED;
 u32 nfsd_direct_misaligned_min_bytes __read_mostly = 8192;
+bool nfsd_direct_misaligned_dontcache __read_mostly = true;
 
 /**
  * nfserrno - Map Linux errnos to NFS errnos
@@ -1325,11 +1326,13 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	size_t prefix, middle, suffix;
 	loff_t offset = iocb->ki_pos;
 	unsigned int dontcache_flags = 0;
+	unsigned int buffered_flags;
 	unsigned int nsegs = 0;
 	u32 cut;
 
 	if (nf->nf_file->f_op->fop_flags & FOP_DONTCACHE)
 		dontcache_flags = IOCB_DONTCACHE;
+	buffered_flags = nfsd_direct_misaligned_dontcache ? dontcache_flags : 0;
 
 	/*
 	 * Check if direct I/O is feasible for this write request.
@@ -1368,7 +1371,7 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	if (prefix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec,
 					nvecs, total, 0, prefix, iocb);
-		segments[nsegs++].flags |= dontcache_flags;
+		segments[nsegs++].flags |= buffered_flags;
 	}
 
 	nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs,
@@ -1392,7 +1395,7 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	if (suffix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs, total,
 					prefix + middle, suffix, iocb);
-		segments[nsegs++].flags |= dontcache_flags;
+		segments[nsegs++].flags |= buffered_flags;
 	}
 
 	return nsegs;
@@ -1400,11 +1403,12 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 no_dio:
 	/*
 	 * Issue the whole WRITE as a single buffered segment, uncached when
-	 * the file system supports FOP_DONTCACHE.
+	 * the file system supports FOP_DONTCACHE and
+	 * direct_misaligned_dontcache is set.
 	 */
 	nfsd_write_dio_seg_init(&segments[0], bvec, nvecs, total, 0,
 				total, iocb);
-	segments[0].flags |= dontcache_flags;
+	segments[0].flags |= buffered_flags;
 	return 1;
 }
 
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 05/10] NFSD: do not use direct I/O for a READ smaller than its alignment
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (3 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 04/10] NFSD: add direct_misaligned_dontcache debugfs knob Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 06/10] NFSD: persist a synchronous direct-mode WRITE once, after all of its segments Mike Snitzer
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

nfsd_direct_read() expands a misaligned READ out to DIO-aligned
boundaries: it reads from round_down(offset, dio_read_offset_align) to
round_up(offset + count, dio_read_offset_align) and returns only the
requested bytes from within that window.  When the READ is smaller than
the alignment, that window is always at least one full alignment unit,
and two when the READ straddles a boundary, so a few hundred bytes of
payload can cost a 4K or 64K device read.

Decline direct I/O for those.  A READ smaller than
dio_read_offset_align now falls through to the DONTCACHE path, which
issues DONTCACHE buffered I/O when the file system supports
FOP_DONTCACHE and normal buffered I/O otherwise.  This mirrors the
WRITE side, which already declines direct I/O for a WRITE smaller than
the larger of its offset and memory alignments.

Only dio_read_offset_align is consulted: nfsd_direct_read() lands the
data in whole rq_pages pages at offset zero, so dio_mem_align is always
satisfied and only the file offset and length can be misaligned.  The
threshold only bites when the file system advertises a large alignment;
where it reports 512 almost no READ is excluded.

Document it in the "Misaligned READ" section of nfsd-io-modes.rst.

Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 Documentation/filesystems/nfs/nfsd-io-modes.rst |  5 +++++
 fs/nfsd/vfs.c                                   | 11 +++++++++--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index 2679d85152dfc..90bf1038453cc 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -121,6 +121,11 @@ Misaligned READ:
     verified to have proper offset/len (logical_block_size) and
     dma_alignment checking.
 
+    A READ smaller than dio_read_offset_align is not expanded: it would
+    read one or two whole alignment units to return less than one unit
+    of data. It is issued as DONTCACHE buffered IO instead (normal
+    buffered IO if the filesystem lacks FOP_DONTCACHE).
+
 Misaligned WRITE:
     If NFSD_IO_DIRECT is used, split any misaligned WRITE into a start,
     middle and end as needed. The large middle segment is DIO-aligned
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index b596bd61f8cb7..7e65328de7c86 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1166,11 +1166,18 @@ nfsd_direct_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
 				eof, host_err);
 }
 
-static bool nfsd_direct_read_ok(struct svc_rqst *rqstp, struct nfsd_file *nf)
+static bool nfsd_direct_read_ok(struct svc_rqst *rqstp, struct nfsd_file *nf,
+				unsigned long count)
 {
 	/* When dio_read_offset_align is zero, dio is not supported */
 	if (!nf->nf_dio_read_offset_align)
 		return false;
+	/*
+	 * A READ smaller than that alignment is not worth expanding to one
+	 * or two whole alignment units.
+	 */
+	if (count < nf->nf_dio_read_offset_align)
+		return false;
 	if (rqstp->rq_res.page_len)
 		return false;
 #ifdef CONFIG_NFSD_V4
@@ -1222,7 +1229,7 @@ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	case NFSD_IO_BUFFERED:
 		break;
 	case NFSD_IO_DIRECT:
-		if (nfsd_direct_read_ok(rqstp, nf))
+		if (nfsd_direct_read_ok(rqstp, nf, *count))
 			return nfsd_direct_read(rqstp, fhp, nf, offset,
 						count, page, eof);
 		fallthrough;
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 06/10] NFSD: persist a synchronous direct-mode WRITE once, after all of its segments
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (4 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 05/10] NFSD: do not use direct I/O for a READ smaller than its alignment Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 07/10] mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it Mike Snitzer
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

nfsd_direct_write() may issue a WRITE as up to three segments: a
buffered prefix, a direct middle and a buffered suffix.  For a
FILE_SYNC or DATA_SYNC WRITE the kiocb carries IOCB_DSYNC and every
segment inherits it, so generic_write_sync() runs a range fsync after
each segment: up to three cache flushes and log forces per WRITE.

Strip IOCB_DSYNC and IOCB_SYNC from the per-segment flags of a split
WRITE, and of an unsplit buffered WRITE, and persist the WRITE once with
vfs_fsync_range() over the bytes actually written, after the last
segment.  Durability is unchanged: the reply is not sent until the
fsync completes, and datasync mirrors the previous per-segment choice
(IOCB_SYNC present means metadata too).  An fsync failure is returned
like a write failure.

A WRITE issued whole as direct I/O keeps its flags: iomap already
completes it in one step, and can complete a DATA_SYNC overwrite of
allocated blocks with FUA writes and no cache flush.

Besides the fewer flushes, the sync now runs after the last segment is
written.  A later commit relies on that order: it marks the page shared
with a neighbouring WRITE for dropping only after writing it, and the
WRITE's own fsync is then the writeback that drops the page.

Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 .../filesystems/nfs/nfsd-io-modes.rst         |  4 +++-
 fs/nfsd/vfs.c                                 | 20 ++++++++++++++++++-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index 90bf1038453cc..c1cc974640cfe 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -141,7 +141,9 @@ Misaligned WRITE:
     has no effect on the O_DIRECT segment unless the filesystem services
     it with buffered IO instead, as XFS does when it cannot invalidate
     page cache that overlaps the segment. The iomap_dio_invalidate_fail
-    trace event reports such a fallback.
+    trace event reports such a fallback. A split FILE_SYNC or DATA_SYNC
+    WRITE is persisted once, after all of its segments are written, not
+    once per segment.
 
     If NFSD does not split a misaligned WRITE, it issues the whole WRITE
     as a single DONTCACHE buffered IO (normal buffered IO if the
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 7e65328de7c86..249358256d2f5 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1426,16 +1426,27 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 {
 	struct nfsd_write_dio_seg segments[3];
 	struct file *file = nf->nf_file;
+	loff_t start = kiocb->ki_pos;
 	unsigned int nsegs, i;
+	int sync_flags;
 	ssize_t host_err;
 	size_t expected;
 
 	nsegs = nfsd_write_dio_iters_init(nf, rqstp->rq_bvec, nvecs,
 					  kiocb, *cnt, segments);
 
+	/*
+	 * Persist a synchronous WRITE once, after all of its segments.  A
+	 * WRITE issued whole as direct I/O keeps IOCB_DSYNC, so iomap can
+	 * still complete a DATA_SYNC overwrite with FUA writes.
+	 */
+	sync_flags = kiocb->ki_flags & (IOCB_DSYNC | IOCB_SYNC);
+	if (nsegs == 1 && (segments[0].flags & IOCB_DIRECT))
+		sync_flags = 0;
+
 	*cnt = 0;
 	for (i = 0; i < nsegs; i++) {
-		kiocb->ki_flags = segments[i].flags;
+		kiocb->ki_flags = segments[i].flags & ~sync_flags;
 		if (kiocb->ki_flags & IOCB_DIRECT)
 			trace_nfsd_write_direct(rqstp, fhp, kiocb->ki_pos,
 						segments[i].iter.count);
@@ -1453,6 +1464,13 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 			break;	/* partial write */
 	}
 
+	if (sync_flags && *cnt) {
+		host_err = vfs_fsync_range(file, start, start + *cnt - 1,
+					   !(sync_flags & IOCB_SYNC));
+		if (host_err < 0)
+			return host_err;
+	}
+
 	return 0;
 }
 
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 07/10] mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (5 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 06/10] NFSD: persist a synchronous direct-mode WRITE once, after all of its segments Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 08/10] mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats Mike Snitzer
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

A folio allocated by an RWF_DONTCACHE write is marked dropbehind before
it is dirtied, so folio_account_dirtied() adds it to WB_DONTCACHE_DIRTY
and folio_clear_dirty_for_io() or folio_account_cleaned() subtracts it
when it is cleaned.  Nothing marks a folio that already exists.  A
caller that sets PG_dropbehind on a folio that is already dirty
unbalances the counter: the clean subtracts what was never added, and
once the counter goes negative, wb_stat_sum() reads it as zero and the
DONTCACHE writeback kick stops writing anything back for every writer
on that bdi_writeback.

Add folio_mark_dropbehind(), the inverse of the clear in
__filemap_get_folio_mpol(): with the folio locked, mark it and, if it is
dirty, add it to WB_DONTCACHE_DIRTY.  The folio lock orders this
against dirtying and against folio_clear_dirty_for_io().  A concurrent
lookup without FGP_DONTCACHE that clears the flag again subtracts what
this added.

The name follows folio_set_dirty() and folio_mark_dirty(): the first
only sets PG_dirty, the second also accounts the dirty folio.
folio_set_dropbehind() likewise only sets PG_dropbehind, and
folio_mark_dropbehind() also accounts it in WB_DONTCACHE_DIRTY.

A later commit uses it in NFSD to drop a page shared by two misaligned
WRITEs once both have written it.

Cc: Jeff Layton <jlayton@kernel.org>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: linux-mm@kvack.org
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 include/linux/pagemap.h |  1 +
 mm/filemap.c            | 31 +++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 0adfa6605653d..3012bd95812a1 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -1310,6 +1310,7 @@ void end_page_writeback(struct page *page);
 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);
 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 00fd89cf6f550..ed7e4dbea653f 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1644,6 +1644,37 @@ void folio_end_dropbehind(struct folio *folio)
 }
 EXPORT_SYMBOL_GPL(folio_end_dropbehind);
 
+/**
+ * folio_mark_dropbehind - mark an existing folio dropbehind
+ * @folio: the folio, locked by the caller
+ *
+ * Mark @folio so that the writeback that cleans it drops it.  A folio that
+ * is already dirty is added to WB_DONTCACHE_DIRTY, as folio_account_dirtied()
+ * would have added it had it been marked when dirtied, so that the clean
+ * that subtracts it leaves the counter balanced.
+ */
+void folio_mark_dropbehind(struct folio *folio)
+{
+	struct address_space *mapping = folio->mapping;
+
+	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
+
+	if (folio_test_dropbehind(folio))
+		return;
+	folio_set_dropbehind(folio);
+	if (mapping && folio_test_dirty(folio) &&
+	    mapping_can_writeback(mapping)) {
+		struct inode *inode = mapping->host;
+		struct bdi_writeback *wb;
+		struct wb_lock_cookie cookie = {};
+
+		wb = unlocked_inode_to_wb_begin(inode, &cookie);
+		wb_stat_mod(wb, WB_DONTCACHE_DIRTY, folio_nr_pages(folio));
+		unlocked_inode_to_wb_end(inode, &cookie);
+	}
+}
+EXPORT_SYMBOL_GPL(folio_mark_dropbehind);
+
 /**
  * folio_end_writeback_no_dropbehind - End writeback against a folio.
  * @folio: The folio.
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 08/10] mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (6 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 07/10] mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 09/10] NFSD: keep boundary page of a split direct-mode WRITE until both writers complete Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 10/10] NFSD: add tracing for how direct-mode READ and WRITE are serviced Mike Snitzer
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

The per-bdi_writeback WB_DONTCACHE_DIRTY counter drives how much the
flusher writes back in response to an IOCB_DONTCACHE kick, but it is
not reported anywhere, so the DONTCACHE dirty backlog can only be
inferred from MemFree/Cached movement.

Report it as BdiDontcacheDirty in /sys/kernel/debug/bdi/<dev>/stats
and as WbDontcacheDirty in the per-wb stats, next to the
Reclaimable line.  Use wb_stat_sum() rather than wb_stat() to fold in
the per-cpu batches: the flusher targets the summed value and small
backlogs would otherwise read as zero.

Fixes: 88d6f128d06d ("mm: track DONTCACHE dirty pages per bdi_writeback")
Cc: Jeff Layton <jlayton@kernel.org>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: linux-mm@kvack.org
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 mm/backing-dev.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 18e999053bae0..b8a2c27e07fac 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -46,6 +46,7 @@ struct wb_stats {
 	unsigned long nr_dirty_time;
 	unsigned long nr_writeback;
 	unsigned long nr_reclaimable;
+	unsigned long nr_dontcache_dirty;
 	unsigned long nr_dirtied;
 	unsigned long nr_written;
 	unsigned long dirty_thresh;
@@ -78,6 +79,12 @@ static void collect_wb_stats(struct wb_stats *stats,
 
 	stats->nr_writeback += wb_stat(wb, WB_WRITEBACK);
 	stats->nr_reclaimable += wb_stat(wb, WB_RECLAIMABLE);
+	/*
+	 * Sum the per-cpu batches: the dontcache flusher targets this
+	 * counter via wb_stat_sum() and small backlogs would otherwise
+	 * read as zero here.
+	 */
+	stats->nr_dontcache_dirty += wb_stat_sum(wb, WB_DONTCACHE_DIRTY);
 	stats->nr_dirtied += wb_stat(wb, WB_DIRTIED);
 	stats->nr_written += wb_stat(wb, WB_WRITTEN);
 	stats->wb_thresh += wb_calc_thresh(wb, stats->dirty_thresh);
@@ -125,6 +132,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
 	seq_printf(m,
 		   "BdiWriteback:       %10lu kB\n"
 		   "BdiReclaimable:     %10lu kB\n"
+		   "BdiDontcacheDirty:  %10lu kB\n"
 		   "BdiDirtyThresh:     %10lu kB\n"
 		   "DirtyThresh:        %10lu kB\n"
 		   "BackgroundThresh:   %10lu kB\n"
@@ -139,6 +147,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
 		   "state:              %10lx\n",
 		   K(stats.nr_writeback),
 		   K(stats.nr_reclaimable),
+		   K(stats.nr_dontcache_dirty),
 		   K(stats.wb_thresh),
 		   K(dirty_thresh),
 		   K(background_thresh),
@@ -163,6 +172,7 @@ static void wb_stats_show(struct seq_file *m, struct bdi_writeback *wb,
 		   "WbCgIno:           %10lu\n"
 		   "WbWriteback:       %10lu kB\n"
 		   "WbReclaimable:     %10lu kB\n"
+		   "WbDontcacheDirty:  %10lu kB\n"
 		   "WbDirtyThresh:     %10lu kB\n"
 		   "WbDirtied:         %10lu kB\n"
 		   "WbWritten:         %10lu kB\n"
@@ -179,6 +189,7 @@ static void wb_stats_show(struct seq_file *m, struct bdi_writeback *wb,
 #endif
 		   K(stats->nr_writeback),
 		   K(stats->nr_reclaimable),
+		   K(stats->nr_dontcache_dirty),
 		   K(stats->wb_thresh),
 		   K(stats->nr_dirtied),
 		   K(stats->nr_written),
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 09/10] NFSD: keep boundary page of a split direct-mode WRITE until both writers complete
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (7 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 08/10] mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  2026-10-15 23:33 ` [PATCH v4 10/10] NFSD: add tracing for how direct-mode READ and WRITE are serviced Mike Snitzer
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm,
	Jonathan Flynn

A direct-mode WRITE whose ends are not aligned for direct I/O is split
into a buffered prefix, a direct middle and a buffered suffix, and the
prefix and suffix are issued IOCB_DONTCACHE so their pages are dropped
once written back.  The page holding such a segment is shared by two
WRITEs, the one ending in it and the one starting in it, which may
arrive in either order, from different clients, at the same time.  If
it is dropped as soon as the first writer's data is written back, the
second has to read the block from disk before it can complete it: one
4 KiB read per WRITE.  With many clients interleaving small misaligned
O_DIRECT records into one shared file, that was 0.81 device reads per
record (585 GiB read while writing 8.1 TiB).

Keep the page until both writers have had it, with no state in NFSD.
Immediately before a buffered DONTCACHE segment is written,
nfsd_write_dio_boundary_claim() puts an empty folio in the page cache
at each end of the segment that covers its page only partly, if there
is not one there already.  The folio is inserted unmarked: a DONTCACHE
write marks only a folio it allocates itself, so neither writer marks
this one and no writeback completion drops it.

The add is atomic, so of two concurrent partners exactly one gets the
folio.  The other finds it (-EEXIST), is therefore the second of the
two, and once its data is in the page nfsd_write_dio_boundary_complete()
marks the folio with folio_mark_dropbehind().  The mark must follow
the write: set while the first writer's data is still dirty or under
writeback, it would have that writeback drop the folio before the
second writer's data is in it.

A WRITE marks its shared pages only once it is complete.  A FILE_SYNC or
DATA_SYNC WRITE marks them after its own fsync, when they are clean, and
nfsd_write_dio_boundary_complete() drops them at once.  Marking them
while still dirty would count them in WB_DONTCACHE_DIRTY and wake the
DONTCACHE writeback kick, only to write back what the fsync is about to
write.  An UNSTABLE WRITE marks them dirty, and the writeback that
cleans them, by the flusher or for the client's COMMIT, drops them.

A WRITE that is not split is issued as one buffered DONTCACHE segment,
and its partly covered first and last pages are claimed the same way.
A WRITE smaller than a page has one page, which may be shared with both
neighbours; it is kept until the second of them has written it.

The state lives in the page cache.  A claimed page whose partner never
arrives, such as the last page of the file, or that is removed before
the partner arrives, by truncate, reclaim or another client's direct
WRITE, is never marked and stays cached until it is reclaimed.  The
direct middle invalidates the page cache over its range, but a split
WRITE's misaligned ends are cut on page boundaries, so the middle never
reaches a claimed page.  Nothing is claimed when the buffered segments
are normal buffered I/O (direct_misaligned_dontcache is N, or the file
system lacks FOP_DONTCACHE), or when the mapping's minimum folio size is
larger than a page.

Measured with 32 interleaved writers over an emulated 4Kn NVMe with 4
KiB pages, direct_misaligned_dontcache=Y and every WRITE persisted as
FILE_SYNC before its reply, each arm starting from a freshly made file
system.  47008-byte records, which split: 704 device reads for 42895
WRITEs, against 45144 for 45664 WRITEs without this patch.  6000-byte
records, which are not split: 385 reads against 196589.  81 of the
written file's 524066 pages are still resident afterwards.  With
47008-byte records the DONTCACHE writeback kick ran 692 times in 15 s
with the pages marked after the fsync, against 24800 times when they
were marked as soon as they were written.

On a four-server pNFS flexfiles rig (x86_64, 4 KiB logical blocks),
three clients writing 47008-byte records for 240 s against 640 nfsd
threads per server: device reads fall from 0.014-0.015 per record to
0.003, page-cache growth over the write phase falls from about 11 GiB to
2.9 GiB, and write throughput rises by 2.4% to 4.9% whether the WRITEs
are answered UNSTABLE, DATA_SYNC or FILE_SYNC, with read throughput
unchanged.  Keeping every boundary page cached instead, with
direct_misaligned_dontcache=N, grows the page cache by about 630 GiB
over the same runs.

Reported-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 .../filesystems/nfs/nfsd-io-modes.rst         |  15 ++
 fs/nfsd/vfs.c                                 | 141 ++++++++++++++++--
 2 files changed, 145 insertions(+), 11 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index c1cc974640cfe..278c0487b5bb2 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -164,6 +164,21 @@ Misaligned WRITE:
     not split, as normal buffered IO instead of DONTCACHE, which suits a
     workload that reads back or rewrites what it just wrote.
 
+    The page holding a start or end segment is shared with the
+    neighbouring WRITE, which may arrive before, after or at the same
+    time, from any client. So that the second WRITE to such a page does
+    not have to read it back from disk, NFSD puts an empty page there
+    immediately before writing a start or end segment, if there is not
+    one already, without marking it DONTCACHE. The WRITE that finds the
+    page already there marks it DONTCACHE once it has written it, and
+    the writeback that then cleans it drops it. A page whose neighbouring
+    WRITE never arrives stays cached until it is reclaimed. The first and
+    last pages of a WRITE that is not split are handled the same way.
+
+    None of this applies when the start and end segments are normal
+    buffered IO, or when the filesystem's block size is larger than the
+    page size.
+
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
     misaligned READ to the next DIO-aligned block (on either end of the
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 249358256d2f5..d899d00714b1a 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -26,6 +26,7 @@
 #include <linux/xattr.h>
 #include <linux/jhash.h>
 #include <linux/pagemap.h>
+#include <linux/backing-dev.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 #include <linux/exportfs.h>
@@ -1300,6 +1301,7 @@ static int wait_for_concurrent_writes(struct file *file)
 struct nfsd_write_dio_seg {
 	struct iov_iter			iter;
 	int				flags;
+	bool				claim;		/* keep shared pages */
 };
 
 static unsigned long
@@ -1319,6 +1321,110 @@ nfsd_write_dio_seg_init(struct nfsd_write_dio_seg *segment,
 		iov_iter_advance(&segment->iter, start);
 	iov_iter_truncate(&segment->iter, len);
 	segment->flags = iocb->ki_flags;
+	segment->claim = false;
+}
+
+/*
+ * A page that a buffered segment covers only partly is shared with the
+ * neighbouring WRITE.  Put an unmarked folio there if there is none: a
+ * DONTCACHE write marks only a folio it allocates, so neither WRITE marks
+ * this one and no writeback drops it before the other WRITE has written it.
+ *
+ * Return: true if a folio was already there.  This WRITE is then the
+ * second of the two and calls nfsd_write_dio_boundary_complete() after
+ * writing the page.
+ */
+static bool
+nfsd_write_dio_boundary_claim(struct file *file, loff_t pos)
+{
+	struct address_space *mapping = file->f_mapping;
+	gfp_t gfp = mapping_gfp_mask(mapping);
+	struct folio *folio;
+	int err;
+
+	/* A folio larger than a page holds more than the shared page. */
+	if (mapping_min_folio_order(mapping))
+		return false;
+	if (mapping_can_writeback(mapping))
+		gfp |= __GFP_WRITE;
+	folio = filemap_alloc_folio(gfp, 0, NULL);
+	if (!folio)
+		return false;
+	err = filemap_add_folio(mapping, folio, pos >> PAGE_SHIFT, gfp);
+	if (!err)
+		folio_unlock(folio);
+	folio_put(folio);
+	return err == -EEXIST;
+}
+
+/*
+ * Both WRITEs have written the page: mark it so that the writeback that
+ * cleans it drops it.
+ */
+static void
+nfsd_write_dio_boundary_complete(struct file *file, loff_t pos)
+{
+	struct folio *folio;
+
+	folio = __filemap_get_folio(file->f_mapping, pos >> PAGE_SHIFT,
+				    FGP_LOCK | FGP_DONTCACHE, 0);
+	if (IS_ERR(folio))
+		return;
+	folio_mark_dropbehind(folio);
+	folio_unlock(folio);
+	/* Already written back: drop it now, as its writeback would have. */
+	folio_end_dropbehind(folio);
+	folio_put(folio);
+}
+
+/* The shared pages a segment claimed, and how much of it was written. */
+struct nfsd_write_dio_hold {
+	loff_t	first, last;
+	bool	complete_first, complete_last;
+	size_t	written;
+};
+
+/*
+ * Claim the pages @seg shares with the neighbouring WRITEs, immediately
+ * before writing it: an earlier claim gives the partner WRITE time to
+ * complete the page and have it dropped first.
+ */
+static void
+nfsd_write_dio_seg_hold(struct file *file, const struct nfsd_write_dio_seg *seg,
+			loff_t pos, size_t len, struct nfsd_write_dio_hold *hold)
+{
+	hold->first = pos;
+	hold->last = pos + len - 1;
+	hold->complete_first = false;
+	hold->complete_last = false;
+	hold->written = 0;
+	if (seg->claim && len) {
+		bool one_page = (hold->first >> PAGE_SHIFT) ==
+				(hold->last >> PAGE_SHIFT);
+		bool head = hold->first & ~PAGE_MASK;
+		bool tail = (hold->last + 1) & ~PAGE_MASK;
+
+		/* Only a page the segment covers partly is shared. */
+		if (head || (one_page && tail))
+			hold->complete_first =
+				nfsd_write_dio_boundary_claim(file, hold->first);
+		if (!one_page && tail)
+			hold->complete_last =
+				nfsd_write_dio_boundary_claim(file, hold->last);
+	}
+}
+
+/* Mark the shared pages this WRITE was the second of two to write. */
+static void
+nfsd_write_dio_seg_release(struct file *file,
+			   const struct nfsd_write_dio_hold *hold)
+{
+	if (hold->complete_first && hold->written)
+		nfsd_write_dio_boundary_complete(file, hold->first);
+	/* A short write did not reach the last page. */
+	if (hold->complete_last &&
+	    hold->written == hold->last - hold->first + 1)
+		nfsd_write_dio_boundary_complete(file, hold->last);
 }
 
 static unsigned int
@@ -1378,7 +1484,8 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	if (prefix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec,
 					nvecs, total, 0, prefix, iocb);
-		segments[nsegs++].flags |= buffered_flags;
+		segments[nsegs].flags |= buffered_flags;
+		segments[nsegs++].claim = !!buffered_flags;
 	}
 
 	nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs,
@@ -1402,7 +1509,8 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	if (suffix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs, total,
 					prefix + middle, suffix, iocb);
-		segments[nsegs++].flags |= buffered_flags;
+		segments[nsegs].flags |= buffered_flags;
+		segments[nsegs++].claim = !!buffered_flags;
 	}
 
 	return nsegs;
@@ -1416,6 +1524,7 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	nfsd_write_dio_seg_init(&segments[0], bvec, nvecs, total, 0,
 				total, iocb);
 	segments[0].flags |= buffered_flags;
+	segments[0].claim = !!buffered_flags;
 	return 1;
 }
 
@@ -1424,12 +1533,13 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		  struct nfsd_file *nf, unsigned int nvecs,
 		  unsigned long *cnt, struct kiocb *kiocb)
 {
+	struct nfsd_write_dio_hold holds[3];
 	struct nfsd_write_dio_seg segments[3];
 	struct file *file = nf->nf_file;
 	loff_t start = kiocb->ki_pos;
-	unsigned int nsegs, i;
+	unsigned int nsegs, nheld = 0, i;
+	ssize_t host_err = 0;
 	int sync_flags;
-	ssize_t host_err;
 	size_t expected;
 
 	nsegs = nfsd_write_dio_iters_init(nf, rqstp->rq_bvec, nvecs,
@@ -1455,23 +1565,32 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 						segments[i].iter.count);
 
 		expected = iov_iter_count(&segments[i].iter);
-
+		nfsd_write_dio_seg_hold(file, &segments[i], kiocb->ki_pos,
+					expected, &holds[nheld]);
 		host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter);
 		if (host_err < 0)
-			return host_err;
+			break;
+		holds[nheld++].written = host_err;
 		*cnt += host_err;
 		if (host_err < (ssize_t)expected)
 			break;	/* partial write */
 	}
 
-	if (sync_flags && *cnt) {
+	if (host_err >= 0 && sync_flags && *cnt)
 		host_err = vfs_fsync_range(file, start, start + *cnt - 1,
 					   !(sync_flags & IOCB_SYNC));
-		if (host_err < 0)
-			return host_err;
-	}
 
-	return 0;
+	/*
+	 * Mark the pages this WRITE shares with its neighbours for dropping
+	 * only once the WRITE is complete, including its fsync if it has one.
+	 * A page marked while still dirty is counted as DONTCACHE dirty and
+	 * wakes the DONTCACHE writeback kick; a page marked once clean is
+	 * simply dropped.
+	 */
+	for (i = 0; i < nheld; i++)
+		nfsd_write_dio_seg_release(file, &holds[i]);
+
+	return host_err < 0 ? host_err : 0;
 }
 
 /**
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 10/10] NFSD: add tracing for how direct-mode READ and WRITE are serviced
  2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
                   ` (8 preceding siblings ...)
  2026-10-15 23:33 ` [PATCH v4 09/10] NFSD: keep boundary page of a split direct-mode WRITE until both writers complete Mike Snitzer
@ 2026-10-15 23:33 ` Mike Snitzer
  9 siblings, 0 replies; 11+ messages in thread
From: Mike Snitzer @ 2026-10-15 23:33 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: linux-nfs, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
	Christoph Hellwig, Jens Axboe, Matthew Wilcox, linux-mm

When io_cache_read or io_cache_write selects a direct I/O mode, a
request may still be serviced with DONTCACHE buffered or normal
buffered I/O, and a WRITE may be split into up to three segments that
each take a different path depending on the request's offset/length
alignment, the payload's memory alignment, the filesystem's
FOP_DONTCACHE support, direct_misaligned_min_bytes and
direct_misaligned_dontcache.  Until now the only visibility was
nfsd_{read,write}_direct for O_DIRECT and nfsd_{read,write}_vector for
everything else, so the DONTCACHE and cached buffered cases were
indistinguishable and the reason a WRITE was not issued as direct I/O
was not recorded at all.

Add:

 - nfsd_write_dio_split, emitted once per direct-mode WRITE before any
   segment is issued.  It records the advertised offset and memory
   alignments, the payload's offset within its first page, the start,
   middle and end segment sizes, a disposition naming the path taken
   (direct, no_alignment, too_small, no_middle, mem_misaligned), and
   whether buffered segments are issued IOCB_DONTCACHE.

 - nfsd_write_dontcache and nfsd_read_dontcache, emitted for WRITE
   segments and READs serviced with IOCB_DONTCACHE.  nfsd_write_vector
   and nfsd_read_vector now fire only for normal buffered I/O.

The disposition enum lives in vfs.h, which trace.h already includes.

Document the new events in nfsd-io-modes.rst, together with iomap's
iomap_dio_invalidate_fail event, which reports an O_DIRECT segment that
the filesystem serviced with buffered I/O instead.

Assisted-by: LLM
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 .../filesystems/nfs/nfsd-io-modes.rst         | 38 ++++++++-
 fs/nfsd/trace.h                               | 77 +++++++++++++++++++
 fs/nfsd/vfs.c                                 | 47 ++++++++---
 fs/nfsd/vfs.h                                 |  9 +++
 4 files changed, 156 insertions(+), 15 deletions(-)

diff --git a/Documentation/filesystems/nfs/nfsd-io-modes.rst b/Documentation/filesystems/nfs/nfsd-io-modes.rst
index 278c0487b5bb2..2a648cedf2588 100644
--- a/Documentation/filesystems/nfs/nfsd-io-modes.rst
+++ b/Documentation/filesystems/nfs/nfsd-io-modes.rst
@@ -182,21 +182,53 @@ Misaligned WRITE:
 Tracing:
     The nfsd_read_direct trace event shows how NFSD expands any
     misaligned READ to the next DIO-aligned block (on either end of the
-    original READ, as needed).
+    original READ, as needed). A READ that is serviced with buffered IO
+    instead emits nfsd_read_dontcache (DONTCACHE buffered IO) or
+    nfsd_read_vector (normal buffered IO).
 
     This combination of trace events is useful for READs::
 
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_read_vector/enable
+      echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_read_dontcache/enable
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_read_direct/enable
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_read_io_done/enable
       echo 1 > /sys/kernel/tracing/events/xfs/xfs_file_direct_read/enable
 
-    The nfsd_write_direct trace event shows how NFSD splits a given
-    misaligned WRITE into a DIO-aligned middle segment.
+    The nfsd_write_dio_split trace event is emitted once per WRITE in a
+    DIRECT IO mode, before any IO is issued. It records the alignments
+    the filesystem advertised, the payload's offset in its first page,
+    the sizes of the start, middle and end segments, and a
+    disposition::
+
+      direct          the aligned middle segment uses O_DIRECT
+      mem_misaligned  payload memory is misaligned; not split
+      no_alignment    the filesystem advertises no DIO alignment; not split
+      too_small       smaller than the larger of the offset and memory
+                      alignments; not split
+      no_middle       no aligned middle, or a misaligned start or end
+                      with a middle smaller than
+                      direct_misaligned_min_bytes bytes; not split
+
+    dontcache=1 means any buffered segment is issued DONTCACHE: the start
+    and end segments of a split WRITE, or the whole of a WRITE that is
+    not split. It is 0 when direct_misaligned_dontcache is N or the
+    filesystem lacks FOP_DONTCACHE, and is reported even for a WRITE
+    that has no buffered segment.
+
+    Each segment then emits nfsd_write_direct (O_DIRECT),
+    nfsd_write_dontcache (DONTCACHE buffered IO) or nfsd_write_vector
+    (normal buffered IO).
 
     This combination of trace events is useful for WRITEs::
 
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_opened/enable
+      echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_dio_split/enable
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_direct/enable
+      echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_dontcache/enable
+      echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_vector/enable
       echo 1 > /sys/kernel/tracing/events/nfsd/nfsd_write_io_done/enable
       echo 1 > /sys/kernel/tracing/events/xfs/xfs_file_direct_write/enable
+      echo 1 > /sys/kernel/tracing/events/iomap/iomap_dio_invalidate_fail/enable
+
+    iomap_dio_invalidate_fail reports an O_DIRECT middle segment that
+    the filesystem serviced with buffered IO instead.
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index e5d19c6f48a2c..8783c092b6338 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -501,6 +501,7 @@ DEFINE_EVENT(nfsd_io_class, nfsd_##name,	\
 DEFINE_NFSD_IO_EVENT(read_start);
 DEFINE_NFSD_IO_EVENT(read_splice);
 DEFINE_NFSD_IO_EVENT(read_vector);
+DEFINE_NFSD_IO_EVENT(read_dontcache);
 DEFINE_NFSD_IO_EVENT(read_direct);
 DEFINE_NFSD_IO_EVENT(read_io_done);
 DEFINE_NFSD_IO_EVENT(read_done);
@@ -508,11 +509,87 @@ DEFINE_NFSD_IO_EVENT(write_start);
 DEFINE_NFSD_IO_EVENT(write_opened);
 DEFINE_NFSD_IO_EVENT(write_direct);
 DEFINE_NFSD_IO_EVENT(write_vector);
+DEFINE_NFSD_IO_EVENT(write_dontcache);
 DEFINE_NFSD_IO_EVENT(write_io_done);
 DEFINE_NFSD_IO_EVENT(write_done);
 DEFINE_NFSD_IO_EVENT(commit_start);
 DEFINE_NFSD_IO_EVENT(commit_done);
 
+TRACE_DEFINE_ENUM(NFSD_WRITE_DIO_DIRECT);
+TRACE_DEFINE_ENUM(NFSD_WRITE_DIO_MEM_MISALIGNED);
+TRACE_DEFINE_ENUM(NFSD_WRITE_DIO_NO_ALIGNMENT);
+TRACE_DEFINE_ENUM(NFSD_WRITE_DIO_TOO_SMALL);
+TRACE_DEFINE_ENUM(NFSD_WRITE_DIO_NO_MIDDLE);
+
+#define show_nfsd_write_dio_disposition(x)				\
+	__print_symbolic(x,						\
+		{ NFSD_WRITE_DIO_DIRECT,	"direct" },		\
+		{ NFSD_WRITE_DIO_MEM_MISALIGNED, "mem_misaligned" },	\
+		{ NFSD_WRITE_DIO_NO_ALIGNMENT,	"no_alignment" },	\
+		{ NFSD_WRITE_DIO_TOO_SMALL,	"too_small" },		\
+		{ NFSD_WRITE_DIO_NO_MIDDLE,	"no_middle" })
+
+/*
+ * Emitted once per direct-mode WRITE, before any segment is issued.
+ * prefix, middle and suffix are zero when not computed.  mem_offset is
+ * the offset of the payload within its first page.
+ */
+TRACE_EVENT(nfsd_write_dio_split,
+	TP_PROTO(struct svc_rqst *rqstp,
+		 struct svc_fh *fhp,
+		 u64 offset,
+		 u32 len,
+		 u32 offset_align,
+		 u32 mem_align,
+		 u32 mem_offset,
+		 u32 prefix,
+		 u32 middle,
+		 u32 suffix,
+		 enum nfsd_write_dio_disposition disposition,
+		 bool dontcache),
+	TP_ARGS(rqstp, fhp, offset, len, offset_align, mem_align, mem_offset,
+		prefix, middle, suffix, disposition, dontcache),
+	TP_STRUCT__entry(
+		__field(u32, xid)
+		__field(u32, fh_hash)
+		__field(u64, offset)
+		__field(u32, len)
+		__field(u32, offset_align)
+		__field(u32, mem_align)
+		__field(u32, mem_offset)
+		__field(u32, prefix)
+		__field(u32, middle)
+		__field(u32, suffix)
+		__field(unsigned int, disposition)
+		__field(bool, dontcache)
+	),
+	TP_fast_assign(
+		__entry->xid = be32_to_cpu(rqstp->rq_xid);
+		__entry->fh_hash = knfsd_fh_hash(&fhp->fh_handle);
+		__entry->offset = offset;
+		__entry->len = len;
+		__entry->offset_align = offset_align;
+		__entry->mem_align = mem_align;
+		__entry->mem_offset = mem_offset;
+		__entry->prefix = prefix;
+		__entry->middle = middle;
+		__entry->suffix = suffix;
+		__entry->disposition = disposition;
+		__entry->dontcache = dontcache;
+	),
+	TP_printk("xid=0x%08x fh_hash=0x%08x offset=%llu len=%u "
+		  "offset_align=%u mem_align=%u mem_offset=%u "
+		  "prefix=%u middle=%u suffix=%u disposition=%s "
+		  "dontcache=%u",
+		  __entry->xid, __entry->fh_hash,
+		  __entry->offset, __entry->len,
+		  __entry->offset_align, __entry->mem_align,
+		  __entry->mem_offset,
+		  __entry->prefix, __entry->middle, __entry->suffix,
+		  show_nfsd_write_dio_disposition(__entry->disposition),
+		  __entry->dontcache)
+);
+
 DECLARE_EVENT_CLASS(nfsd_err_class,
 	TP_PROTO(struct svc_rqst *rqstp,
 		 struct svc_fh	*fhp,
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index d899d00714b1a..d56b2f01b1cfd 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1255,7 +1255,10 @@ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	}
 	rqstp->rq_next_page = page;
 
-	trace_nfsd_read_vector(rqstp, fhp, offset, *count - total);
+	if (kiocb.ki_flags & IOCB_DONTCACHE)
+		trace_nfsd_read_dontcache(rqstp, fhp, offset, *count - total);
+	else
+		trace_nfsd_read_vector(rqstp, fhp, offset, *count - total);
 	iov_iter_bvec(&iter, ITER_DEST, rqstp->rq_bvec, v, *count - total);
 	host_err = vfs_iocb_iter_read(file, &kiocb, &iter);
 	return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err);
@@ -1428,7 +1431,8 @@ nfsd_write_dio_seg_release(struct file *file,
 }
 
 static unsigned int
-nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
+nfsd_write_dio_iters_init(struct svc_rqst *rqstp, struct svc_fh *fhp,
+			  struct nfsd_file *nf, struct bio_vec *bvec,
 			  unsigned int nvecs, struct kiocb *iocb,
 			  unsigned long total,
 			  struct nfsd_write_dio_seg segments[3])
@@ -1436,7 +1440,9 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	u32 offset_align = nf->nf_dio_offset_align;
 	loff_t prefix_end, orig_end, middle_end;
 	u32 mem_align = nf->nf_dio_mem_align;
-	size_t prefix, middle, suffix;
+	size_t prefix = 0, middle = 0, suffix = 0;
+	enum nfsd_write_dio_disposition disposition;
+	u32 mem_offset = nvecs ? bvec->bv_offset : 0;
 	loff_t offset = iocb->ki_pos;
 	unsigned int dontcache_flags = 0;
 	unsigned int buffered_flags;
@@ -1452,9 +1458,14 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	 * If alignments are not available, the write is too small,
 	 * or no alignment can be found, fall back to buffered I/O.
 	 */
-	if (unlikely(!mem_align || !offset_align) ||
-	    unlikely(total < max(offset_align, mem_align)))
+	if (unlikely(!mem_align || !offset_align)) {
+		disposition = NFSD_WRITE_DIO_NO_ALIGNMENT;
 		goto no_dio;
+	}
+	if (unlikely(total < max(offset_align, mem_align))) {
+		disposition = NFSD_WRITE_DIO_TOO_SMALL;
+		goto no_dio;
+	}
 
 	prefix_end = round_up(offset, offset_align);
 	orig_end = offset + total;
@@ -1478,8 +1489,10 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 
 	if (!middle ||
 	    ((prefix || suffix) &&
-	     middle < nfsd_direct_misaligned_min_bytes))
+	     middle < nfsd_direct_misaligned_min_bytes)) {
+		disposition = NFSD_WRITE_DIO_NO_MIDDLE;
 		goto no_dio;
+	}
 
 	if (prefix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec,
@@ -1498,13 +1511,16 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 	 * the first bvec, all subsequent bvecs start at bv_offset zero
 	 * (page-aligned). Therefore, only the first bvec is checked.
 	 */
-	if (iov_iter_bvec_offset(&segments[nsegs].iter) & (mem_align - 1))
+	if (iov_iter_bvec_offset(&segments[nsegs].iter) & (mem_align - 1)) {
+		disposition = NFSD_WRITE_DIO_MEM_MISALIGNED;
 		goto no_dio;
+	}
 	/*
 	 * IOCB_DONTCACHE matters only if the file system falls back to
 	 * buffered I/O (-ENOTBLK).
 	 */
 	segments[nsegs++].flags |= IOCB_DIRECT | dontcache_flags;
+	disposition = NFSD_WRITE_DIO_DIRECT;
 
 	if (suffix) {
 		nfsd_write_dio_seg_init(&segments[nsegs], bvec, nvecs, total,
@@ -1512,8 +1528,7 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 		segments[nsegs].flags |= buffered_flags;
 		segments[nsegs++].claim = !!buffered_flags;
 	}
-
-	return nsegs;
+	goto out;
 
 no_dio:
 	/*
@@ -1525,7 +1540,12 @@ nfsd_write_dio_iters_init(struct nfsd_file *nf, struct bio_vec *bvec,
 				total, iocb);
 	segments[0].flags |= buffered_flags;
 	segments[0].claim = !!buffered_flags;
-	return 1;
+	nsegs = 1;
+out:
+	trace_nfsd_write_dio_split(rqstp, fhp, offset, total, offset_align,
+				   mem_align, mem_offset, prefix, middle,
+				   suffix, disposition, !!buffered_flags);
+	return nsegs;
 }
 
 static noinline_for_stack int
@@ -1542,8 +1562,8 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	int sync_flags;
 	size_t expected;
 
-	nsegs = nfsd_write_dio_iters_init(nf, rqstp->rq_bvec, nvecs,
-					  kiocb, *cnt, segments);
+	nsegs = nfsd_write_dio_iters_init(rqstp, fhp, nf, rqstp->rq_bvec,
+					  nvecs, kiocb, *cnt, segments);
 
 	/*
 	 * Persist a synchronous WRITE once, after all of its segments.  A
@@ -1560,6 +1580,9 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		if (kiocb->ki_flags & IOCB_DIRECT)
 			trace_nfsd_write_direct(rqstp, fhp, kiocb->ki_pos,
 						segments[i].iter.count);
+		else if (kiocb->ki_flags & IOCB_DONTCACHE)
+			trace_nfsd_write_dontcache(rqstp, fhp, kiocb->ki_pos,
+						   segments[i].iter.count);
 		else
 			trace_nfsd_write_vector(rqstp, fhp, kiocb->ki_pos,
 						segments[i].iter.count);
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index eb75e4c823185..8f6349233c892 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -229,4 +229,13 @@ __be32		nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
 
 void		nfsd_filp_close(struct file *fp);
 
+/* The path nfsd_write_dio_iters_init() took for a direct-mode WRITE. */
+enum nfsd_write_dio_disposition {
+	NFSD_WRITE_DIO_DIRECT,
+	NFSD_WRITE_DIO_MEM_MISALIGNED,
+	NFSD_WRITE_DIO_NO_ALIGNMENT,
+	NFSD_WRITE_DIO_TOO_SMALL,
+	NFSD_WRITE_DIO_NO_MIDDLE,
+};
+
 #endif /* LINUX_NFSD_VFS_H */
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-10-15 23:34 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 23:32 [PATCH v4 00/10] NFSD: keep direct-mode I/O out of the page cache Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 01/10] NFSD: cut a misaligned direct-mode WRITE at page boundaries Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 02/10] NFSD: mark the direct middle of a split WRITE IOCB_DONTCACHE as well Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 03/10] NFSD: only split a direct-mode WRITE for a worthwhile direct middle Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 04/10] NFSD: add direct_misaligned_dontcache debugfs knob Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 05/10] NFSD: do not use direct I/O for a READ smaller than its alignment Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 06/10] NFSD: persist a synchronous direct-mode WRITE once, after all of its segments Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 07/10] mm: add folio_mark_dropbehind() to mark a folio dropbehind and account it Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 08/10] mm: expose WB_DONTCACHE_DIRTY in bdi debugfs stats Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 09/10] NFSD: keep boundary page of a split direct-mode WRITE until both writers complete Mike Snitzer
2026-10-15 23:33 ` [PATCH v4 10/10] NFSD: add tracing for how direct-mode READ and WRITE are serviced Mike Snitzer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox