Add buffered write-through support to iomap & xfs
Subsystems (5)
v3 cover Add buffered write-through support to iomap & xfs
This is the next revision of writethrough series. I'll quote part of the original cover:
Hi all,
This patchset implements an early design prototype of buffered I/O write-through semantics in linux.
This idea mainly picked up traction to enable RWF_ATOMIC buffered IO, however write-through path can have many use cases beyond atomic writes,
- such as enabling truly async AIO buffered I/O when issued with O_DSYNC
- better scalability for buffered I/O
==============================================
** Changes since rfc v2 ** [1]
==============================================
1. Refined the error handling semantics:
----------------------------------------
Writethrough has 2 steps - memcpy to folio & IO to disk. Only the part of write completely submitted to disk is considered successful. With that in mind we have 2 types of errors. Suppose we have 16K write and we encounter an error after 4k:
a) If have only memcpy'd the 4k and we are able to submit it for IO, then we can return 4k as a short write, since everything is consistent.
b) If we have memcpy'd the whole 16k but faced and error (like EIO) after the first 4k write, then we return the error to user. Since the page cache is in inconsistent state, the user must treat this as a buffered IO fsync failure.
2. Introduce RWF_NOSERIAL to allow parallel writes
--------------------------------------------------
As per our discussions in LSFMM 2026, we noticed that writethrough (v2 design) suffered from a big regression (~65%) in workloads with multiple writers writing to a single file. This is because writethrough submits IO within the inode lock and since buffered IO has an exclusive lock in write, this hurt massively. In LSFMM, we discussed 3 approaches:
a) Defer IO submission outside inode lock
b) Avoid folio dirty - clear cycle
c) Shared lock for writes
We tried a) by only staging prepared folios in a list under inode lock and then submitting them outside. However, the initial implementation still showed around ~30% regression even though the code complexity was significantly higher. As the ROI was not worth it, we dropped this idea (if there is interest I can share a github link for these patches). So we finally decided to go with b) and c).
With b), we can avoid cycling folio through dirty and clear as we immediately submit the IO. We also don't use the heavy folio_start/end_writeback() and instead only use the writeback master bit on folio. This cuts back significantly on xa_lock contention.
With c), the NOSERIAL flag allows us to do writes under a shared lock if possible. This violates guarantees that XFS has historically provided however its expected to be an advanced feature that should be used by applications who know what they are doing. If used correctly, it can give a very good performance boost to parallel workloads. This is something that was also discussed at LSFMM 2026 [3] and since WRITETHROUGH is a new path it seems like a good time to introduce such a semantic change . For now we auto apply NOSERIAL for writethrough and disallow users to pass it till we finalize the semantics.
With b & c, we are able to see a good performance improvement in almost all of the cases that we regressing. More details and performance numbers specific to the NOSERIAL flag can be found in the respective patches.
3. Use REQ_SYNC | REQ_IDLE like dio
-----------------------------------
Writethrough doesn't go via the writeback mechanism and it's IO characteristics are similar to dio. Hence we pass REQ_SYNC | REQ_IDLE in the bio, just like dio, which allows us to bypass writeback throttling.
4. Move inode i_size update from completion path to write path
--------------------------------------------------------------
As per Dave's suggestion we originally wanted to update isize in completion like dio however this resulted in a big regression in extending IO because we end up holding the exclusive lock throughout the IO. To avoid this, we can just take the buffered IO approach of updating i_size in write path so that we can safely drop the inode lock and allow completion to finish outside the lock. This brings back the append IO performance in par with buffered IO.
5. There's a deadock in v2 that is fixed in the last patch. If needed this can be squashed in but I've kept it separate for now for easier review.
6. Added a patch to make stable writes more compatible to our usage model. Check patch 1 for details. Thanks to Darrick for suggesting this!.
7. Addressed reviews from Jan and Sashiko (thanks).
===================================================================
Performance Comparison Tables (with fio snippet)
===================================================================
Table 1: Extending writes using (libaio + O_DSYNC) - All writers on single file
+----------+-------------------+---------------------+
| numjobs | Buffered IO | Writethrough IO |
+----------+-------------------+---------------------+
| 1 | 133 MiB/s | 133 MiB/s (+0.0%) |
| 2 | 179 MiB/s | 243 MiB/s (+35.8%) |
| 4 | 253 MiB/s | 358 MiB/s (+41.5%) |
| 8 | 366 MiB/s | 376 MiB/s (+2.7%) |
| 16 | 474 MiB/s | 449 MiB/s (-5.3%) |
+----------+-------------------+---------------------+
(fio --ioengine=libaio --writethrough=0/1 --bs=4k --rw=write \
--iodepth=32 --sync=dsync--file_append=1)Table 2: Random pure overwrites (libaio + O_DSYNC) - All writers on single file
+----------+-------------------+---------------------+
| numjobs | Buffered IO | Writethrough IO |
+----------+-------------------+---------------------+
| 1 | 131 MiB/s | 391 MiB/s (+198.5%) |
| 2 | 377 MiB/s | 791 MiB/s (+109.8%) |
| 4 | 695 MiB/s | 1591 MiB/s (+128.9%)|
| 8 | 1217 MiB/s | 1846 MiB/s (+51.7%) |
| 16 | 1197 MiB/s | 1844 MiB/s (+54.1%) |
+----------+-------------------+---------------------+
(fio --ioengine=libaio --bs=4k --size=2G --sync=dsync --writethrough=0/1 \
--overwrite=1--rw=randwrite --iodepth=32)Table 3: Random pure overwrites (libaio + O_DSYNC) - Each write writes own file
+----------+-------------------+---------------------+
| numjobs | Buffered IO | Writethrough IO |
+----------+-------------------+---------------------+
| 1 | 187 MiB/s | 389 MiB/s (+108.0%) |
| 2 | 373 MiB/s | 781 MiB/s (+109.4%) |
| 4 | 706 MiB/s | 1568 MiB/s (+122.1%)|
| 8 | 1222 MiB/s | 1796 MiB/s (+47.0%) |
+----------+-------------------+---------------------+
(fio --ioengine=libaio --bs=4k --size=2G --filename_format=.test_file.\$jobnum \
--sync=dsync --writethrough=0/1 --overwrite=1 --rw=randwrite --iodepth=32)Table 4: Random 16KB overwrites with sync_file_range:16 - Single file (Roughly mimics postgresql IO pattern)
+----------+-------------------+---------------------+
| numjobs | Buffered IO | Writethrough IO |
+----------+-------------------+---------------------+
| 1 | 1323 MiB/s | 1275 MiB/s (-3.6%) |
| 2 | 1779 MiB/s | 2434 MiB/s (+36.8%) |
| 4 | 2092 MiB/s | 2519 MiB/s (+20.4%) |
| 8 | 2272 MiB/s | 2517 MiB/s (+10.8%) |
| 16 | 2328 MiB/s | 2525 MiB/s (+8.5%) |
+----------+-------------------+---------------------+
(fio --ioengine=libaio --bs=16k --size=5G --writethrough=0/1 --overwrite=1\
--sync_file_range=wait_before,write:16 --rw=randwrite --iodepth=32)* Environment details *
CPU : IBM Power 11 LPAR
Memory : 62Gi
Storage : Samsung PM173-series Enterprise NVMe SSD
Kernel : Linux 7.2-rc1
Filesystem : XFS (4k block size)
[1] https://lore.kernel.org/linux-xfs/[email protected]/
[2] https://github.com/OjaswinM/xfstests/tree/iomap-buf-writethrough2
[3] https://lwn.net/Articles/1072019
As usual, thoughts and suggestions are welcome :)
Regards,
ojaswin
Ojaswin Mujoo (11):
fs: Add counter to track inflight writes that need stable pages
mm: Refactor folio_clear_dirty_for_io()
iomap: Add helper to revert iomap iter
iomap: Add initial support for buffered RWF_WRITETHROUGH
xfs: Add RWF_WRITETHROUGH support to xfs
iomap: Add aio support to RWF_WRITETHROUGH
iomap: Add DSYNC support to RWF_WRITETHROUGH
fs: Introduce RWF_NOSERIAL flag to indicate parallel reads/writes
xfs: Implement RWF_NOSERIAL to parallelize RWF_WRITETHROUGH writes
iomap: Avoid folio dirtying in case of RWF_WRITETHROUGH
iomap: Handle deadlock due to repeating folios in RWF_WRITETHROUGH
fs/iomap/buffered-io.c | 570 +++++++++++++++++++++++++++++++++++++++- fs/iomap/iter.c | 9 + fs/xfs/xfs_file.c | 135 +++++++++- include/linux/fs.h | 29 ++ include/linux/iomap.h | 50 ++++ include/linux/pagemap.h | 16 +- include/uapi/linux/fs.h | 9 +- mm/filemap.c | 22 ++ mm/page-writeback.c | 49 +++- 9 files changed, 861 insertions(+), 28 deletions(-)
--
2.55.0
RFC PATCH v3 1/11 fs: Add counter to track inflight writes that need stable pages
The current flag-style stable write implementation uses idempotent set and clear functions. This is okay because the users for the most part just want to set or clear it once based on factors like underlying device support.
However, this scheme doesn't play well when we have parallel users wanting to temporarily set and unset stable writes. For example, the upcoming RWF_WRITETHROUGH patches need stable writes to be enabled for the duration of the IO. The current scheme can lead to bugs like:
RWF_WRITETHROUGH write 1 RWF_WRITETHROUGH write 2
enable stable write enable stable write
submit IO
disable stable write <---- WRONG
submit IO disable stable write
The 2nd write loses the stable write guarantee midway which is not correct. Fix this by introducing a new inflight_stable_write counter which can be used by parallel users safely.
Unfortunately, due to the way the current users are designed, we cannot directly migrate them to the counter approach hence for now we will have to keep both methods till all the users adapt to the counters.
--- include/linux/fs.h | 1 + include/linux/pagemap.h | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-)
include/linux/fs.h +1 -0
@@ -483,6 +483,7 @@ struct address_space {errseq_t wb_err;spinlock_t i_private_lock;struct rw_semaphore i_mmap_rwsem; +atomic_t inflight_stable_writes_count; } __attribute__((aligned(sizeof(long)))) __randomize_layout;/* * On most architectures that alignment is already the case; but
include/linux/pagemap.h +13 -1
@@ -306,7 +306,8 @@ static inline void mapping_clear_release_always(struct address_space *mapping)static inline bool mapping_stable_writes (const struct address_space *mapping) { -return test_bit (AS_STABLE_WRITES , &mapping->flags); +return test_bit (AS_STABLE_WRITES , &mapping->flags) || +atomic_read (&mapping->inflight_stable_writes_count) >0 ; }static inline void mapping_set_stable_writes (struct address_space *mapping)
@@ -319,6 +320,17 @@ static inline void mapping_clear_stable_writes(struct address_space *mapping)clear_bit (AS_STABLE_WRITES , &mapping->flags); } +static inline void mapping_inc_inflight_stable_writes (struct address_space *mapping) +{ +atomic_inc (&mapping->inflight_stable_writes_count); +} + +static inline void mapping_dec_inflight_stable_writes (struct address_space *mapping) +{ +WARN_ON_ONCE (atomic_read (&mapping->inflight_stable_writes_count) ==0 ); +atomic_dec_if_positive (&mapping->inflight_stable_writes_count); +} +static inline void mapping_set_inaccessible (struct address_space *mapping) {/*
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 01/11] fs: Add counter to track inflight writes that need stable pages
Quoted 63 lines · Ojaswin Mujoo
The current flag-style stable write implementation uses idempotent set and clear functions. This is okay because the users for the most part just want to set or clear it once based on factors like underlying device support.
However, this scheme doesn't play well when we have parallel users wanting to temporarily set and unset stable writes. For example, the upcoming
RWF_WRITETHROUGHpatches need stable writes to be enabled for the duration of the IO. The current scheme can lead to bugs like:
RWF_WRITETHROUGHwrite 1RWF_WRITETHROUGHwrite 2enable stable write enable stable write
submit IO
disable stable write <---- WRONGsubmit IO disable stable write
The 2nd write loses the stable write guarantee midway which is not correct. Fix this by introducing a new
inflight_stable_writecounter which can be used by parallel users safely.Unfortunately, due to the way the current users are designed, we cannot directly migrate them to the counter approach hence for now we will have to keep both methods till all the users adapt to the counters.
Suggested-by "Darrick J. Wong" <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>--- include/linux/fs.h | 1 + include/linux/pagemap.h | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-)include/linux/fs.h +1
-0@@ -483,6 +483,7 @@ struct address_space {errseq_t wb_err;spinlock_t i_private_lock;struct rw_semaphore i_mmap_rwsem; +atomic_t inflight_stable_writes_count; } __attribute__((aligned(sizeof(long)))) __randomize_layout;/* * On most architectures that alignment is already the case; but include/linux/pagemap.h +2
-1@@ -306,7 +306,8 @@ static inline void mapping_clear_release_always(struct address_space *mapping)static inline bool mapping_stable_writes (const struct address_space *mapping) { -return test_bit (AS_STABLE_WRITES , &mapping->flags); +return test_bit (AS_STABLE_WRITES , &mapping->flags) || +atomic_read (&mapping->inflight_stable_writes_count) >0 ; }static inline void mapping_set_stable_writes (struct address_space *mapping)@@ -319,6 +320,17 @@ static inline void mapping_clear_stable_writes(struct address_space *mapping)clear_bit (AS_STABLE_WRITES , &mapping->flags); }
include/linux/pagemap.h static inline void mapping_clear_stable_writes(struct address_space *mapping) · line 331
Quoted 10 lines
include/linux/pagemap.h +9
-0+static inline void mapping_inc_inflight_stable_writes (struct address_space *mapping) +{ +atomic_inc (&mapping->inflight_stable_writes_count); +} + +static inline void mapping_dec_inflight_stable_writes (struct address_space *mapping) +{ +WARN_ON_ONCE (atomic_read (&mapping->inflight_stable_writes_count) ==0 ); +atomic_dec_if_positive (&mapping->inflight_stable_writes_count);
From Sashiko:
If two threads concurrently call mapping_dec_inflight_stable_writes() when the counter is 1:
CPU1
atomic_read() // reads 1, bypasses warning
CPU2
atomic_read() // reads 1, bypasses warning
CPU1
atomic_dec_if_positive() // successfully decrements to 0 CPU2
atomic_dec_if_positive() // fails to decrement and returns -1
Since CPU2 already bypassed the WARN_ON_ONCE via its separate read, the unbalanced decrement fails silently without being reported.
Could this be made atomic by checking the return value directly, such as using WARN_ON_ONCE(atomic_dec_if_positive(...) < 0)?
Yes theres a race here. I'll change it to
WARN_ON_ONCE(atomic_dec_if_positive(...) < 0)?
Regards,
ojaswin
Quoted 8 lines
include/linux/pagemap.h +2
-0+} +static inline void mapping_set_inaccessible (struct address_space *mapping) {/* Signature · 1 line
2.55.0
RFC PATCH v3 2/11 mm: Refactor folio_clear_dirty_for_io()
In reply to Ojaswin MujooAdd a new __folio_clear_dirty_for_io() helper which takes an extra parameter to indicate folio_mkclean() is needed. This is in preparation of buffered writethrough support where we already do folio_mkclean() before calling into this function.
--- mm/page-writeback.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-)
mm/page-writeback.c +25 -14
@@ -2856,20 +2856,12 @@ void __folio_cancel_dirty(struct folio *folio)EXPORT_SYMBOL (__folio_cancel_dirty);/* -* Clear a folio's dirty flag, while caring for dirty memory accounting. -* Returns true if the folio was previously dirty. -* -* This is for preparing to put the folio under writeout. We leave -* the folio tagged as dirty in the xarray so that a concurrent -* write-for-sync can discover it via a PAGECACHE_TAG_DIRTY walk. -* The ->writepage implementation will run either folio_start_writeback() -* or folio_mark_dirty(), at which stage we bring the folio's dirty flag -* and xarray dirty tag back into sync. -* -* This incoherency between the folio's dirty flag and xarray tag is -* unfortunate, but it only exists while the folio is locked. +* Internal helper to take care of clearing dirty bit on a folio in preparation +* of an IO. For some cases we might not want to do mkclean, eg, if we've +* already taken care of it, hence pass the should_mkclean flag to indicate if +* its needed. */ -bool folio_clear_dirty_for_io (struct folio *folio) +static bool __folio_clear_dirty_for_io (struct folio *folio,bool should_mkclean) {struct address_space *mapping =folio_mapping (folio);bool ret = false;
@@ -2906,7 +2898,7 @@ bool folio_clear_dirty_for_io(struct folio *folio)* as a serialization point for all the different * threads doing their things. */ -if (folio_mkclean (folio)) +if (should_mkclean &&folio_mkclean (folio))folio_mark_dirty (folio);/* * We carefully synchronise fault handlers against
@@ -2931,6 +2923,25 @@ bool folio_clear_dirty_for_io(struct folio *folio) }return folio_test_clear_dirty (folio); } + +/* +* Clear a folio's dirty flag, while caring for dirty memory accounting. +* Returns true if the folio was previously dirty. +* +* This is for preparing to put the folio under writeout. We leave +* the folio tagged as dirty in the xarray so that a concurrent +* write-for-sync can discover it via a PAGECACHE_TAG_DIRTY walk. +* The ->writepage implementation will run either folio_start_writeback() +* or folio_mark_dirty(), at which stage we bring the folio's dirty flag +* and xarray dirty tag back into sync. +* +* This incoherency between the folio's dirty flag and xarray tag is +* unfortunate, but it only exists while the folio is locked. +*/ +bool folio_clear_dirty_for_io (struct folio *folio) +{ +return __folio_clear_dirty_for_io (folio, true); +}EXPORT_SYMBOL (folio_clear_dirty_for_io);static void wb_inode_writeback_start (struct bdi_writeback *wb)
Signature · 1 line
2.55.0
RFC PATCH v3 3/11 iomap: Add helper to revert iomap iter
In reply to Ojaswin MujooAdd helper to revert back the iomap iter by count bytes. This will be used by upcoming RWF_WRITETHROUGH feature to revert the state of the iomap in case an error is encountered.
Writethrough can process and park folios so that they can be batched together for IO later, this also advances the iomap iter. However if we encounter any error, we would want to cancel the IO on those processed folios. In this case, we want to revert the iter back so the upper layers can know how much we were actually able to write before failure.
--- fs/iomap/iter.c | 9 +++++++++ include/linux/iomap.h | 1 + 2 files changed, 10 insertions(+)
fs/iomap/iter.c +9 -0
@@ -32,6 +32,15 @@ int iomap_iter_advance(struct iomap_iter *iter, u64 count)return 0 ; } +int iomap_iter_revert (struct iomap_iter *iter,u64 count) +{ +if (WARN_ON_ONCE (count > iter->pos)) +return -EIO ; + iter->pos -= count; + iter->len += count; +return 0 ; +} +static inline void iomap_iter_done (struct iomap_iter *iter) {WARN_ON_ONCE (iter->iomap.offset > iter->pos);
include/linux/iomap.h +1 -0
@@ -275,6 +275,7 @@ struct iomap_iter {int iomap_iter (struct iomap_iter *iter,const struct iomap_ops *ops);int iomap_iter_advance (struct iomap_iter *iter,u64 count); +int iomap_iter_revert (struct iomap_iter *iter,u64 count);/** * iomap_length_trim - trimmed length of the current iomap iteration
Signature · 1 line
2.55.0
RFC PATCH v3 4/11 iomap: Add initial support for buffered RWF_WRITETHROUGH
In reply to Ojaswin MujooThis adds initial support for performing buffered non-aio RWF_WRITETHROUGH write. The rough flow for a writethrough write is as follows:
1. Acquire inode lock
2. initialize writethrough context (wt_ctx) and mark
mapping as stable.
3. Start the iomap_iter() loop. For each iomap:
3.1. Acquire folio and folio_lock.
3.2. perform memcpy from user buffer to the folio and mark it
dirty
3.3. Wait for any current writeback to complete and then call
folio_mkclean() to prevent mmap writes from changing it.
3.4. Start writeback on the folio
3.5. Add the folio range under write to wt_ctx->bvec and folio_unlock() 3.6. If bvec is full, submit the current bvecs for IO.
3.7. Repeat 3.2 to 3.6 till the whole iomap is processed. Submit
the final set of bvecs for IO.
4. Repeat step 3 till we have no more data to write.
5. Finally, sleep in the syscall thread till all the IOs are completed (refcount == 0). Once that happens, the end io handler will wake us up.
6. Upon waking up, call fs ->end_io() callback (which updates inode size), record any errors and return.
7. inode_unlock()
This design gives buffered writethrough the same semantics as dio and any error in the IO is directly returned to the caller. However, the users should note that an error should be treated equivalent of a buffered IO fsync error, since we can't always guarantee that the page cache is consistent.
The design has deliberately open coded the IO submission and completion flow (inspired by dio) rather than reusing the dio functions as accommodating buffered writethrough logic in dio code was polluting it with too many if else conditionals and special cases.
--- fs/iomap/buffered-io.c | 437 ++++++++++++++++++++++++++++++++++++++++ include/linux/fs.h | 11 + include/linux/iomap.h | 42 ++++ include/linux/pagemap.h | 1 + include/uapi/linux/fs.h | 5 +- mm/page-writeback.c | 10 + 6 files changed, 505 insertions(+), 1 deletion(-)
fs/iomap/buffered-io.c +437 -0
@@ -10,6 +10,7 @@#include <linux/migrate.h> #include <linux/fserror.h> #include <linux/fsverity.h> +#include <linux/rmap.h> #include "internal.h" #include "trace.h"
@@ -1185,6 +1186,360 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied,return __iomap_write_end (iter->inode, pos, len, copied, folio); } +static ssize_t iomap_writethrough_complete (struct iomap_writethrough_ctx *wt_ctx) +{ +struct kiocb *iocb = wt_ctx->iocb; +struct inode *inode = wt_ctx->inode; +ssize_t ret = wt_ctx->error; + +if (wt_ctx->end_io) { +int err = wt_ctx->end_io (wt_ctx, wt_ctx->written, + wt_ctx->error, + wt_ctx->flags); +if (err) + ret = err; + } + +mapping_dec_inflight_stable_writes (inode->i_mapping); + +if (!ret) { + ret = wt_ctx->written; + iocb->ki_pos += ret; + } + +kfree (wt_ctx); +return ret; +} + +static void iomap_writethrough_done (struct iomap_writethrough_ctx *wt_ctx) +{ +struct task_struct *waiter = wt_ctx->waiter; + +WRITE_ONCE (wt_ctx->waiter,NULL ); +blk_wake_io_task (waiter); +} + +static void iomap_writethrough_bio_end_io (struct bio *bio) +{ +struct iomap_writethrough_ctx *wt_ctx = bio->bi_private; +struct folio_iter fi; + +if (bio->bi_status) +cmpxchg (&wt_ctx->error,0 , +blk_status_to_errno (bio->bi_status)); +bio_for_each_folio_all (fi, bio) +folio_end_writeback (fi.folio); + +bio_put (bio); +if (atomic_dec_and_test (&wt_ctx->ref)) +iomap_writethrough_done (wt_ctx); +} + +static int +iomap_writethrough_submit_bio (struct iomap_writethrough_ctx *wt_ctx, +struct iomap *iomap, +const struct iomap_writethrough_ops *wt_ops,int error) +{ +struct bio *bio; +unsigned int i; +u64 len =0 ; +blk_opf_t opf =REQ_OP_WRITE |REQ_SYNC |REQ_IDLE ; + +if (!wt_ctx->nr_bvecs) + goto exit; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) + len += wt_ctx->bvec[i].bv_len; + + bio =bio_alloc (iomap->bdev, wt_ctx->nr_bvecs, opf,GFP_NOFS ); + bio->bi_iter.bi_sector =iomap_sector (iomap, wt_ctx->bio_pos); + bio->bi_end_io = iomap_writethrough_bio_end_io; + bio->bi_private = wt_ctx; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) +__bio_add_page (bio, wt_ctx->bvec[i].bv_page, + wt_ctx->bvec[i].bv_len, + wt_ctx->bvec[i].bv_offset); + +if (!error && wt_ops->writethrough_submit) + error = wt_ops->writethrough_submit (wt_ctx->inode, iomap, + wt_ctx->bio_pos, len); + + +atomic_inc (&wt_ctx->ref); + +/* +* In case of error we still need the I/O completion to run so we can +* release references and end writeback on the folios. +*/ +if (error) { + bio->bi_status =errno_to_blk_status (error); +bio_endio (bio); +return error; + } + +submit_bio (bio); + wt_ctx->nr_bvecs =0 ; + +exit: +return 0 ; +} + +/* +* Submit any pending bvecs as a bio and account the written bytes. +* On failure, iomap_writethrough_submit_bio() has already called the +* endio completion handler to record the error. +*/ +static int +iomap_writethrough_try_submit (struct iomap_writethrough_ctx *wt_ctx, +struct iomap *iomap, +const struct iomap_writethrough_ops *wt_ops, +ssize_t *pending) +{ +int ret =iomap_writethrough_submit_bio (wt_ctx, iomap, wt_ops,0 ); + +if (ret <0 ) +return ret; + wt_ctx->written += *pending; + *pending =0 ; +return 0 ; +} + +/** +* iomap_writethrough_begin - prepare the various structures for writethrough +* @folio: folio to prepare for writethrough +* @off: offset of write within folio +* @len: len of write within folio +* +* This function does the major preparation work needed before starting the +* writethrough. The main task is to prepare folio for writeththrough by blocking +* mmap writes and setting writeback on it. Further, we must clear the write range +* to non-dirty. If this results in the complete folio becoming non-dirty, then we +* need to clear the master dirty bit. +*/ +static void iomap_folio_prepare_writethrough (struct folio *folio,size_t off, +size_t len) +{ +bool fully_written; +u64 zero =0 ; + +if (folio_test_writeback (folio)) +folio_wait_writeback (folio); + +if (folio_mkclean (folio)) +folio_mark_dirty (folio); + +/* +* We might either write through the complete folio or a partial folio +* writethrough might result in all blocks becoming non-dirty, so we need to +* check and mark the folio clean if that is the case. +*/ + fully_written = (off ==0 && len ==folio_size (folio)); +iomap_clear_range_dirty (folio, off, len); +if (fully_written || + !iomap_find_dirty_range (folio, &zero,folio_size (folio))) +folio_clear_dirty_for_writethrough (folio); + +folio_start_writeback (folio); +} + +/** +* iomap_writethrough_iter - perform RWF_WRITETHROUGH buffered write +* @wt_ctx: writethrough context +* @iter: iomap iter holding mapping information +* @i: iov_iter for write +* @wt_ops: the fs callbacks needed for writethrough +* +* This function copies the user buffer to folio similar to usual buffered +* IO path, with the difference that we immediately issue the IO. For this we +* utilize IO submission and completion mechanism that is inspired by dio. +* +* Folio handling note: We might be writing through a partial folio so we need +* to be careful to not clear the folio dirty bit unless there are no dirty blocks +* in the folio after the writethrough. +*/ +static int iomap_writethrough_iter (struct iomap_writethrough_ctx *wt_ctx, +struct iomap_iter *iter,struct iov_iter *i, +const struct iomap_writethrough_ops *wt_ops) + +{ +ssize_t total_written =0 , pending =0 ; +loff_t submit_start_pos; +int status =0 ; +struct address_space *mapping = iter->inode->i_mapping; +size_t chunk =mapping_max_folio_size (mapping); +unsigned int bdp_flags = (iter->flags &IOMAP_NOWAIT ) ?BDP_ASYNC :0 ; +unsigned int bs =i_blocksize (iter->inode); + +/* copied over based on how DIO handles these flags */ +if (iter->iomap.type ==IOMAP_UNWRITTEN ) + wt_ctx->flags |=IOMAP_DIO_UNWRITTEN ; +if (iter->iomap.flags &IOMAP_F_SHARED ) + wt_ctx->flags |=IOMAP_DIO_COW ; + +if (!(iter->flags &IOMAP_WRITETHROUGH )) +return -EINVAL ; + +/* +* IOMAP_INLINE mappings have NULL bdev and would cause +* iomap_sector() to dereference invalid memory. Reject them. +*/ +if (iter->iomap.type ==IOMAP_INLINE ) +return -EINVAL ; + +do { +struct folio *folio; +size_t offset;/* Offset into folio */ +loff_t old_size; +u64 bytes;/* Bytes to write to folio */ +size_t copied;/* Bytes copied from user */ +u64 written;/* Bytes have been written */ +loff_t pos; +size_t off_aligned, len_aligned; + + bytes =iov_iter_count (i); +retry: + offset = iter->pos & (chunk -1 ); + bytes =min (chunk - offset, bytes); + status =balance_dirty_pages_ratelimited_flags (mapping, + bdp_flags); +if (unlikely (status)) +break ; + +/* +* If completions already occurred and reported errors, give up +* now and don't bother submitting more bios. +*/ + status =data_race (wt_ctx->error); +if (unlikely (status)) { + wt_ctx->nr_bvecs =0 ; +break ; + } + +if (bytes >iomap_length (iter)) + bytes =iomap_length (iter); + +/* +* Bring in the user page that we'll copy from _first_. +* Otherwise there's a nasty deadlock on copying from the +* same page as we're writing to, without it being marked +* up-to-date. +* +* For async buffered writes the assumption is that the user +* page has already been faulted in. This can be optimized by +* faulting the user page. +*/ +if (unlikely (fault_in_iov_iter_readable (i, bytes) == bytes)) { + status = -EFAULT ; +break ; + } + + status =iomap_write_begin (iter, wt_ops->write_ops, &folio, + &offset, &bytes); +if (unlikely (status)) { +iomap_write_failed (iter->inode, iter->pos, bytes); +break ; + } +if (iter->iomap.flags &IOMAP_F_STALE ) +break ; + + pos = iter->pos; + +if (mapping_writably_mapped (mapping)) +flush_dcache_folio (folio); + + copied =copy_folio_from_iter_atomic (folio, offset, bytes, i); + written =iomap_write_end (iter, bytes, copied, folio) ? + copied :0 ; + + old_size = iter->inode->i_size; +if (pos + written > old_size) +i_size_write (iter->inode, pos + written); + +if (!written) + goto put_folio; + + off_aligned =round_down (offset, bs); + len_aligned =round_up (offset + written, bs) - off_aligned; + +iomap_folio_prepare_writethrough (folio, off_aligned, + len_aligned); + +if (!wt_ctx->nr_bvecs) { + wt_ctx->bio_pos =round_down (pos, bs); + submit_start_pos = pos; + } + +bvec_set_folio (&wt_ctx->bvec[wt_ctx->nr_bvecs], folio, + len_aligned, off_aligned); + wt_ctx->nr_bvecs++; + +put_folio: +__iomap_put_folio (iter, wt_ops->write_ops, written, folio); + +if (old_size < pos) +pagecache_isize_extended (iter->inode, old_size, pos); + +cond_resched (); +if (unlikely (written ==0 )) { +iomap_write_failed (iter->inode, pos, bytes); +iov_iter_revert (i, copied); + +if (chunk >PAGE_SIZE ) + chunk /=2 ; +if (copied) { + bytes = copied; + goto retry; + } + }else { + total_written += written; + pending += written; +iomap_iter_advance (iter, written); + } + +/* +* If we fail to submit the bio, we immediately call the +* IO completion handler that records the error. We +* shall not retry anymore cause this could lead to +* infinite loops in case of non-transient errors. +*/ +if (wt_ctx->nr_bvecs == wt_ctx->max_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + + }while (iov_iter_count (i) &&iomap_length (iter)); + +if (wt_ctx->nr_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + +/* +* In case of an error, we only consider the bytes we were actually able +* to submit IO for as valid data and revert the iters accordingly +*/ +if (status) { +/* +* we still need to run the endio completion for cleanup work +* hence call the below helper to take care of it, if we haven't +* already done so. We can ignore the return value here. +*/ +iomap_writethrough_submit_bio (wt_ctx, &iter->iomap, wt_ops, status); + +submit_failed: +iomap_write_failed (iter->inode, submit_start_pos, pending); +iomap_iter_revert (iter, pending); +iov_iter_revert (i, pending); + } + +return status; +} +static int iomap_write_iter (struct iomap_iter *iter,struct iov_iter *i,const struct iomap_write_ops *write_ops) {
@@ -1345,6 +1700,88 @@ int iomap_fsverity_write(struct file *file, loff_t pos, size_t length, }EXPORT_SYMBOL_GPL (iomap_fsverity_write); +ssize_t iomap_file_writethrough_write (struct kiocb *iocb,struct iov_iter *i, +const struct iomap_writethrough_ops *wt_ops, +void *private) +{ +struct inode *inode = iocb->ki_filp->f_mapping->host; +struct iomap_iter iter = { + .inode = inode, + .pos = iocb->ki_pos, + .len =iov_iter_count (i), + .flags =IOMAP_WRITE |IOMAP_WRITETHROUGH , + .private = private, + }; +struct iomap_writethrough_ctx *wt_ctx; +unsigned int max_bvecs; +ssize_t ret; +struct blk_plug plug; +size_t min_folio_bytes =PAGE_SIZE + <<mapping_min_folio_order (inode->i_mapping); + +/* +* For now we don't support any other flag with WRITETHROUGH +*/ +if (!(iocb->ki_flags &IOCB_WRITETHROUGH )) +return -EINVAL ; +if (iocb->ki_flags & (IOCB_DONTCACHE )) +return -EINVAL ; +if (iocb_is_dsync (iocb)) +/* D_SYNC support not implemented yet */ +return -EOPNOTSUPP ; +if (!is_sync_kiocb (iocb)) +/* aio support not implemented yet */ +return -EOPNOTSUPP ; + +/* +* +1 to max bvecs to account for unaligned write spanning multiple +* folios. Guard against overflow since iov_iter_count() returns size_t. +*/ + max_bvecs = (unsigned int )min_t ( + size_t,DIV_ROUND_UP (iov_iter_count (i), min_folio_bytes) +1 , +BIO_MAX_VECS ); + + wt_ctx =kzalloc (struct_size (wt_ctx, bvec, max_bvecs),GFP_NOFS ); +if (!wt_ctx) +return -ENOMEM ; + + wt_ctx->iocb = iocb; + wt_ctx->inode = inode; + wt_ctx->end_io = wt_ops->end_io; + wt_ctx->old_i_size =i_size_read (inode); + wt_ctx->max_bvecs = max_bvecs; +atomic_set (&wt_ctx->ref,1 ); + wt_ctx->waiter = current; + +mapping_inc_inflight_stable_writes (inode->i_mapping); + +blk_start_plug (&plug); + +while ((ret =iomap_iter (&iter, wt_ops->ops)) >0 ) { +WARN_ON (iter.iomap.type !=IOMAP_UNWRITTEN && + iter.iomap.type !=IOMAP_MAPPED ); + iter.status =iomap_writethrough_iter (wt_ctx, &iter, i, wt_ops); + } + +blk_finish_plug (&plug); + +if (ret <0 ) +cmpxchg (&wt_ctx->error,0 , ret); + +if (!atomic_dec_and_test (&wt_ctx->ref)) { +for (;;) { +set_current_state (TASK_UNINTERRUPTIBLE ); +if (!READ_ONCE (wt_ctx->waiter)) +break ; +blk_io_schedule (); + } +__set_current_state (TASK_RUNNING ); + } + +return iomap_writethrough_complete (wt_ctx); +} +EXPORT_SYMBOL_GPL (iomap_file_writethrough_write); +static void iomap_write_delalloc_ifs_punch (struct inode *inode,struct folio *folio,loff_t start_byte,loff_t end_byte,struct iomap *iomap,iomap_punch_t punch)
include/linux/fs.h +11 -0
@@ -344,6 +344,7 @@ struct readahead_control;#define IOCB_ATOMIC (__force int) RWF_ATOMIC#define IOCB_DONTCACHE (__force int) RWF_DONTCACHE#define IOCB_NOSIGNAL (__force int) RWF_NOSIGNAL +#define IOCB_WRITETHROUGH (__force int) RWF_WRITETHROUGH/* non-RWF related bits - start at 16 */ #define IOCB_EVENTFD (1 << 16)
@@ -1980,6 +1981,8 @@ struct file_operations {#define FOP_ASYNC_LOCK ((__force fop_flags_t)(1 << 6))/* File system supports uncached read/write buffered IO */ #define FOP_DONTCACHE ((__force fop_flags_t)(1 << 7)) +/* File system supports write through buffered IO */ +#define FOP_WRITETHROUGH ((__force fop_flags_t)(1 << 8))/* Wrap a directory iterator that needs exclusive inode access */ int wrap_directory_iterator(struct file *, struct dir_context *,
@@ -3464,6 +3467,14 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,if (IS_DAX (ki->ki_filp->f_mapping->host))return -EOPNOTSUPP ; } +if (flags &RWF_WRITETHROUGH ) { +/* file system must support it */ +if (!(ki->ki_filp->f_op->fop_flags &FOP_WRITETHROUGH )) +return -EOPNOTSUPP ; +/* DAX mappings not supported */ +if (IS_DAX (ki->ki_filp->f_mapping->host)) +return -EOPNOTSUPP ; + } kiocb_flags |= (__force int ) (flags &RWF_SUPPORTED );if (flags &RWF_SYNC ) kiocb_flags |=IOCB_DSYNC ;
include/linux/iomap.h +42 -0
60 lines not on this page · raw message
include/linux/pagemap.h +1 -0
@@ -1270,6 +1270,7 @@ static inline void folio_cancel_dirty(struct folio *folio)__folio_cancel_dirty (folio); }bool folio_clear_dirty_for_io (struct folio *folio); +bool folio_clear_dirty_for_writethrough (struct folio *folio);bool clear_page_dirty_for_io (struct page *page);void folio_invalidate (struct folio *folio,size_t offset,size_t length);bool noop_dirty_folio (struct address_space *mapping,struct folio *folio);
include/uapi/linux/fs.h +4 -1
@@ -451,10 +451,13 @@ typedef int __bitwise __kernel_rwf_t;/* prevent pipe and socket writes from raising SIGPIPE */ #define RWF_NOSIGNAL ((__force __kernel_rwf_t)0x00000100) +/* buffered IO that is asynchronously written through to disk after write */ +#define RWF_WRITETHROUGH ((__force __kernel_rwf_t)0x00000200) +/* mask of flags supported by the kernel */ #define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\ RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\ - RWF_DONTCACHE | RWF_NOSIGNAL) + RWF_DONTCACHE | RWF_NOSIGNAL | RWF_WRITETHROUGH)#define PROCFS_IOCTL_MAGIC 'f'
mm/page-writeback.c +10 -0
@@ -2944,6 +2944,16 @@ bool folio_clear_dirty_for_io(struct folio *folio) }EXPORT_SYMBOL (folio_clear_dirty_for_io); +/* +* Clear folio dirty in preparation of writethrough. Note that for writethrough +* we have already done folkio_mkclean so we avoid it here +*/ +bool folio_clear_dirty_for_writethrough (struct folio *folio) +{ +return __folio_clear_dirty_for_io (folio, false); +} +EXPORT_SYMBOL (folio_clear_dirty_for_writethrough); +static void wb_inode_writeback_start (struct bdi_writeback *wb) {atomic_inc (&wb->writeback_inodes);
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 04/11] iomap: Add initial support for buffered RWF_WRITETHROUGH
I have added some minor comments. I haven't checked Sashiko's review, so ignore them if it is a repeat.
Quoted 6 lines
fs/iomap/buffered-io.c +6
-0+ +if (!wt_ctx->nr_bvecs) + goto exit; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) + len += wt_ctx->bvec[i].bv_len;
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1262
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+ + bio =bio_alloc (iomap->bdev, wt_ctx->nr_bvecs, opf,GFP_NOFS ); + bio->bi_iter.bi_sector =iomap_sector (iomap, wt_ctx->bio_pos); + bio->bi_end_io = iomap_writethrough_bio_end_io; + bio->bi_private = wt_ctx; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) +__bio_add_page (bio, wt_ctx->bvec[i].bv_page, + wt_ctx->bvec[i].bv_len, + wt_ctx->bvec[i].bv_offset);
bio_add_folio_nofail()?
Quoted 23 lines
fs/iomap/buffered-io.c +23
-0+ +if (!error && wt_ops->writethrough_submit) + error = wt_ops->writethrough_submit (wt_ctx->inode, iomap, + wt_ctx->bio_pos, len); + + +atomic_inc (&wt_ctx->ref); + +/* +* In case of error we still need the I/O completion to run so we can +* release references and end writeback on the folios. +*/ +if (error) { + bio->bi_status =errno_to_blk_status (error); +bio_endio (bio); +return error; + } + +submit_bio (bio); + wt_ctx->nr_bvecs =0 ; + +exit: +return 0 ;
<snip>
Quoted 5 lines
fs/iomap/buffered-io.c +5
-0+ +if (!wt_ctx->nr_bvecs) { + wt_ctx->bio_pos =round_down (pos, bs); + submit_start_pos = pos; + }
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1481
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+ +bvec_set_folio (&wt_ctx->bvec[wt_ctx->nr_bvecs], folio, + len_aligned, off_aligned); + wt_ctx->nr_bvecs++; + +put_folio: +__iomap_put_folio (iter, wt_ops->write_ops, written, folio); + +if (old_size < pos) +pagecache_isize_extended (iter->inode, old_size, pos);
Some new code has been added upstream to include VERITY. Probably the same thing needs to be done here?
if (pos + written > old_size &&
!(iter->iomap.flags & IOMAP_F_FSVERITY)) {
i_size_write(iter->inode, pos + written);
iter->iomap.flags |= IOMAP_F_SIZE_CHANGED; }
__iomap_put_folio(iter, write_ops, written, folio);
if (old_size < pos && !(iter->iomap.flags & IOMAP_F_FSVERITY))
pagecache_isize_extended(iter->inode, old_size, pos);
Quoted 41 lines
fs/iomap/buffered-io.c +41
-0+ +cond_resched (); +if (unlikely (written ==0 )) { +iomap_write_failed (iter->inode, pos, bytes); +iov_iter_revert (i, copied); + +if (chunk >PAGE_SIZE ) + chunk /=2 ; +if (copied) { + bytes = copied; + goto retry; + } + }else { + total_written += written; + pending += written; +iomap_iter_advance (iter, written); + } + +/* +* If we fail to submit the bio, we immediately call the +* IO completion handler that records the error. We +* shall not retry anymore cause this could lead to +* infinite loops in case of non-transient errors. +*/ +if (wt_ctx->nr_bvecs == wt_ctx->max_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + + }while (iov_iter_count (i) &&iomap_length (iter)); + +if (wt_ctx->nr_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + +/*
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1532
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+* In case of an error, we only consider the bytes we were actually able +* to submit IO for as valid data and revert the iters accordingly +*/ +if (status) { +/* +* we still need to run the endio completion for cleanup work +* hence call the below helper to take care of it, if we haven't +* already done so. We can ignore the return value here. +*/ +iomap_writethrough_submit_bio (wt_ctx, &iter->iomap, wt_ops, status);
Do we even need to call iomap_writethrough_submit_bio() here?
In all the failure scenario we reach submit_failed directly right? In the cases where we fail before copy_folio_from_iter_atomic(), we will not have anything to do anyway, therefore, making the above call a no op? Am I missing something?
Quoted 25 lines
fs/iomap/buffered-io.c +18
-0+ +submit_failed: +iomap_write_failed (iter->inode, submit_start_pos, pending); +iomap_iter_revert (iter, pending); +iov_iter_revert (i, pending); + } + +return status; +} +static int iomap_write_iter (struct iomap_iter *iter,struct iov_iter *i,const struct iomap_write_ops *write_ops) {@@ -1345,6 +1700,88 @@ int iomap_fsverity_write(struct file *file, loff_t pos, size_t length, }EXPORT_SYMBOL_GPL (iomap_fsverity_write); +ssize_t iomap_file_writethrough_write (struct kiocb *iocb,struct iov_iter *i, +const struct iomap_writethrough_ops *wt_ops, +void *private) +{ +struct inode *inode = iocb->ki_filp->f_mapping->host; +struct iomap_iter iter = { + .inode = inode, + .pos = iocb->ki_pos,
fs/iomap/buffered-io.c int iomap_fsverity_write(struct file *file, loff_t pos, size_t length, · line 1720
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+ .len = iov_iter_count(i), + .flags = IOMAP_WRITE | IOMAP_WRITETHROUGH, + .private = private, + }; +struct iomap_writethrough_ctx *wt_ctx; +unsigned int max_bvecs; +ssize_t ret; +struct blk_plug plug; +size_t min_folio_bytes =PAGE_SIZE + <<mapping_min_folio_order (inode->i_mapping);
mapping_min_folio_nrbytes can be used here.
Quoted 16 lines
include/linux/pagemap.h +5
-0+ +/* +* For now we don't support any other flag with WRITETHROUGH +*/ __folio_cancel_dirty (folio); }bool folio_clear_dirty_for_io (struct folio *folio); +bool folio_clear_dirty_for_writethrough (struct folio *folio);bool clear_page_dirty_for_io (struct page *page);void folio_invalidate (struct folio *folio,size_t offset,size_t length);bool noop_dirty_folio (struct address_space *mapping,struct folio *folio);include/uapi/linux/fs.h +0
-0
include/uapi/linux/fs.h typedef int __bitwise __kernel_rwf_t; · line 454
Quoted 4 lines
include/uapi/linux/fs.h +1
-0@@ -451,10 +451,13 @@ typedef int __bitwise __kernel_rwf_t;/* prevent pipe and socket writes from raising SIGPIPE */ #define RWF_NOSIGNAL ((__force __kernel_rwf_t)0x00000100) +/* buffered IO that is asynchronously written through to disk after write */
Isn't it synchronously written?
include/uapi/linux/fs.h typedef int __bitwise __kernel_rwf_t; · line 461
Quoted 8 lines
include/uapi/linux/fs.h +3
-1+#define RWF_WRITETHROUGH ((__force __kernel_rwf_t)0x00000200) +/* mask of flags supported by the kernel */ #define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\ RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\ - RWF_DONTCACHE | RWF_NOSIGNAL) + RWF_DONTCACHE | RWF_NOSIGNAL | RWF_WRITETHROUGH)
--
Pankaj
Re: [RFC PATCH v3 04/11] iomap: Add initial support for buffered RWF_WRITETHROUGH
Quoted 2 lines · Pankaj Raghav (Samsung)
I have added some minor comments. I haven't checked Sashiko's review, so ignore them if it is a repeat.
Hi Pankaj,
Thanks for the review!
Quoted 11 lines
Quoted 11 lines
fs/iomap/buffered-io.c +10
-0+ +if (!wt_ctx->nr_bvecs) + goto exit; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) + len += wt_ctx->bvec[i].bv_len; + + bio =bio_alloc (iomap->bdev, wt_ctx->nr_bvecs, opf,GFP_NOFS ); + bio->bi_iter.bi_sector =iomap_sector (iomap, wt_ctx->bio_pos); + bio->bi_end_io = iomap_writethrough_bio_end_io;
Quoted 8 lines
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1262
Quoted 6 lines
fs/iomap/buffered-io.c +6
-0+ bio->bi_private = wt_ctx; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) +__bio_add_page (bio, wt_ctx->bvec[i].bv_page, + wt_ctx->bvec[i].bv_len, + wt_ctx->bvec[i].bv_offset);
bio_add_folio_nofail()?
Hmm actually your suggestion is right but it also got me thinking about if we are actually ensuring that bio_add_folio_nofail() will never face any issues.
We make sure that we don't let the bvecs grow more than BIO_MAX_VECS ie we can at max have 256 pages in the bio, however on higher page size systems will a larger large page size, i think we can actually cross the BIO_MAX_SIZE limit causing the bi_size to wrap around.
I think in addition to adding the helper, we should also add a check to make sure we are not crossing that limit. I'll look into this a bit more.
Quoted 45 lines
Quoted 24 lines
fs/iomap/buffered-io.c +23
-0+ +if (!error && wt_ops->writethrough_submit) + error = wt_ops->writethrough_submit (wt_ctx->inode, iomap, + wt_ctx->bio_pos, len); + + +atomic_inc (&wt_ctx->ref); + +/* +* In case of error we still need the I/O completion to run so we can +* release references and end writeback on the folios. +*/ +if (error) { + bio->bi_status =errno_to_blk_status (error); +bio_endio (bio); +return error; + } + +submit_bio (bio); + wt_ctx->nr_bvecs =0 ; + +exit: +return 0 ;<snip>
Quoted 5 lines
fs/iomap/buffered-io.c +5
-0+ +if (!wt_ctx->nr_bvecs) { + wt_ctx->bio_pos =round_down (pos, bs); + submit_start_pos = pos; + }fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1481
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+ +bvec_set_folio (&wt_ctx->bvec[wt_ctx->nr_bvecs], folio, + len_aligned, off_aligned); + wt_ctx->nr_bvecs++; + +put_folio: +__iomap_put_folio (iter, wt_ops->write_ops, written, folio); + +if (old_size < pos) +pagecache_isize_extended (iter->inode, old_size, pos);Some new code has been added upstream to include VERITY. Probably the same thing needs to be done here?
if (pos + written >
old_size&&
Quoted 9 lines
!(iter->iomap.flags & IOMAP_F_FSVERITY)) { i_size_write(iter->inode, pos + written); iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;}
__iomap_put_folio(iter, write_ops, written, folio);if (
old_size< pos && !(iter->iomap.flags &IOMAP_F_FSVERITY))pagecache_isize_extended(iter->inode, old_size, pos);
Oh right, I'll take care of it, thanks for pointing it out.
Quoted 54 lines
Quoted 41 lines
fs/iomap/buffered-io.c +41
-0+ +cond_resched (); +if (unlikely (written ==0 )) { +iomap_write_failed (iter->inode, pos, bytes); +iov_iter_revert (i, copied); + +if (chunk >PAGE_SIZE ) + chunk /=2 ; +if (copied) { + bytes = copied; + goto retry; + } + }else { + total_written += written; + pending += written; +iomap_iter_advance (iter, written); + } + +/* +* If we fail to submit the bio, we immediately call the +* IO completion handler that records the error. We +* shall not retry anymore cause this could lead to +* infinite loops in case of non-transient errors. +*/ +if (wt_ctx->nr_bvecs == wt_ctx->max_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + + }while (iov_iter_count (i) &&iomap_length (iter)); + +if (wt_ctx->nr_bvecs) { + status =iomap_writethrough_try_submit (wt_ctx, + &iter->iomap, wt_ops, &pending); +if (status) + goto submit_failed; + } + +/* fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1532
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+* In case of an error, we only consider the bytes we were actually able +* to submit IO for as valid data and revert the iters accordingly +*/ +if (status) { +/* +* we still need to run the endio completion for cleanup work +* hence call the below helper to take care of it, if we haven't +* already done so. We can ignore the return value here. +*/ +iomap_writethrough_submit_bio (wt_ctx, &iter->iomap, wt_ops, status);Do we even need to call
iomap_writethrough_submit_bio()here?
Quoted 4 lines
In all the failure scenario we reach
submit_faileddirectly right? In the cases where we fail beforecopy_folio_from_iter_atomic(), we will not have anything to do anyway, therefore, making the above call a no op? Am I missing something?
Actually if we break of of the loop due to some error and nr_bvecs is empty then I added this call so that we could handle the cleanup and even if there are not bvecs to submit, it is still responsible for calling bio_endio to set wt_ctx->error = status, so that later we can return it to the user in iomap_writethrough_complete().
But as you said I think we might not need the complete endio flow and further, I think in this case even wt_ctx->error is set in the higher layer. I'll take a look at it again, thanks for pointing it out.
Quoted 30 lines
Quoted 30 lines
fs/iomap/buffered-io.c +22
-0+ +submit_failed: +iomap_write_failed (iter->inode, submit_start_pos, pending); +iomap_iter_revert (iter, pending); +iov_iter_revert (i, pending); + } + +return status; +} +static int iomap_write_iter (struct iomap_iter *iter,struct iov_iter *i,const struct iomap_write_ops *write_ops) {@@ -1345,6 +1700,88 @@ int iomap_fsverity_write(struct file *file, loff_t pos, size_t length, }EXPORT_SYMBOL_GPL (iomap_fsverity_write); +ssize_t iomap_file_writethrough_write (struct kiocb *iocb,struct iov_iter *i, +const struct iomap_writethrough_ops *wt_ops, +void *private) +{ +struct inode *inode = iocb->ki_filp->f_mapping->host; +struct iomap_iter iter = { + .inode = inode, + .pos = iocb->ki_pos, + .len =iov_iter_count (i), + .flags =IOMAP_WRITE |IOMAP_WRITETHROUGH , + .private = private, + };
Quoted 8 lines
fs/iomap/buffered-io.c int iomap_fsverity_write(struct file *file, loff_t pos, size_t length, · line 1720
Quoted 6 lines
fs/iomap/buffered-io.c +6
-0+struct iomap_writethrough_ctx *wt_ctx; +unsigned int max_bvecs; +ssize_t ret; +struct blk_plug plug; +size_t min_folio_bytes =PAGE_SIZE + <<mapping_min_folio_order (inode->i_mapping);
mapping_min_folio_nrbytescan be used here.
Right, I will make the change.
Quoted 20 lines
Quoted 20 lines
include/linux/pagemap.h +5
-0+ +/* +* For now we don't support any other flag with WRITETHROUGH +*/ __folio_cancel_dirty (folio); }bool folio_clear_dirty_for_io (struct folio *folio); +bool folio_clear_dirty_for_writethrough (struct folio *folio);bool clear_page_dirty_for_io (struct page *page);void folio_invalidate (struct folio *folio,size_t offset,size_t length);bool noop_dirty_folio (struct address_space *mapping,struct folio *folio);include/uapi/linux/fs.h +0
-0@@ -451,10 +451,13 @@ typedef int __bitwise __kernel_rwf_t;/* prevent pipe and socket writes from raising SIGPIPE */ #define RWF_NOSIGNAL ((__force __kernel_rwf_t)0x00000100)
Quoted 3 lines
include/uapi/linux/fs.h typedef int __bitwise __kernel_rwf_t; · line 454
Quoted 1 line
include/uapi/linux/fs.h +1
-0+/* buffered IO that is asynchronously written through to disk after write */ Isn't it synchronously written?
Oh right, I think it got left over for an earlier variant. I'll fix this thanks!
Regards,
ojaswin
Quoted 12 lines
include/uapi/linux/fs.h typedef int __bitwise __kernel_rwf_t; · line 461
Quoted 9 lines
include/uapi/linux/fs.h +3
-1+#define RWF_WRITETHROUGH ((__force __kernel_rwf_t)0x00000200) +/* mask of flags supported by the kernel */ #define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\ RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\ - RWF_DONTCACHE | RWF_NOSIGNAL) + RWF_DONTCACHE | RWF_NOSIGNAL | RWF_WRITETHROUGH)--
Pankaj
Re: [RFC PATCH v3 04/11] iomap: Add initial support for buffered RWF_WRITETHROUGH
In reply to Ojaswin MujooQuoted 37 lines · Ojaswin Mujoo
This adds initial support for performing buffered non-aio
RWF_WRITETHROUGHwrite. The rough flow for a writethrough write is as follows:1. Acquire inode lock
2. initialize writethrough context (wt_ctx) and mark
mapping as stable.
3. Start theiomap_iter()loop. For each iomap:
3.1. Acquire folio andfolio_lock.
3.2. perform memcpy from user buffer to the folio and mark it
dirty
3.3. Wait for any current writeback to complete and then call
folio_mkclean()to prevent mmap writes from changing it.
3.4. Start writeback on the folio
3.5. Add the folio range under write towt_ctx->bvecandfolio_unlock()3.6. If bvec is full, submit the current bvecs for IO.
3.7. Repeat 3.2 to 3.6 till the whole iomap is processed. Submit
the final set of bvecs for IO.
4. Repeat step 3 till we have no more data to write.
5. Finally, sleep in the syscall thread till all the IOs are completed (refcount == 0). Once that happens, the end io handler will wake us up.
6. Upon waking up, call fs ->end_io() callback (which updates inode size), record any errors and return.
7.inode_unlock()This design gives buffered writethrough the same semantics as dio and any error in the IO is directly returned to the caller. However, the users should note that an error should be treated equivalent of a buffered IO fsync error, since we can't always guarantee that the page cache is consistent.
The design has deliberately open coded the IO submission and completion flow (inspired by dio) rather than reusing the dio functions as accommodating buffered writethrough logic in dio code was polluting it with too many if else conditionals and special cases.
Quoted 5 lines
Suggested-by Jan Kara <[email protected]>Suggested-by Dave Chinner <[email protected]>Co-developed-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Addressing Sashiko reviews here:
Quoted 194 lines
--- fs/iomap/buffered-io.c | 437 ++++++++++++++++++++++++++++++++++++++++ include/linux/fs.h | 11 + include/linux/iomap.h | 42 ++++ include/linux/pagemap.h | 1 + include/uapi/linux/fs.h | 5 +- mm/page-writeback.c | 10 + 6 files changed, 505 insertions(+), 1 deletion(-)fs/iomap/buffered-io.c +170
-0@@ -10,6 +10,7 @@#include <linux/migrate.h> #include <linux/fserror.h> #include <linux/fsverity.h> +#include <linux/rmap.h> #include "internal.h" #include "trace.h" @@ -1185,6 +1186,360 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied,return __iomap_write_end (iter->inode, pos, len, copied, folio); } +static ssize_t iomap_writethrough_complete (struct iomap_writethrough_ctx *wt_ctx) +{ +struct kiocb *iocb = wt_ctx->iocb; +struct inode *inode = wt_ctx->inode; +ssize_t ret = wt_ctx->error; + +if (wt_ctx->end_io) { +int err = wt_ctx->end_io (wt_ctx, wt_ctx->written, + wt_ctx->error, + wt_ctx->flags); +if (err) + ret = err; + } + +mapping_dec_inflight_stable_writes (inode->i_mapping); + +if (!ret) { + ret = wt_ctx->written; + iocb->ki_pos += ret; + } + +kfree (wt_ctx); +return ret; +} + +static void iomap_writethrough_done (struct iomap_writethrough_ctx *wt_ctx) +{ +struct task_struct *waiter = wt_ctx->waiter; + +WRITE_ONCE (wt_ctx->waiter,NULL ); +blk_wake_io_task (waiter); +} + +static void iomap_writethrough_bio_end_io (struct bio *bio) +{ +struct iomap_writethrough_ctx *wt_ctx = bio->bi_private; +struct folio_iter fi; + +if (bio->bi_status) +cmpxchg (&wt_ctx->error,0 , +blk_status_to_errno (bio->bi_status)); +bio_for_each_folio_all (fi, bio) +folio_end_writeback (fi.folio); + +bio_put (bio); +if (atomic_dec_and_test (&wt_ctx->ref)) +iomap_writethrough_done (wt_ctx); +} + +static int +iomap_writethrough_submit_bio (struct iomap_writethrough_ctx *wt_ctx, +struct iomap *iomap, +const struct iomap_writethrough_ops *wt_ops,int error) +{ +struct bio *bio; +unsigned int i; +u64 len =0 ; +blk_opf_t opf =REQ_OP_WRITE |REQ_SYNC |REQ_IDLE ; + +if (!wt_ctx->nr_bvecs) + goto exit; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) + len += wt_ctx->bvec[i].bv_len; + + bio =bio_alloc (iomap->bdev, wt_ctx->nr_bvecs, opf,GFP_NOFS ); + bio->bi_iter.bi_sector =iomap_sector (iomap, wt_ctx->bio_pos); + bio->bi_end_io = iomap_writethrough_bio_end_io; + bio->bi_private = wt_ctx; + +for (i =0 ; i < wt_ctx->nr_bvecs; i++) +__bio_add_page (bio, wt_ctx->bvec[i].bv_page, + wt_ctx->bvec[i].bv_len, + wt_ctx->bvec[i].bv_offset); + +if (!error && wt_ops->writethrough_submit) + error = wt_ops->writethrough_submit (wt_ctx->inode, iomap, + wt_ctx->bio_pos, len); + + +atomic_inc (&wt_ctx->ref); + +/* +* In case of error we still need the I/O completion to run so we can +* release references and end writeback on the folios. +*/ +if (error) { + bio->bi_status =errno_to_blk_status (error); +bio_endio (bio); +return error; + } + +submit_bio (bio); + wt_ctx->nr_bvecs =0 ; + +exit: +return 0 ; +} + +/* +* Submit any pending bvecs as a bio and account the written bytes. +* On failure, iomap_writethrough_submit_bio() has already called the +* endio completion handler to record the error. +*/ +static int +iomap_writethrough_try_submit (struct iomap_writethrough_ctx *wt_ctx, +struct iomap *iomap, +const struct iomap_writethrough_ops *wt_ops, +ssize_t *pending) +{ +int ret =iomap_writethrough_submit_bio (wt_ctx, iomap, wt_ops,0 ); + +if (ret <0 ) +return ret; + wt_ctx->written += *pending; + *pending =0 ; +return 0 ; +} + +/** +* iomap_writethrough_begin - prepare the various structures for writethrough +* @folio: folio to prepare for writethrough +* @off: offset of write within folio +* @len: len of write within folio +* +* This function does the major preparation work needed before starting the +* writethrough. The main task is to prepare folio for writeththrough by blocking +* mmap writes and setting writeback on it. Further, we must clear the write range +* to non-dirty. If this results in the complete folio becoming non-dirty, then we +* need to clear the master dirty bit. +*/ +static void iomap_folio_prepare_writethrough (struct folio *folio,size_t off, +size_t len) +{ +bool fully_written; +u64 zero =0 ; + +if (folio_test_writeback (folio)) +folio_wait_writeback (folio); + +if (folio_mkclean (folio)) +folio_mark_dirty (folio); + +/* +* We might either write through the complete folio or a partial folio +* writethrough might result in all blocks becoming non-dirty, so we need to +* check and mark the folio clean if that is the case. +*/ + fully_written = (off ==0 && len ==folio_size (folio)); +iomap_clear_range_dirty (folio, off, len); +if (fully_written || + !iomap_find_dirty_range (folio, &zero,folio_size (folio))) +folio_clear_dirty_for_writethrough (folio); + +folio_start_writeback (folio); +} + +/** +* iomap_writethrough_iter - perform RWF_WRITETHROUGH buffered write +* @wt_ctx: writethrough context +* @iter: iomap iter holding mapping information +* @i: iov_iter for write +* @wt_ops: the fs callbacks needed for writethrough +* +* This function copies the user buffer to folio similar to usual buffered +* IO path, with the difference that we immediately issue the IO. For this we +* utilize IO submission and completion mechanism that is inspired by dio. +* +* Folio handling note: We might be writing through a partial folio so we need
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1367
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+* to be careful to not clear the folio dirty bit unless there are no dirty blocks +* in the folio after the writethrough. +*/ +static int iomap_writethrough_iter (struct iomap_writethrough_ctx *wt_ctx, +struct iomap_iter *iter,struct iov_iter *i, +const struct iomap_writethrough_ops *wt_ops) + +{ +ssize_t total_written =0 , pending =0 ; +loff_t submit_start_pos;
If an error occurs on the very first iteration before any data is written, the loop exits early.
Will the uninitialized stack variable submit_start_pos be passed into iomap_write_failed() during the error cleanup path?
Yes this seems like a valid issue and can be fixed with:
submit_failed:
- iomap_write_failed(iter->inode, submit_start_pos, pending);
- iomap_iter_revert(iter, pending);
- iov_iter_revert(i, pending);
+ if (pending) {
+ iomap_write_failed(iter->inode, submit_start_pos, pending);
+ iomap_iter_revert(iter, pending);
+ iov_iter_revert(i, pending);
+ }
If we do fail in the first iter, then pending would not have been set and we won't hit the issue. Regardless, guarding the reverts behind pending makes sense because if we don't have any folios pending submission, then no point to revert anything.
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+int status =0 ; +struct address_space *mapping = iter->inode->i_mapping; +size_t chunk =mapping_max_folio_size (mapping); +unsigned int bdp_flags = (iter->flags &IOMAP_NOWAIT ) ?BDP_ASYNC :0 ; +unsigned int bs =i_blocksize (iter->inode); + +/* copied over based on how DIO handles these flags */ +if (iter->iomap.type ==IOMAP_UNWRITTEN ) + wt_ctx->flags |=IOMAP_DIO_UNWRITTEN ; +if (iter->iomap.flags &IOMAP_F_SHARED )
<...>
Quoted 9 lines
fs/iomap/buffered-io.c +9
-0+ + bytes =iov_iter_count (i); +retry: + offset = iter->pos & (chunk -1 ); + bytes =min (chunk - offset, bytes); + status =balance_dirty_pages_ratelimited_flags (mapping, + bdp_flags); +if (unlikely (status)) +break ;
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1417
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+ +/* +* If completions already occurred and reported errors, give up +* now and don't bother submitting more bios. +*/ + status =data_race (wt_ctx->error); +if (unlikely (status)) { + wt_ctx->nr_bvecs =0 ; +break ; + }
If an asynchronous error is detected here, the pending bvec count is zeroed out and the loop breaks.
Since folios from previous iterations were already added to the bvec array and marked as PG_writeback, does this leak the PG_writeback state and abandon those folios permanently?
Yes if we set nr_bvecs = 0 here, the error handling in iomap_writethrough_submit_bio() -> bio_endio() will pass an empty bio and hence the references will leak. I will fix this in next verison.
<...>
Quoted 14 lines
fs/iomap/buffered-io.c +14
-0+ copied =copy_folio_from_iter_atomic (folio, offset, bytes, i); + written =iomap_write_end (iter, bytes, copied, folio) ? + copied :0 ; + + old_size = iter->inode->i_size; +if (pos + written > old_size) +i_size_write (iter->inode, pos + written); + +if (!written) + goto put_folio; + + off_aligned =round_down (offset, bs); + len_aligned =round_up (offset + written, bs) - off_aligned; +
fs/iomap/buffered-io.c static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, · line 1474
Quoted 10 lines
fs/iomap/buffered-io.c +10
-0+iomap_folio_prepare_writethrough (folio, off_aligned, + len_aligned); + +if (!wt_ctx->nr_bvecs) { + wt_ctx->bio_pos =round_down (pos, bs); + submit_start_pos = pos; + } + +bvec_set_folio (&wt_ctx->bvec[wt_ctx->nr_bvecs], folio, + len_aligned, off_aligned);
If a short copy occurs within a single filesystem block, the offset and length are aligned to the block size.
When the remainder of the short copy is processed on the next iteration, will this append an identical, duplicate bvec for the same block? The block layer would then write the duplicate segment into the next logical sector, potentially corrupting adjacent data on disk.
Oh right, we might end up adding the exact same block to the bvec which is not correct, this can lead to the bio iter becoming corrupt. I think we will have to somehow track the previous offset added to bio and make sure that bit is not re added. I'll look into this.
Rest are some minor comments that I'll address in next version.
Regards,
ojaswin
RFC PATCH v3 5/11 xfs: Add RWF_WRITETHROUGH support to xfs
In reply to Ojaswin MujooAdd the boilerplate needed to start supporting RWF_WRITETHROUGH in XFS. We use the direct write ->iomap_begin() functions to ensure the range under write through always has a real non-delalloc extent. We reuse the xfs dio's end IO function to perform extent conversion and i_size handling for us.
*Note on COW extent over DATA hole case*
In case of an unmapped COW extent over a DATA hole
(due to COW preallocations), leave the extent unmapped until we are just about to send IO. At that time, use the ->writethrough_submit() call back to convert the COW extent to written.
We initially tried converting during iomap begin() time (like dio does) but that results in a stale data exposure as follows:
1. iomap begin() - converts COW extent over DATA hole to written and marks IOMAP_F_NEW to handle zeroing.
2. During iomap_write_begin() -> realise extent is stale and return back without zeroing.
3. iomap begin() - Again sees the same COW extent but it's written this time so we don't mark IOMAP_F_NEW
4. Since IOMAP_F_NEW is unmarked, we never zeroout and hence expose stale data.
To avoid the above, take the buffered IO approach of converting the extent just before IO, when we are sure to have zeroed out the folio.
--- fs/xfs/xfs_file.c | 83 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-)
fs/xfs/xfs_file.c +77 -6
@@ -702,6 +702,36 @@ static const struct iomap_dio_ops xfs_dio_write_ops = { .end_io = xfs_dio_write_end_io, }; +static int +xfs_writethrough_end_io ( +struct iomap_writethrough_ctx *wt_ctx, +ssize_t size, +int error, +unsigned int flags) +{ +struct xfs_inode *ip =XFS_I (wt_ctx->inode); +xfs_off_t offset = wt_ctx->iocb->ki_pos; + +if (unlikely (error)) { +if (wt_ctx->flags &IOMAP_DIO_COW ) +xfs_reflink_cancel_cow_range (ip, offset, size, true); + +return error; + } + +/* +* writethrough completions are handled same as dio with the exception +* that we need to explicitly change the i_disk_size. This is because +* unlike dio, we have already updated the i_size and hence the +* (i_disk_size < i_size) check will fail in dio code +*/ +xfs_dio_write_end_io (wt_ctx->iocb, size, error, flags); +if (offset + size > ip->i_disk_size) +return xfs_setfilesize (ip, offset, size); + +return 0 ; +} + static void xfs_dio_zoned_submit_io( const struct iomap_iter *iter,
@@ -1033,6 +1063,39 @@ xfs_file_dax_write(return ret; } +static int +xfs_writethrough_submit ( +struct inode *inode, +struct iomap *iomap, +loff_t offset, +u64 count) +{ +int error =0 ; +unsigned int nofs_flag; + +/* +* Convert CoW extents to regular. +* +* We are under writethrough context with folio lock possibly held. To +* avoid memory allocation deadlocks, set the task-wide nofs context. +*/ +if (iomap->flags &IOMAP_F_SHARED ) { + nofs_flag =memalloc_nofs_save (); + error =xfs_reflink_convert_cow (XFS_I (inode), offset, count); +memalloc_nofs_restore (nofs_flag); + } + +return error; +} + +const struct iomap_writethrough_ops xfs_writethrough_ops = { + .ops = &xfs_direct_write_iomap_ops, + .write_ops = &xfs_iomap_write_ops, + .end_io = xfs_writethrough_end_io, + .writethrough_submit = &xfs_writethrough_submit +}; + + STATIC ssize_t xfs_file_buffered_write( struct kiocb *iocb,
@@ -1055,9 +1118,13 @@ xfs_file_buffered_write( goto out;trace_xfs_file_buffered_write (iocb, from); - ret =iomap_file_buffered_write (iocb, from, - &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops, -NULL ); +if (iocb->ki_flags &IOCB_WRITETHROUGH ) { + ret =iomap_file_writethrough_write (iocb, from, + &xfs_writethrough_ops,NULL ); + }else + ret =iomap_file_buffered_write (iocb, from, + &xfs_buffered_write_iomap_ops, + &xfs_iomap_write_ops,NULL );/* * If we hit a space limit, try to free up some lingering preallocated
@@ -1092,8 +1159,12 @@ xfs_file_buffered_write(if (ret >0 ) {XFS_STATS_ADD (ip->i_mount, xs_write_bytes, ret); -/* Handle various SYNC-type writes */ - ret =generic_write_sync (iocb, ret); +/* +* Handle various SYNC-type writes. +* For writethrough, we handle sync during completion. +*/ +if (!(iocb->ki_flags &IOCB_WRITETHROUGH )) + ret =generic_write_sync (iocb, ret); }return ret; }
@@ -2104,7 +2175,7 @@ const struct file_operations xfs_file_operations = { .remap_file_range = xfs_file_remap_range, .fop_flags =FOP_MMAP_SYNC |FOP_BUFFER_RASYNC |FOP_BUFFER_WASYNC |FOP_DIO_PARALLEL_WRITE | - FOP_DONTCACHE, + FOP_DONTCACHE | FOP_WRITETHROUGH, .setlease = generic_setlease, };
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 05/11] xfs: Add RWF_WRITETHROUGH support to xfs
Quoted 58 lines · Ojaswin Mujoo
Add the boilerplate needed to start supporting
RWF_WRITETHROUGHin XFS. We use the direct write ->iomap_begin() functions to ensure the range under write through always has a real non-delalloc extent. We reuse the xfs dio's end IO function to perform extent conversion andi_sizehandling for us.*Note on COW extent over DATA hole case*
In case of an unmapped COW extent over a DATA hole
(due to COW preallocations), leave the extent unmapped until we are just about to send IO. At that time, use the ->writethrough_submit() call back to convert the COW extent to written.We initially tried converting during iomap
begin()time (like dio does) but that results in a stale data exposure as follows:1. iomap
begin()- converts COW extent over DATA hole to written and marksIOMAP_F_NEWto handle zeroing.
2. Duringiomap_write_begin()-> realise extent is stale and return back without zeroing.
3. iomapbegin()- Again sees the same COW extent but it's written this time so we don't markIOMAP_F_NEW
4. SinceIOMAP_F_NEWis unmarked, we never zeroout and hence expose stale data.To avoid the above, take the buffered IO approach of converting the extent just before IO, when we are sure to have zeroed out the folio.
Co-developed-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>--- fs/xfs/xfs_file.c | 83 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-)fs/xfs/xfs_file.c +15
-0@@ -702,6 +702,36 @@ static const struct iomap_dio_ops xfs_dio_write_ops = { .end_io = xfs_dio_write_end_io, }; +static int +xfs_writethrough_end_io ( +struct iomap_writethrough_ctx *wt_ctx, +ssize_t size, +int error, +unsigned int flags) +{ +struct xfs_inode *ip =XFS_I (wt_ctx->inode); +xfs_off_t offset = wt_ctx->iocb->ki_pos; + +if (unlikely (error)) { +if (wt_ctx->flags &IOMAP_DIO_COW ) +xfs_reflink_cancel_cow_range (ip, offset, size, true); + +return error;
fs/xfs/xfs_file.c static const struct iomap_dio_ops xfs_dio_write_ops = { · line 729
Quoted 10 lines
fs/xfs/xfs_file.c +10
-0+ } + +/* +* writethrough completions are handled same as dio with the exception +* that we need to explicitly change the i_disk_size. This is because +* unlike dio, we have already updated the i_size and hence the +* (i_disk_size < i_size) check will fail in dio code +*/ +xfs_dio_write_end_io (wt_ctx->iocb, size, error, flags); +if (offset + size > ip->i_disk_size)
Okay so mostly the Sashiko reviews are straight forward but I would like to discuss one of it here:
Is it safe to read ip->i_disk_size locklessly here?
On 32-bit systems, a lockless read of a 64-bit value can result in a torn read. If another thread is concurrently updating i_disk_size via xfs_setfilesize(), a torn read might yield a spuriously large value.
Could this cause the offset + size > ip->i_disk_size condition to evaluate to false, skipping the on-disk size update entirely?
Although this seems like a valid concern and we can end up tearing i_disk_size read on 32 bit machines which can lead to a skipped disksize update, I see that same sort of unsafe checks for i_disk_size is also present in the endio code (xfs_ioend_is_append() call)
Shouldnt we ideally be fixing those sites as well or is there something I'm missing in xfs that makes this acceptable?
Regards,
ojaswin
Quoted 93 lines
fs/xfs/xfs_file.c +52
-6+return xfs_setfilesize (ip, offset, size); + +return 0 ; +} + static void xfs_dio_zoned_submit_io( const struct iomap_iter *iter,@@ -1033,6 +1063,39 @@ xfs_file_dax_write(return ret; } +static int +xfs_writethrough_submit ( +struct inode *inode, +struct iomap *iomap, +loff_t offset, +u64 count) +{ +int error =0 ; +unsigned int nofs_flag; + +/* +* Convert CoW extents to regular. +* +* We are under writethrough context with folio lock possibly held. To +* avoid memory allocation deadlocks, set the task-wide nofs context. +*/ +if (iomap->flags &IOMAP_F_SHARED ) { + nofs_flag =memalloc_nofs_save (); + error =xfs_reflink_convert_cow (XFS_I (inode), offset, count); +memalloc_nofs_restore (nofs_flag); + } + +return error; +} + +const struct iomap_writethrough_ops xfs_writethrough_ops = { + .ops = &xfs_direct_write_iomap_ops, + .write_ops = &xfs_iomap_write_ops, + .end_io = xfs_writethrough_end_io, + .writethrough_submit = &xfs_writethrough_submit +}; + + STATIC ssize_t xfs_file_buffered_write( struct kiocb *iocb,@@ -1055,9 +1118,13 @@ xfs_file_buffered_write( goto out;trace_xfs_file_buffered_write (iocb, from); - ret =iomap_file_buffered_write (iocb, from, - &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops, -NULL ); +if (iocb->ki_flags &IOCB_WRITETHROUGH ) { + ret =iomap_file_writethrough_write (iocb, from, + &xfs_writethrough_ops,NULL ); + }else + ret =iomap_file_buffered_write (iocb, from, + &xfs_buffered_write_iomap_ops, + &xfs_iomap_write_ops,NULL );/* * If we hit a space limit, try to free up some lingering preallocated @@ -1092,8 +1159,12 @@ xfs_file_buffered_write(if (ret >0 ) {XFS_STATS_ADD (ip->i_mount, xs_write_bytes, ret); -/* Handle various SYNC-type writes */ - ret =generic_write_sync (iocb, ret); +/* +* Handle various SYNC-type writes. +* For writethrough, we handle sync during completion. +*/ +if (!(iocb->ki_flags &IOCB_WRITETHROUGH )) + ret =generic_write_sync (iocb, ret); }return ret; }@@ -2104,7 +2175,7 @@ const struct file_operations xfs_file_operations = { .remap_file_range = xfs_file_remap_range, .fop_flags =FOP_MMAP_SYNC |FOP_BUFFER_RASYNC |FOP_BUFFER_WASYNC |FOP_DIO_PARALLEL_WRITE | - FOP_DONTCACHE, + FOP_DONTCACHE | FOP_WRITETHROUGH, .setlease = generic_setlease, };Signature · 1 line
2.55.0
RFC PATCH v3 6/11 iomap: Add aio support to RWF_WRITETHROUGH
In reply to Ojaswin MujooWith aio the only thing we need to be careful of is that writethrough can be in progress even after dropping inode and folio lock. Due to this, we need a way to synchronise with other paths where stable write is not enough, example:
1. Truncate to 0 in xfs sets i_size = 0 before waiting for writeback to complete. In case of writethrough, the end io completion can again push the i_size to a non-zero value.
2. Dio reads might race with aio writethrough ->end_io() and read 0s if unwritten conversion is yet to happen.
Hence use the dio begin/end as it gives us the required guarantees.
--- fs/iomap/buffered-io.c | 54 ++++++++++++++++++++++++++++++++++++------ include/linux/iomap.h | 11 +++++++-- 2 files changed, 56 insertions(+), 9 deletions(-)
fs/iomap/buffered-io.c +47 -7
@@ -1202,6 +1202,9 @@ static ssize_t iomap_writethrough_complete(struct iomap_writethrough_ctx *wt_ctxmapping_dec_inflight_stable_writes (inode->i_mapping); +if (wt_ctx->is_aio) +inode_dio_end (inode); +if (!ret) { ret = wt_ctx->written; iocb->ki_pos += ret;
@@ -1211,12 +1214,27 @@ static ssize_t iomap_writethrough_complete(struct iomap_writethrough_ctx *wt_ctxreturn ret; } +static void iomap_writethrough_complete_work (struct work_struct *work) +{ +struct iomap_writethrough_ctx *wt_ctx = + container_of(work, struct iomap_writethrough_ctx, aio_work); +struct kiocb *iocb = wt_ctx->iocb; + + iocb->ki_complete (iocb,iomap_writethrough_complete (wt_ctx)); +} +static void iomap_writethrough_done (struct iomap_writethrough_ctx *wt_ctx) { -struct task_struct *waiter = wt_ctx->waiter; +if (!wt_ctx->is_aio) { +struct task_struct *waiter = wt_ctx->waiter; -WRITE_ONCE (wt_ctx->waiter,NULL ); -blk_wake_io_task (waiter); +WRITE_ONCE (wt_ctx->waiter,NULL ); +blk_wake_io_task (waiter); +return ; + } + +INIT_WORK (&wt_ctx->aio_work, iomap_writethrough_complete_work); +queue_work (wt_ctx->inode->i_sb->s_dio_done_wq, &wt_ctx->aio_work); }static void iomap_writethrough_bio_end_io (struct bio *bio)
@@ -1729,9 +1747,6 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,if (iocb_is_dsync (iocb))/* D_SYNC support not implemented yet */ return -EOPNOTSUPP ; -if (!is_sync_kiocb (iocb)) -/* aio support not implemented yet */ -return -EOPNOTSUPP ;/* * +1 to max bvecs to account for unaligned write spanning multiple
@@ -1750,11 +1765,33 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i, wt_ctx->end_io = wt_ops->end_io; wt_ctx->old_i_size =i_size_read (inode); wt_ctx->max_bvecs = max_bvecs; + wt_ctx->is_aio = !is_sync_kiocb (iocb);atomic_set (&wt_ctx->ref,1 ); - wt_ctx->waiter = current; + +if (!wt_ctx->is_aio) + wt_ctx->waiter = current; +else +/* +* With aio, writethrough can be in progress even after dropping +* inode and folio lock. Due to this, we need a way to +* synchronise with other paths where stable write is not enough +* (example truncate). Hence use the dio begin/end as it gives +* us the required guarantees. +*/ +inode_dio_begin (inode);mapping_inc_inflight_stable_writes (inode->i_mapping); +if (wt_ctx->is_aio && !inode->i_sb->s_dio_done_wq) { + ret =sb_init_dio_done_wq (inode->i_sb); +if (ret <0 ) { +mapping_dec_inflight_stable_writes (inode->i_mapping); +inode_dio_end (inode); +kfree (wt_ctx); +return ret; + } + } +blk_start_plug (&plug);while ((ret =iomap_iter (&iter, wt_ops->ops)) >0 ) {
@@ -1769,6 +1806,9 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,cmpxchg (&wt_ctx->error,0 , ret);if (!atomic_dec_and_test (&wt_ctx->ref)) { +if (wt_ctx->is_aio) +return -EIOCBQUEUED ; +for (;;) {set_current_state (TASK_UNINTERRUPTIBLE );if (!READ_ONCE (wt_ctx->waiter))
include/linux/iomap.h +9 -2
@@ -572,9 +572,16 @@ struct iomap_writethrough_ctx {atomic_t ref;unsigned int flags;int error; +bool is_aio; + +union { +/* used during submission and for non-aio completion */ +struct task_struct *waiter; + +/* used during aio completion */ +struct work_struct aio_work; + }; -/* used during submission and for non-aio completion */ -struct task_struct *waiter;int (*end_io)(struct iomap_writethrough_ctx *wt_ctx,ssize_t size,int error,unsigned int flags);
Signature · 1 line
2.55.0
RFC PATCH v3 7/11 iomap: Add DSYNC support to RWF_WRITETHROUGH
In reply to Ojaswin MujooAdd DSYNC support to writethrough buffered writes. Unlike the usual buffered writes where we call generic_write_sync() inline during the syscall path, for writethrough we instead sync the data during IO completion path, just like dio.
This allows aio writethrough to be truly async where the syscall can return after IO submission and the sync can then be done asynchronously during IO completion time.
Further, just like dio, we utilize the FUA optimization, if available, to avoid syncing the data for DSYNC operations.
--- fs/iomap/buffered-io.c | 34 +++++++++++++++++++++++++++++++--- include/linux/iomap.h | 1 + 2 files changed, 32 insertions(+), 3 deletions(-)
fs/iomap/buffered-io.c +31 -3
@@ -1208,6 +1208,14 @@ static ssize_t iomap_writethrough_complete(struct iomap_writethrough_ctx *wt_ctxif (!ret) { ret = wt_ctx->written; iocb->ki_pos += ret; + +/* +* If this is a DSYNC write and we couldn't optimize it, make +* sure we push it to stable storage now that we've written +* data. +*/ +if (iocb_is_dsync (wt_ctx->iocb) && !wt_ctx->use_fua) + ret =generic_write_sync (iocb, ret); }kfree (wt_ctx);
@@ -1269,6 +1277,9 @@ iomap_writethrough_submit_bio(struct iomap_writethrough_ctx *wt_ctx,for (i =0 ; i < wt_ctx->nr_bvecs; i++) len += wt_ctx->bvec[i].bv_len; +if (wt_ctx->use_fua) + opf |=REQ_FUA ; + bio =bio_alloc (iomap->bdev, wt_ctx->nr_bvecs, opf,GFP_NOFS ); bio->bi_iter.bi_sector =iomap_sector (iomap, wt_ctx->bio_pos); bio->bi_end_io = iomap_writethrough_bio_end_io;
@@ -1405,6 +1416,19 @@ static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx,if (iter->iomap.type ==IOMAP_INLINE )return -EINVAL ; +/* +* If we realise that cache flush is necessary (eg FUA is not present +* or we need metadata updates) then we turn off the optimization. +*/ +if (wt_ctx->use_fua) { +if (iter->iomap.type !=IOMAP_MAPPED || + (iter->iomap.flags & + (IOMAP_F_NEW |IOMAP_F_SHARED |IOMAP_F_DIRTY )) || + (bdev_write_cache (iter->iomap.bdev) && + !bdev_fua (iter->iomap.bdev))) + wt_ctx->use_fua = false; + } + do { struct folio *folio; size_t offset; /* Offset into folio */
@@ -1744,9 +1768,6 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,return -EINVAL ;if (iocb->ki_flags & (IOCB_DONTCACHE ))return -EINVAL ; -if (iocb_is_dsync (iocb)) -/* D_SYNC support not implemented yet */ -return -EOPNOTSUPP ;/* * +1 to max bvecs to account for unaligned write spanning multiple
@@ -1768,6 +1789,13 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i, wt_ctx->is_aio = !is_sync_kiocb (iocb);atomic_set (&wt_ctx->ref,1 ); +/* +* Similar to dio, we optimistically set use_fua=true to avoid explicit +* sync. In case we later realise cache flush is needed we set it back +* to false. +*/ + wt_ctx->use_fua =iocb_is_dsync (iocb) && !(iocb->ki_flags &IOCB_SYNC ); +if (!wt_ctx->is_aio) wt_ctx->waiter = current;else
include/linux/iomap.h +1 -0
@@ -573,6 +573,7 @@ struct iomap_writethrough_ctx {unsigned int flags;int error;bool is_aio; +bool use_fua;union {/* used during submission and for non-aio completion */
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 07/11] iomap: Add DSYNC support to RWF_WRITETHROUGH
fs/iomap/buffered-io.c static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, · line 1428
Quoted 13 lines
fs/iomap/buffered-io.c +10
-0return -EINVAL ; +/* +* If we realise that cache flush is necessary (eg FUA is not present +* or we need metadata updates) then we turn off the optimization. +*/ +if (wt_ctx->use_fua) { +if (iter->iomap.type !=IOMAP_MAPPED || + (iter->iomap.flags & + (IOMAP_F_NEW |IOMAP_F_SHARED |IOMAP_F_DIRTY )) || + (bdev_write_cache (iter->iomap.bdev) && + !bdev_fua (iter->iomap.bdev)))
Shouldn't this be similar to DIO code: !bdev_write_cache() || bdev_fua() ?
As long as bdev_fua() is supported, it does not matter if the bdev_write_cache is there or not right?
fs/iomap/buffered-io.c static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, · line 1431
Quoted 3 lines
fs/iomap/buffered-io.c +3
-0+ wt_ctx->use_fua = false; + } +
--
Pankaj
Re: [RFC PATCH v3 07/11] iomap: Add DSYNC support to RWF_WRITETHROUGH
Quoted 11 lines · Pankaj Raghav (Samsung)
Quoted 10 lines
fs/iomap/buffered-io.c +8
-0return -EINVAL ; +/* +* If we realise that cache flush is necessary (eg FUA is not present +* or we need metadata updates) then we turn off the optimization. +*/ +if (wt_ctx->use_fua) { + if (iter->iomap.type != IOMAP_MAPPED || + (iter->iomap.flags & + (IOMAP_F_NEW | IOMAP_F_SHARED | IOMAP_F_DIRTY)) ||
Quoted 7 lines
fs/iomap/buffered-io.c static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, · line 1428
Quoted 2 lines
fs/iomap/buffered-io.c +2
-0+ (bdev_write_cache (iter->iomap.bdev) && + !bdev_fua(iter->iomap.bdev)))Shouldn't this be similar to DIO code: !bdev_write_cache() ||
bdev_fua()?As long as
bdev_fua()is supported, it does not matter if thebdev_write_cacheis there or not right?
Hey Pankaj, sorry i somehow thought i replied to this already :/
So yeah we do keep the logic same as dio. In dio we enable FUA if:
(!bdev_write_cache(iomap->bdev) || bdev_fua(iomap->bdev)))
and here we __disable__ if
(bdev_write_cache(iter->iomap.bdev) && !bdev_fua(iter->iomap.bdev)))
So its the exact inverse of the dio check.
Regards,
ojaswin
Quoted 7 lines
fs/iomap/buffered-io.c static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, · line 1431
Quoted 4 lines
fs/iomap/buffered-io.c +3
-0+ wt_ctx->use_fua = false; + } +--
Pankaj
RFC PATCH v3 8/11 fs: Introduce RWF_NOSERIAL flag to indicate parallel reads/writes
In reply to Ojaswin MujooIntroduce RWF_NOSERIAL flag to indicate that the application is okay with its reads and writes going in parallel to other read/writes. This flag will allow writes and read to go in parallel (for eg, under a shared lock) increasing performance at the cost of losing the (loosely implemented) POSIX guarantees wrt to R/W serialization that various filesystems provide.
In this patch we just introduce the flag and in upcoming patches we will use it to implement parallel writes to increase performance of RWF_WRITETHROUGH
--- include/linux/fs.h | 10 ++++++++++ include/uapi/linux/fs.h | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-)
include/linux/fs.h +10 -0
@@ -345,6 +345,7 @@ struct readahead_control;#define IOCB_DONTCACHE (__force int) RWF_DONTCACHE#define IOCB_NOSIGNAL (__force int) RWF_NOSIGNAL#define IOCB_WRITETHROUGH (__force int) RWF_WRITETHROUGH +#define IOCB_NOSERIAL (__force int) RWF_NOSERIAL/* non-RWF related bits - start at 16 */ #define IOCB_EVENTFD (1 << 16)
@@ -3475,6 +3476,15 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,if (IS_DAX (ki->ki_filp->f_mapping->host))return -EOPNOTSUPP ; } + +/* +* For now we don't allow users to pass the NOSERIAL flag +* TODO: Once the semantics of this flag are finalized we can lift this +* restriction. +*/ +if (flags &IOCB_NOSERIAL ) +return -EOPNOTSUPP ; + kiocb_flags |= (__force int ) (flags &RWF_SUPPORTED );if (flags &RWF_SYNC ) kiocb_flags |=IOCB_DSYNC ;
include/uapi/linux/fs.h +5 -1
@@ -454,10 +454,14 @@ typedef int __bitwise __kernel_rwf_t;/* buffered IO that is asynchronously written through to disk after write */ #define RWF_WRITETHROUGH ((__force __kernel_rwf_t)0x00000200) +/* buffered IO writes that are non sequential because they use a shared lock */ +#define RWF_NOSERIAL ((__force __kernel_rwf_t)0x00000400) +/* mask of flags supported by the kernel */ #define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\ RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\ - RWF_DONTCACHE | RWF_NOSIGNAL | RWF_WRITETHROUGH) + RWF_DONTCACHE | RWF_NOSIGNAL | RWF_WRITETHROUGH |\ + RWF_NOSERIAL)#define PROCFS_IOCTL_MAGIC 'f'
Signature · 1 line
2.55.0
RFC PATCH v3 9/11 xfs: Implement RWF_NOSERIAL to parallelize RWF_WRITETHROUGH writes
In reply to Ojaswin MujooIn xfs, buffered writethrough writes take an exclusive inode lock similar to regular buffered write path. However, since writethrough also submits the write under the inode lock, the increased critical section really hurts performance when we have parallel writers writing to the same file.
To mitigate this, implement RWF_NOSERIAL flag which allows us to perform the write under a shared inode lock, instead of an exclusive lock. This gives significant performance boost to single file, multiple writer workloads at the cost of losing write-write serialization and read-write serialization guarantees which XFS has historically provided.
Let's look at each of the guarantees and what changes with the NOSERIAL flag:
Write-write guarantee:
Image 2 writers trying to write the same 3 folios. One is write As to all 3 folios (denoted by AAA) and the other is writing BBB. Then under exclusive lock the final state of the 3 folios could only be either AAA or BBB.
However, with NOSERIAL writes, we could end up with mixed data in the 3 folios, like AAB, ABA, BAA etc. Note that this mixing will always happen at the inter folio level, data contained within the same folio will not be mixed as it is protected by the folio lock.
Read-write guarantee:
In XFS, reads also take a shared lock, however they don't take a folio lock when copying from the folio to the user buffer. Due to the shared lock of read and exclusive lock of write, we are able to guarantee that the read always reads either completely old data or completely new data.
However, with the NOSERIAL flag, writethrough will take a shared lock for writes, ie a read can race with a write which is still in middle of copying data to the folio, hence the read can read a mix of old and new data.
Despite the above changes in behavior, there might be applications who would be okay to lose the guarantees because of the nature of their workloads for example, if they never have multiple readers/writes doing IO to the same range in the file. Such applications would benefit significantly by using NOSERIAL writes.
Below are some fio performance numbers of RWF_WRITETHROUGH with and without NOSERIAL writes.
** Multiple writes, single file (Pure overwrites, DSYNC) **
Fio Workload:
libaio, buffered randwrite, bs=4k, size=2G (pre written), O_DSYNC iodepth=32
numjobs baseline BW RWF_NOSERIAL BW Δ%
1 350 392 +12.0%
2 526 798 +51.7%
4 597 1591 +166.5%
8 630 1839 +191.9%
16 570 1836 +222.1%** Multiple writes, single file (Preallocated file, sync_file_range) **
Fio Workload:
libaio, buffered randwrite, bs=16k, size=5G (fallocated) sync_file_range=wait_before,write:16
numjobs baseline BW RWF_NOSERIAL BW Δ%
1 1099 1279 +16.4%
2 1482 2456 +65.7%
4 2065 2479 +20.1%
8 1778 2500 +40.6%
16 1787 2508 +40.3%* Multiple writes, single file (Truncated file, DSYNC) * Fio Workload:
libaio, buffered randwrite, bs=4k, size=6.5G (truncated), O_DSYNC iodepth=32
numjobs baseline BW RWF_NOSERIAL BW Δ%
1 78 80 +2.6%
2 97 88 -9.3%
4 100 96 -4.0%
8 109 97 -11.0%
16 106 107 +0.9%--- fs/xfs/xfs_file.c | 54 ++++++++++++++++++++++++++++++++++++++++------ include/linux/fs.h | 7 ++++++ 2 files changed, 55 insertions(+), 6 deletions(-)
fs/xfs/xfs_file.c +48 -6
@@ -520,6 +520,42 @@ xfs_file_write_checks(return kiocb_modified (iocb); } +STATIC ssize_t +xfs_file_writethrough_checks ( +struct kiocb *iocb, +struct iov_iter *from, +unsigned int *iolock, +struct xfs_zone_alloc_ctx *ac) +{ +struct inode *inode = iocb->ki_filp->f_mapping->host; +size_t isize =i_size_read (inode); +size_t count =iov_iter_count (from); +ssize_t error; + + error =xfs_file_write_checks (iocb, from, iolock, ac); +if (error <0 ) +return error; + +if (*iolock ==XFS_IOLOCK_EXCL ) +return 0 ; + +/* +* Extending IO needs exclusive lock for i_size change +*/ +if (iocb->ki_pos > isize || iocb->ki_pos + count >= isize) + goto upgrade_excl; + +return 0 ; + +upgrade_excl: +xfs_iunlock (XFS_I (inode), *iolock); + *iolock =XFS_IOLOCK_EXCL ; + error =xfs_ilock_iocb (iocb, *iolock); +if (error) + *iolock =0 ; +return error; +} + static ssize_t xfs_zoned_write_space_reserve( struct xfs_mount *mp,
@@ -1108,23 +1144,29 @@ xfs_file_buffered_write(unsigned int iolock; write_retry: - iolock =XFS_IOLOCK_EXCL ; +if (iocb->ki_flags &IOCB_NOSERIAL ) + iolock =XFS_IOLOCK_SHARED ; +else + iolock =XFS_IOLOCK_EXCL ; ret =xfs_ilock_iocb (iocb, iolock);if (ret)return ret; - ret =xfs_file_write_checks (iocb, from, &iolock,NULL ); -if (ret) - goto out; -trace_xfs_file_buffered_write (iocb, from);if (iocb->ki_flags &IOCB_WRITETHROUGH ) { + ret =xfs_file_writethrough_checks (iocb, from, &iolock,NULL ); +if (ret) + goto out; ret =iomap_file_writethrough_write (iocb, from, &xfs_writethrough_ops,NULL ); - }else + }else { + ret =xfs_file_write_checks (iocb, from, &iolock,NULL ); +if (ret) + goto out; ret =iomap_file_buffered_write (iocb, from, &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,NULL ); + }/* * If we hit a space limit, try to free up some lingering preallocated
include/linux/fs.h +7 -0
@@ -3495,6 +3495,13 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags, ki->ki_flags &= ~IOCB_APPEND ; } +/* +* Writethrough implies non-serial writes ie writes can go +* parallelly. +*/ +if (flags &RWF_WRITETHROUGH ) + kiocb_flags |=IOCB_NOSERIAL ; + ki->ki_flags |= kiocb_flags;return 0 ; }
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 09/11] xfs: Implement RWF_NOSERIAL to parallelize RWF_WRITETHROUGH writes
include/linux/fs.h static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags, · line 3504
Quoted 9 lines
include/linux/fs.h +7
-0} +/* +* Writethrough implies non-serial writes ie writes can go +* parallelly. +*/ +if (flags &RWF_WRITETHROUGH ) + kiocb_flags |=IOCB_NOSERIAL ; +
From a user perspective, shouldn't this be an explicit user flag instead of implicitly enabling for all RWF_WRITETHROUGH?
Power users who would want performance over POSIX compliance can enable this but it should not be the other way around IMO.
--
Pankaj
Re: [RFC PATCH v3 09/11] xfs: Implement RWF_NOSERIAL to parallelize RWF_WRITETHROUGH writes
Quoted 13 lines · Pankaj Raghav (Samsung)
include/linux/fs.h static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags, · line 3504
Quoted 9 lines
include/linux/fs.h +7
-0} +/* +* Writethrough implies non-serial writes ie writes can go +* parallelly. +*/ +if (flags &RWF_WRITETHROUGH ) + kiocb_flags |=IOCB_NOSERIAL ; +From a user perspective, shouldn't this be an explicit user flag instead of implicitly enabling for all
RWF_WRITETHROUGH?
Quoted 5 lines
Power users who would want performance over POSIX compliance can enable this but it should not be the other way around IMO.
--
Pankaj
Hi Pankaj,
Yes I think we just wanted to float the idea of such a flag but I was unsure right now whether to have users pass it or enable it implicitly.
Thinking about it again I think I agree that we can allow users to pass it even if we just reject it for all cases except writethrough (for now).
Regards,
ojaswin
RFC PATCH v3 10/11 iomap: Avoid folio dirtying in case of RWF_WRITETHROUGH
In reply to Ojaswin MujooRWF_WRITETHROUGH dirties the folio only to send it for IO immediately, in the same context. Due to this, we can optimize away the folio dirtying and clearing step usually seen in buffered IO. Althrough we can do away with most of the accounting there are a couple of counters we need to take care of which we do during IO submission/completion.
--- fs/iomap/buffered-io.c | 64 +++++++++++++++++++++++++++++++++-------- include/linux/pagemap.h | 1 + mm/filemap.c | 21 ++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-)
fs/iomap/buffered-io.c +52 -12
@@ -11,6 +11,9 @@#include <linux/fserror.h> #include <linux/fsverity.h> #include <linux/rmap.h> +#include <linux/task_io_accounting_ops.h> +#include "linux/pagemap.h" +#include "linux/page-flags.h" #include "internal.h" #include "trace.h"
@@ -1162,6 +1165,34 @@ static bool iomap_write_end_inline(const struct iomap_iter *iter,return true; } +/* +* __iomap_writethrough_end() is almost same as __iomap_write_end() but with the difference +* that we don't mark folio dirty since we are about to issue it for IO anyways. +* Consequently, most of the accounting is skipped. +*/ +static bool __iomap_writethrough_end (struct inode *inode,loff_t pos,size_t len, +size_t copied,struct folio *folio) +{ +flush_dcache_folio (folio); + +/* +* The blocks that were entirely written will now be up-to-date, so we +* don't have to worry about a read_folio reading them and overwriting a +* partial write. However, if we've encountered a short write and only +* partially written into a block, it will not be marked up-to-date, so a +* read_folio might come in and destroy our partial write. +* +* Do the simplest thing and just treat any short write to a +* non-uptodate page as a zero-length write, and force the caller to +* redo the whole thing. +*/ +if (unlikely (copied < len && !folio_test_uptodate (folio))) +return false; +iomap_set_range_uptodate (folio,offset_in_folio (folio, pos), len); +return true; +} + +/* * Returns true if all copied bytes have been written to the pagecache, * otherwise return false.
@@ -1183,7 +1214,10 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied,return bh_written == copied; } -return __iomap_write_end (iter->inode, pos, len, copied, folio); +if (iter->flags &IOMAP_WRITETHROUGH ) +return __iomap_writethrough_end (iter->inode, pos, len, copied, folio); +else +return __iomap_write_end (iter->inode, pos, len, copied, folio); }static ssize_t iomap_writethrough_complete (struct iomap_writethrough_ctx *wt_ctx)
@@ -1254,7 +1288,7 @@ static void iomap_writethrough_bio_end_io(struct bio *bio)cmpxchg (&wt_ctx->error,0 ,blk_status_to_errno (bio->bi_status));bio_for_each_folio_all (fi, bio) -folio_end_writeback (fi.folio); +folio_end_writethrough (fi.folio, wt_ctx->error);bio_put (bio);if (atomic_dec_and_test (&wt_ctx->ref))
@@ -1299,9 +1333,11 @@ iomap_writethrough_submit_bio(struct iomap_writethrough_ctx *wt_ctx,/* * In case of error we still need the I/O completion to run so we can -* release references and end writeback on the folios. +* release references, handle accounting and end writeback on the +* folios. */ if (error) { +task_io_account_cancelled_write (len); bio->bi_status =errno_to_blk_status (error);bio_endio (bio);return error;
@@ -1351,6 +1387,7 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off, {bool fully_written;u64 zero =0 ; +u64 tmp_off = off;if (folio_test_writeback (folio))folio_wait_writeback (folio);
@@ -1359,17 +1396,20 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off,folio_mark_dirty (folio);/* -* We might either write through the complete folio or a partial folio -* writethrough might result in all blocks becoming non-dirty, so we need to -* check and mark the folio clean if that is the case. +* For writethrough, we don't mark the write range dirty but we still +* need clear the dirty range if someone else has dirtied it before. +* Further, if the clearing results in folio becoming completely clean, +* then we need to take care of accounting. */ - fully_written = (off ==0 && len ==folio_size (folio)); -iomap_clear_range_dirty (folio, off, len); -if (fully_written || - !iomap_find_dirty_range (folio, &zero,folio_size (folio))) -folio_clear_dirty_for_writethrough (folio); +if (iomap_find_dirty_range (folio, &tmp_off, tmp_off + len)) { +iomap_clear_range_dirty (folio, off, len); -folio_start_writeback (folio); +if (!iomap_find_dirty_range (folio, &zero,folio_size (folio))) +folio_clear_dirty_for_writethrough (folio); + } + +task_io_account_write (folio_nr_pages (folio) *PAGE_SIZE ); +folio_test_set_writeback (folio); }/**
include/linux/pagemap.h +1 -0
@@ -1257,6 +1257,7 @@ void folio_wait_writeback(struct folio *folio);int folio_wait_writeback_killable (struct folio *folio);void end_page_writeback (struct page *page);void folio_end_writeback (struct folio *folio); +void folio_end_writethrough (struct folio *folio,bool error);void folio_end_writeback_no_dropbehind (struct folio *folio);void folio_end_dropbehind (struct folio *folio);void folio_wait_stable (struct folio *folio);
mm/filemap.c +21 -0
@@ -1695,6 +1695,27 @@ void folio_end_writeback(struct folio *folio) }EXPORT_SYMBOL (folio_end_writeback); +/** +* folio_end_writethrough - End writethrough against a folio +* @folio: The folio. +* error: Was there an error in IO. +* +* Context: May be called from process or interrupt context. +*/ +void folio_end_writethrough (struct folio *folio,bool error) +{ +long nr =folio_nr_pages (folio); + +VM_BUG_ON_FOLIO (!folio_test_writeback (folio), folio); + +if (!error) +node_stat_mod_folio (folio,NR_WRITTEN , nr); + +if (folio_xor_flags_has_waiters (folio,1 << PG_writeback)) +folio_wake_bit (folio, PG_writeback); +} +EXPORT_SYMBOL (folio_end_writethrough); +/** * __folio_lock - Get a lock on the folio, assuming we need to sleep to get it. * @folio: The folio to lock
Signature · 1 line
2.55.0
Re: [RFC PATCH v3 10/11] iomap: Avoid folio dirtying in case of RWF_WRITETHROUGH
Quoted 115 lines · Ojaswin Mujoo
RWF_WRITETHROUGHdirties the folio only to send it for IO immediately, in the same context. Due to this, we can optimize away the folio dirtying and clearing step usually seen in buffered IO. Althrough we can do away with most of the accounting there are a couple of counters we need to take care of which we do during IO submission/completion.Co-developed-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>--- fs/iomap/buffered-io.c | 64 +++++++++++++++++++++++++++++++++-------- include/linux/pagemap.h | 1 + mm/filemap.c | 21 ++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-)fs/iomap/buffered-io.c +42
-6@@ -11,6 +11,9 @@#include <linux/fserror.h> #include <linux/fsverity.h> #include <linux/rmap.h> +#include <linux/task_io_accounting_ops.h> +#include "linux/pagemap.h" +#include "linux/page-flags.h" #include "internal.h" #include "trace.h" @@ -1162,6 +1165,34 @@ static bool iomap_write_end_inline(const struct iomap_iter *iter,return true; } +/* +* __iomap_writethrough_end() is almost same as __iomap_write_end() but with the difference +* that we don't mark folio dirty since we are about to issue it for IO anyways. +* Consequently, most of the accounting is skipped. +*/ +static bool __iomap_writethrough_end (struct inode *inode,loff_t pos,size_t len, +size_t copied,struct folio *folio) +{ +flush_dcache_folio (folio); + +/* +* The blocks that were entirely written will now be up-to-date, so we +* don't have to worry about a read_folio reading them and overwriting a +* partial write. However, if we've encountered a short write and only +* partially written into a block, it will not be marked up-to-date, so a +* read_folio might come in and destroy our partial write. +* +* Do the simplest thing and just treat any short write to a +* non-uptodate page as a zero-length write, and force the caller to +* redo the whole thing. +*/ +if (unlikely (copied < len && !folio_test_uptodate (folio))) +return false; +iomap_set_range_uptodate (folio,offset_in_folio (folio, pos), len); +return true; +} + +/* * Returns true if all copied bytes have been written to the pagecache, * otherwise return false. @@ -1183,7 +1214,10 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied,return bh_written == copied; } -return __iomap_write_end (iter->inode, pos, len, copied, folio); +if (iter->flags &IOMAP_WRITETHROUGH ) +return __iomap_writethrough_end (iter->inode, pos, len, copied, folio); +else +return __iomap_write_end (iter->inode, pos, len, copied, folio); }static ssize_t iomap_writethrough_complete (struct iomap_writethrough_ctx *wt_ctx)@@ -1254,7 +1288,7 @@ static void iomap_writethrough_bio_end_io(struct bio *bio)cmpxchg (&wt_ctx->error,0 ,blk_status_to_errno (bio->bi_status));bio_for_each_folio_all (fi, bio) -folio_end_writeback (fi.folio); +folio_end_writethrough (fi.folio, wt_ctx->error);bio_put (bio);if (atomic_dec_and_test (&wt_ctx->ref))@@ -1299,9 +1333,11 @@ iomap_writethrough_submit_bio(struct iomap_writethrough_ctx *wt_ctx,/* * In case of error we still need the I/O completion to run so we can -* release references and end writeback on the folios. +* release references, handle accounting and end writeback on the +* folios. */ if (error) { +task_io_account_cancelled_write (len); bio->bi_status =errno_to_blk_status (error);bio_endio (bio);return error;@@ -1351,6 +1387,7 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off, {bool fully_written;u64 zero =0 ; +u64 tmp_off = off;if (folio_test_writeback (folio))folio_wait_writeback (folio);@@ -1359,17 +1396,20 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off,folio_mark_dirty (folio);/* -* We might either write through the complete folio or a partial folio -* writethrough might result in all blocks becoming non-dirty, so we need to -* check and mark the folio clean if that is the case. +* For writethrough, we don't mark the write range dirty but we still +* need clear the dirty range if someone else has dirtied it before.
fs/iomap/buffered-io.c static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off, · line 1405
Quoted 10 lines
fs/iomap/buffered-io.c +4
-5+* Further, if the clearing results in folio becoming completely clean, +* then we need to take care of accounting. */ - fully_written = (off ==0 && len ==folio_size (folio)); -iomap_clear_range_dirty (folio, off, len); -if (fully_written || - !iomap_find_dirty_range (folio, &zero,folio_size (folio))) -folio_clear_dirty_for_writethrough (folio); +if (iomap_find_dirty_range (folio, &tmp_off, tmp_off + len)) { +iomap_clear_range_dirty (folio, off, len);
Does this fail to clear the dirty flag for filesystems without sub-folio state? When the filesystem lacks sub-folio state (for example, when block size equals page size and ifs is NULL), iomap_find_dirty_range() unconditionally returns the queried range length.
This means !iomap_find_dirty_range() will evaluate to !(folio_size(folio)), which is always false. The folio_clear_dirty_for_writethrough() call is then bypassed, leaving fully overwritten folios dirty in the page cache.
Right, this is true, we need to also consider if a folio doesn't have ifs. Will fix this.
fs/iomap/buffered-io.c static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off, · line 1412
Quoted 8 lines
fs/iomap/buffered-io.c +6
-1-folio_start_writeback (folio); +if (!iomap_find_dirty_range (folio, &zero,folio_size (folio))) +folio_clear_dirty_for_writethrough (folio); + } + +task_io_account_write (folio_nr_pages (folio) *PAGE_SIZE ); +folio_test_set_writeback (folio);
Does this bypass setting PAGECACHE_TAG_WRITEBACK in the mapping's xarray? Using folio_test_set_writeback() directly sets the PG_writeback flag on the folio but skips the xarray tagging performed by __folio_start_writeback(). If fsync() or filemap_fdatawait_range() is called later, they iterate over the xarray looking for this tag to wait for in-flight IO. Because the tag is missing, they will return immediately while the IO is still in progress, breaking data durability guarantees.
Hmm, looking into it, yes I think this is correct, the WRITEBACK tag is definitely needed. So I think we might not be able to use just the master bit here.
Regards,
ojaswin
Quoted 51 lines
fs/iomap/buffered-io.c +0
-0}/** include/linux/pagemap.h +1
-0@@ -1257,6 +1257,7 @@ void folio_wait_writeback(struct folio *folio);int folio_wait_writeback_killable (struct folio *folio);void end_page_writeback (struct page *page);void folio_end_writeback (struct folio *folio); +void folio_end_writethrough (struct folio *folio,bool error);void folio_end_writeback_no_dropbehind (struct folio *folio);void folio_end_dropbehind (struct folio *folio);void folio_wait_stable (struct folio *folio);mm/filemap.c +21
-0@@ -1695,6 +1695,27 @@ void folio_end_writeback(struct folio *folio) }EXPORT_SYMBOL (folio_end_writeback); +/** +* folio_end_writethrough - End writethrough against a folio +* @folio: The folio. +* error: Was there an error in IO. +* +* Context: May be called from process or interrupt context. +*/ +void folio_end_writethrough (struct folio *folio,bool error) +{ +long nr =folio_nr_pages (folio); + +VM_BUG_ON_FOLIO (!folio_test_writeback (folio), folio); + +if (!error) +node_stat_mod_folio (folio,NR_WRITTEN , nr); + +if (folio_xor_flags_has_waiters (folio,1 << PG_writeback)) +folio_wake_bit (folio, PG_writeback); +} +EXPORT_SYMBOL (folio_end_writethrough); +/** * __folio_lock - Get a lock on the folio, assuming we need to sleep to get it. * @folio: The folio to lock Signature · 1 line
2.55.0
RFC PATCH v3 11/11 iomap: Handle deadlock due to repeating folios in RWF_WRITETHROUGH
In reply to Ojaswin MujooIn iomap_writethrough_iter() we might encounter repeating folios across multiple iterations. Repeating folios can occur if, example, copy_folio_from_iter_atomic() does a short copy due to userspace pages not faulted in. This is an issue because a previous loop might have started writeback on them but not yet issued the IO. In the next iteration trying to get the same folio with FGP_STABLE will result in a deadlock. Since repeating folios will always be encountered back to back, we can just use a simple cur != prev check to detect them.
Use this to avoid waiting for writeback or starting writeback on folios we have already processed. Note that in ->endio() we might end up calling folio_end_writethrough() twice on the same folio which can cause issues with folio_xor_flags_has_waiters(). For simplicity, just change the folio_xor_flags_has_waiters() call to an idempotent variant.
--- fs/iomap/buffered-io.c | 37 ++++++++++++++++++++++++++++++------- mm/filemap.c | 3 ++- 2 files changed, 32 insertions(+), 8 deletions(-)
fs/iomap/buffered-io.c +30 -7
@@ -804,6 +804,13 @@ struct folio *iomap_get_folio(struct iomap_iter *iter, loff_t pos, size_t len) {fgf_t fgp =FGP_WRITEBEGIN ; +/* +* For writethrough, we open code the FGP_STABLE logic directly in +* iomap_writhrethrough_iter() so disable it here.. See +* iomap_writethrough_iter() for details. +*/ +if (iter->flags &IOMAP_WRITETHROUGH ) + fgp &= ~FGP_STABLE ;if (iter->flags &IOMAP_NOWAIT ) fgp |=FGP_NOWAIT ;if (iter->flags &IOMAP_DONTCACHE )
@@ -1383,15 +1390,12 @@ iomap_writethrough_try_submit(struct iomap_writethrough_ctx *wt_ctx,* need to clear the master dirty bit. */ static void iomap_folio_prepare_writethrough (struct folio *folio,size_t off, -size_t len) +size_t len,bool already_prepared) {bool fully_written;u64 zero =0 ;u64 tmp_off = off; -if (folio_test_writeback (folio)) -folio_wait_writeback (folio); -if (folio_mkclean (folio))folio_mark_dirty (folio);
@@ -1409,7 +1413,8 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off, }task_io_account_write (folio_nr_pages (folio) *PAGE_SIZE ); -folio_test_set_writeback (folio); +if (!already_prepared) +folio_test_set_writeback (folio); }/**
@@ -1426,6 +1431,17 @@ static void iomap_folio_prepare_writethrough(struct folio *folio, size_t off,* Folio handling note: We might be writing through a partial folio so we need * to be careful to not clear the folio dirty bit unless there are no dirty blocks * in the folio after the writethrough. +* +* **A corner case to be careful about** +* +* For writethrough, we open code the stable write behavior to handle the case +* where we encounter a folio that we already started writeback on but have not +* yet submitted. In that case we must not wait for writeback again to avoid +* deadlocking. Repeating folios can occur if, example, +* copy_folio_from_iter_atomic() does a short copy due to userspace pages not +* faulted in. Also, repeating folios will always be encountered back to back so +* we can just use a simple cur != prev check to detect them. +*/ static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, struct iomap_iter *iter, struct iov_iter *i,
@@ -1439,6 +1455,7 @@ static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx,size_t chunk =mapping_max_folio_size (mapping);unsigned int bdp_flags = (iter->flags &IOMAP_NOWAIT ) ?BDP_ASYNC :0 ;unsigned int bs =i_blocksize (iter->inode); +struct folio *prev_folio =NULL ;/* copied over based on how DIO handles these flags */ if (iter->iomap.type ==IOMAP_UNWRITTEN )
@@ -1530,6 +1547,10 @@ static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx,if (mapping_writably_mapped (mapping))flush_dcache_folio (folio); +/* Open coding stable write behavior, see comment on top. */ +if (prev_folio != folio) +folio_wait_writeback (folio); + copied =copy_folio_from_iter_atomic (folio, offset, bytes, i); written =iomap_write_end (iter, bytes, copied, folio) ? copied :0 ;
@@ -1544,8 +1565,10 @@ static int iomap_writethrough_iter(struct iomap_writethrough_ctx *wt_ctx, off_aligned =round_down (offset, bs); len_aligned =round_up (offset + written, bs) - off_aligned; -iomap_folio_prepare_writethrough (folio, off_aligned, - len_aligned); +iomap_folio_prepare_writethrough ( + folio, off_aligned, len_aligned, prev_folio == folio); + + prev_folio = folio;if (!wt_ctx->nr_bvecs) { wt_ctx->bio_pos =round_down (pos, bs);
mm/filemap.c +2 -1
@@ -1711,7 +1711,8 @@ void folio_end_writethrough(struct folio *folio, bool error)if (!error)node_stat_mod_folio (folio,NR_WRITTEN , nr); -if (folio_xor_flags_has_waiters (folio,1 << PG_writeback)) +folio_test_clear_writeback (folio); +if (folio_test_waiters (folio))folio_wake_bit (folio, PG_writeback); }EXPORT_SYMBOL (folio_end_writethrough);
Signature · 1 line
2.55.0
Quoted 1 line · Ojaswin Mujoo
This is the next revision of writethrough series. I'll quote part of the
(+cc Andres)