* [PATCH] block: skip redundant flush for O_DSYNC direct writes
@ 2026-08-12 13:03 Zhenxian Ma
2026-08-12 16:42 ` Christoph Hellwig
2026-08-15 9:06 ` [PATCH v2 0/2] block: avoid redundant flushes " Zhenxian Ma
0 siblings, 2 replies; 7+ messages in thread
From: Zhenxian Ma @ 2026-08-12 13:03 UTC (permalink / raw)
To: Jens Axboe; +Cc: linux-block, Zhenxian Ma, Zhenxian Ma
For an O_DIRECT | O_DSYNC write to a block device, dio_bio_write_op()
adds REQ_FUA to the bio, so the data is durable when the direct-IO
returns. blkdev_write_iter() then falls through to
generic_write_sync(), which for O_DSYNC turns into an additional
REQ_PREFLUSH. That flush is redundant: the FUA write already made
the data persistent.
Skip generic_write_sync() when the direct-IO path already provided
durability via FUA. The buffered and direct_write_fallback() paths
are unchanged.
Measured on a Seagate ST20000NM007D (20 TB, 7200 rpm, fua=1,
write_cache=write back), Linux v7.2.0-rc7, single-threaded pwrite()
loop opening the raw block device with O_WRONLY | O_DIRECT | O_DSYNC,
4 KiB writes for 60 s:
Sequential 4 KiB writes:
baseline patched
IOPS 119.7 7497.0
avg latency (us) 8357 133
p50 latency (us) 8346 127
p99 latency (us) 8368 395
p99.9 latency (us) 8728 569
Random 4 KiB writes (100 GiB span):
baseline patched
IOPS 156.1 666.4
avg latency (us) 6405 1500
p50 latency (us) 6186 1450
p99 latency (us) 16133 2285
p99.9 latency (us) 17250 9916
Signed-off-by: Zhenxian Ma <mzx199711@gmail.com>
Signed-off-by: Zhenxian Ma <mazhenxian@xiaohongshu.com>
---
block/fops.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/block/fops.c b/block/fops.c
index 15783a6180de..9c34a372b476 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -726,6 +726,7 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
bool atomic = iocb->ki_flags & IOCB_ATOMIC;
loff_t size = bdev_nr_bytes(bdev);
size_t shorted = 0;
+ bool dio_fua_done = false;
ssize_t ret;
if (bdev_read_only(bdev))
@@ -763,9 +764,13 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (iocb->ki_flags & IOCB_DIRECT) {
ret = blkdev_direct_write(iocb, from);
- if (ret >= 0 && iov_iter_count(from))
+ if (ret >= 0 && iov_iter_count(from)) {
ret = direct_write_fallback(iocb, from, ret,
blkdev_buffered_write(iocb, from));
+ } else if (ret > 0 && iocb_is_dsync(iocb)) {
+ /* FUA from dio_bio_write_op() already made it durable */
+ dio_fua_done = true;
+ }
} else {
/*
* Take i_rwsem and invalidate_lock to avoid racing with
@@ -777,7 +782,7 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
inode_unlock_shared(bd_inode);
}
- if (ret > 0)
+ if (ret > 0 && !dio_fua_done)
ret = generic_write_sync(iocb, ret);
iov_iter_reexpand(from, iov_iter_count(from) + shorted);
return ret;
--
2.43.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] block: skip redundant flush for O_DSYNC direct writes
2026-08-12 13:03 [PATCH] block: skip redundant flush for O_DSYNC direct writes Zhenxian Ma
@ 2026-08-12 16:42 ` Christoph Hellwig
2026-08-13 7:35 ` 马振先
2026-08-15 9:06 ` [PATCH v2 0/2] block: avoid redundant flushes " Zhenxian Ma
1 sibling, 1 reply; 7+ messages in thread
From: Christoph Hellwig @ 2026-08-12 16:42 UTC (permalink / raw)
To: Zhenxian Ma; +Cc: Jens Axboe, linux-block, Zhenxian Ma
> + bool dio_fua_done = false;
> ssize_t ret;
>
> if (bdev_read_only(bdev))
> @@ -763,9 +764,13 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
>
> if (iocb->ki_flags & IOCB_DIRECT) {
> ret = blkdev_direct_write(iocb, from);
> - if (ret >= 0 && iov_iter_count(from))
> + if (ret >= 0 && iov_iter_count(from)) {
> ret = direct_write_fallback(iocb, from, ret,
> blkdev_buffered_write(iocb, from));
> + } else if (ret > 0 && iocb_is_dsync(iocb)) {
> + /* FUA from dio_bio_write_op() already made it durable */
> + dio_fua_done = true;
This is kinda the wrong way aroud, it should just have a need_sync
variable that is only set for the fallback.
But looking at this, the original logic here actually is very fishy.
If we have a driver that does not actually support FUA, emulating it
for every write command is a bad idea. So this should be fixed to
only do FUA when actually useful, and then return from the non-fallback
implementations if that actually happened.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] block: skip redundant flush for O_DSYNC direct writes
2026-08-12 16:42 ` Christoph Hellwig
@ 2026-08-13 7:35 ` 马振先
2026-08-14 6:32 ` Christoph Hellwig
0 siblings, 1 reply; 7+ messages in thread
From: 马振先 @ 2026-08-13 7:35 UTC (permalink / raw)
To: Christoph Hellwig; +Cc: Jens Axboe, linux-block, Zhenxian Ma
On Thu, Aug 13, 2026 at 12:42 AM Christoph Hellwig <hch@infradead.org> wrote:
>
> > + bool dio_fua_done = false;
> > ssize_t ret;
> >
> > if (bdev_read_only(bdev))
> > @@ -763,9 +764,13 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
> >
> > if (iocb->ki_flags & IOCB_DIRECT) {
> > ret = blkdev_direct_write(iocb, from);
> > - if (ret >= 0 && iov_iter_count(from))
> > + if (ret >= 0 && iov_iter_count(from)) {
> > ret = direct_write_fallback(iocb, from, ret,
> > blkdev_buffered_write(iocb, from));
> > + } else if (ret > 0 && iocb_is_dsync(iocb)) {
> > + /* FUA from dio_bio_write_op() already made it durable */
> > + dio_fua_done = true;
>
> This is kinda the wrong way aroud, it should just have a need_sync
> variable that is only set for the fallback.
Agreed, that reads much cleaner, I'll modify the flag in v2.
> But looking at this, the original logic here actually is very fishy.
> If we have a driver that does not actually support FUA, emulating it
> for every write command is a bad idea. So this should be fixed to
> only do FUA when actually useful, and then return from the non-fallback
> implementations if that actually happened.
Let me make sure:
blkdev_write_iter() then only skips generic_write_sync() when
the direct-IO path actually issued REQ_FUA (i.e. iocb_is_dsync(iocb)
&& bdev_fua(bdev) && the write completed without falling back to
buffered). Is that the direction you had in mind? If yes, I'll send patch
v2 later.
Thanks for the review,
Zhenxian
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] block: skip redundant flush for O_DSYNC direct writes
2026-08-13 7:35 ` 马振先
@ 2026-08-14 6:32 ` Christoph Hellwig
0 siblings, 0 replies; 7+ messages in thread
From: Christoph Hellwig @ 2026-08-14 6:32 UTC (permalink / raw)
To: 马振先
Cc: Christoph Hellwig, Jens Axboe, linux-block, Zhenxian Ma
On Thu, Aug 13, 2026 at 03:35:13PM +0800, 马振先 wrote:
> > But looking at this, the original logic here actually is very fishy.
> > If we have a driver that does not actually support FUA, emulating it
> > for every write command is a bad idea. So this should be fixed to
> > only do FUA when actually useful, and then return from the non-fallback
> > implementations if that actually happened.
>
> Let me make sure:
> blkdev_write_iter() then only skips generic_write_sync() when
> the direct-IO path actually issued REQ_FUA (i.e. iocb_is_dsync(iocb)
> && bdev_fua(bdev) && the write completed without falling back to
> buffered). Is that the direction you had in mind? If yes, I'll send patch
Yes. If you want you can probably do this as an incremental patch
on top of the new version of this patch.
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 0/2] block: avoid redundant flushes for O_DSYNC direct writes
2026-08-12 13:03 [PATCH] block: skip redundant flush for O_DSYNC direct writes Zhenxian Ma
2026-08-12 16:42 ` Christoph Hellwig
@ 2026-08-15 9:06 ` Zhenxian Ma
2026-08-15 9:06 ` [PATCH v2 1/2] block: skip redundant flush " Zhenxian Ma
2026-08-15 12:02 ` [PATCH v2 2/2] block: only use REQ_FUA for direct writes if the device supports it Zhenxian Ma
1 sibling, 2 replies; 7+ messages in thread
From: Zhenxian Ma @ 2026-08-15 9:06 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christoph Hellwig, linux-block, Zhenxian Ma
This series removes redundant cache flushes on the O_DIRECT | O_DSYNC
write path to a block device, following Christoph's review of v1.
Patch 1 skips generic_write_sync() when the direct write was already
made durable by REQ_FUA.
Patch 2 stops setting REQ_FUA on synchronous O_DSYNC direct writes to
devices without FUA support, relying on generic_write_sync() instead
and avoiding per-bio POSTFLUSH emulation. Asynchronous writes keep
REQ_FUA, since they cannot fall back to generic_write_sync().
Changes since v1
[https://lore.kernel.org/linux-block/20260812130335.60354-1-mzx199711@gmail.com/]:
- Split into two patches per Christoph's suggestion.
- Patch 1 tracks need_sync (default false) instead of dio_fua_done.
- Added Patch 2 for the no-FUA case; asynchronous submitters stay at
parity with mainline.
Tested on Linux 7.2.0-rc7 with a Seagate ST20000NM007D (fua=1), 4 KiB
O_DSYNC direct writes, mainline vs this series:
Sequential: 119.7 -> 7497.0 IOPS
Random: 156.1 -> 666.4 IOPS
On a loop device without FUA there was no regression, and async
O_DSYNC direct writes were verified durable with fio verify=crc32c
(262144 IOs, no errors).
Zhenxian Ma (2):
block: skip redundant flush for O_DSYNC direct writes
block: only use REQ_FUA for direct writes if the device supports it
block/fops.c | 40 +++++++++++++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 7 deletions(-)
--
2.43.5
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/2] block: skip redundant flush for O_DSYNC direct writes
2026-08-15 9:06 ` [PATCH v2 0/2] block: avoid redundant flushes " Zhenxian Ma
@ 2026-08-15 9:06 ` Zhenxian Ma
2026-08-15 12:02 ` [PATCH v2 2/2] block: only use REQ_FUA for direct writes if the device supports it Zhenxian Ma
1 sibling, 0 replies; 7+ messages in thread
From: Zhenxian Ma @ 2026-08-15 9:06 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christoph Hellwig, linux-block, Zhenxian Ma
For an O_DIRECT | O_DSYNC write, dio_bio_write_op() adds REQ_FUA to the
bio, so the data is durable once the direct I/O returns. The
unconditional generic_write_sync() in blkdev_write_iter() then issues a
REQ_PREFLUSH that is redundant.
Skip it when the direct path already provided durability via FUA. A
need_sync flag, clear by default, is set only for buffered writes and
for the buffered fallback after a partial direct write.
Measured on a Seagate ST20000NM007D (20 TB, 7200 rpm, fua=1,
write_cache=write back), Linux v7.2.0-rc7, single-threaded pwrite()
loop opening the raw block device with O_WRONLY | O_DIRECT | O_DSYNC,
4 KiB writes for 60 s:
Sequential 4 KiB writes:
baseline patched
IOPS 119.7 7497.0
avg latency (us) 8357 133
p50 latency (us) 8346 127
p99 latency (us) 8368 395
p99.9 latency (us) 8728 569
Random 4 KiB writes (100 GiB span):
baseline patched
IOPS 156.1 666.4
avg latency (us) 6405 1500
p50 latency (us) 6186 1450
p99 latency (us) 16133 2285
p99.9 latency (us) 17250 9916
Signed-off-by: Zhenxian Ma <mzx199711@gmail.com>
Signed-off-by: Zhenxian Ma <mazhenxian@xiaohongshu.com>
---
block/fops.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/block/fops.c b/block/fops.c
index 15783a6180de..a2c3af38106b 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -726,6 +726,7 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
bool atomic = iocb->ki_flags & IOCB_ATOMIC;
loff_t size = bdev_nr_bytes(bdev);
size_t shorted = 0;
+ bool need_sync = false;
ssize_t ret;
if (bdev_read_only(bdev))
@@ -763,9 +764,11 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
if (iocb->ki_flags & IOCB_DIRECT) {
ret = blkdev_direct_write(iocb, from);
- if (ret >= 0 && iov_iter_count(from))
+ if (ret >= 0 && iov_iter_count(from)) {
ret = direct_write_fallback(iocb, from, ret,
blkdev_buffered_write(iocb, from));
+ need_sync = true;
+ }
} else {
/*
* Take i_rwsem and invalidate_lock to avoid racing with
@@ -775,9 +778,10 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
inode_lock_shared(bd_inode);
ret = blkdev_buffered_write(iocb, from);
inode_unlock_shared(bd_inode);
+ need_sync = true;
}
- if (ret > 0)
+ if (ret > 0 && need_sync)
ret = generic_write_sync(iocb, ret);
iov_iter_reexpand(from, iov_iter_count(from) + shorted);
return ret;
--
2.43.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/2] block: only use REQ_FUA for direct writes if the device supports it
2026-08-15 9:06 ` [PATCH v2 0/2] block: avoid redundant flushes " Zhenxian Ma
2026-08-15 9:06 ` [PATCH v2 1/2] block: skip redundant flush " Zhenxian Ma
@ 2026-08-15 12:02 ` Zhenxian Ma
1 sibling, 0 replies; 7+ messages in thread
From: Zhenxian Ma @ 2026-08-15 12:02 UTC (permalink / raw)
To: Jens Axboe; +Cc: Christoph Hellwig, linux-block, mzx199711, Zhenxian Ma
From: Zhenxian Ma <mzx199711@gmail.com>
When a block device does not support FUA natively, the block layer
emulates it by adding a cache flush to every write bio. An O_DSYNC
direct write that spans N bios then costs N flushes, rather than the
single generic_write_sync() issued after the write completes.
Introduce blkdev_dio_fua() to decide when REQ_FUA is set. A synchronous
write can rely on generic_write_sync() when the device lacks FUA, so it
sets REQ_FUA only when bdev_fua() is true. An asynchronous write
completes in blkdev_bio_end_io() and cannot call the blocking
generic_write_sync(), so it keeps REQ_FUA (emulated when needed) to stay
durable.
Suggested-by: Christoph Hellwig <hch@infradead.org>
Signed-off-by: Zhenxian Ma <mzx199711@gmail.com>
Signed-off-by: Zhenxian Ma <mazhenxian@xiaohongshu.com>
---
block/fops.c | 32 +++++++++++++++++++++++++++-----
1 file changed, 27 insertions(+), 5 deletions(-)
diff --git a/block/fops.c b/block/fops.c
index a2c3af38106b..e7d073620271 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -26,12 +26,26 @@ static inline struct inode *bdev_file_inode(struct file *file)
return file->f_mapping->host;
}
-static blk_opf_t dio_bio_write_op(struct kiocb *iocb)
+static bool blkdev_dio_fua(struct kiocb *iocb, struct block_device *bdev)
+{
+ if (!iocb_is_dsync(iocb))
+ return false;
+ /*
+ * Async writes cannot fall back to generic_write_sync(), so they must
+ * use FUA (emulated if needed); sync writes only need it when the
+ * device supports FUA natively.
+ */
+ if (!is_sync_kiocb(iocb))
+ return true;
+ return bdev_fua(bdev);
+}
+
+static blk_opf_t dio_bio_write_op(struct kiocb *iocb, struct block_device *bdev)
{
blk_opf_t opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE;
/* avoid the need for a I/O completion work item */
- if (iocb_is_dsync(iocb))
+ if (blkdev_dio_fua(iocb, bdev))
opf |= REQ_FUA;
return opf;
}
@@ -75,7 +89,8 @@ static ssize_t __blkdev_direct_IO_simple(struct kiocb *iocb,
if (user_backed_iter(iter))
should_dirty = true;
} else {
- bio_init(&bio, bdev, vecs, nr_pages, dio_bio_write_op(iocb));
+ bio_init(&bio, bdev, vecs, nr_pages,
+ dio_bio_write_op(iocb, bdev));
}
bio.bi_iter.bi_sector = pos >> SECTOR_SHIFT;
bio.bi_write_hint = file_inode(iocb->ki_filp)->i_write_hint;
@@ -179,7 +194,7 @@ static ssize_t __blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
struct blkdev_dio *dio;
struct bio *bio;
bool is_read = (iov_iter_rw(iter) == READ), is_sync;
- blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb);
+ blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb, bdev);
loff_t pos = iocb->ki_pos;
int ret = 0;
@@ -323,7 +338,7 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb,
unsigned int nr_pages)
{
bool is_read = iov_iter_rw(iter) == READ;
- blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb);
+ blk_opf_t opf = is_read ? REQ_OP_READ : dio_bio_write_op(iocb, bdev);
struct blkdev_dio *dio;
struct bio *bio;
loff_t pos = iocb->ki_pos;
@@ -768,6 +783,13 @@ static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
ret = direct_write_fallback(iocb, from, ret,
blkdev_buffered_write(iocb, from));
need_sync = true;
+ } else if (ret > 0 && !blkdev_dio_fua(iocb, bdev)) {
+ /*
+ * The device does not support FUA, so REQ_FUA was not
+ * set and the O_DSYNC direct write still needs a flush
+ * to be made durable.
+ */
+ need_sync = true;
}
} else {
/*
--
2.43.5
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-15 12:02 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 13:03 [PATCH] block: skip redundant flush for O_DSYNC direct writes Zhenxian Ma
2026-08-12 16:42 ` Christoph Hellwig
2026-08-13 7:35 ` 马振先
2026-08-14 6:32 ` Christoph Hellwig
2026-08-15 9:06 ` [PATCH v2 0/2] block: avoid redundant flushes " Zhenxian Ma
2026-08-15 9:06 ` [PATCH v2 1/2] block: skip redundant flush " Zhenxian Ma
2026-08-15 12:02 ` [PATCH v2 2/2] block: only use REQ_FUA for direct writes if the device supports it Zhenxian Ma
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.