The bio_for_each_segment_all() loop can take more than 10 ms for a large
bio on an ARM little core. This is too much for interrupt context. Hence
perform the write bio completion work asynchronously if a bio is large and
if f2fs_write_end_io() is called from atomic context. This patch reduces
the time spent in f2fs_write_end_io() from about 10 ms to about 150
microseconds on an Arm Cortex-A520 core.

Signed-off-by: Bart Van Assche <[email protected]>
---
 Documentation/ABI/testing/sysfs-fs-f2fs |  9 +++++++++
 fs/f2fs/data.c                          | 21 ++++++++++++++++++++-
 fs/f2fs/f2fs.h                          |  2 ++
 fs/f2fs/super.c                         |  5 +++++
 fs/f2fs/sysfs.c                         |  2 ++
 5 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs 
b/Documentation/ABI/testing/sysfs-fs-f2fs
index 423ec40e2e4e..7c6fe1e7e9bc 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -1001,3 +1001,12 @@ Description:     It can be used to tune priority of f2fs 
critical task, e.g. f2fs_ck
                threads, limitation as below:
                - it requires user has CAP_SYS_NICE capability.
                - the range is [100, 139], by default the value is 100.
+
+What:          /sys/fs/f2fs/<disk>/max_atc_write_bio_size
+Date:          June 2026
+Contact:       Bart Van Assche <[email protected]>
+Description:   Every time a write operation completes f2fs_write_end_io() is
+               called. This function may be called from an atomic context,
+               e.g. from inside an interrupt handler. This attribute controls
+               the maximum size of a write bio that is completed in atomic
+               (atc) context.
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 48c004976c4e..6e169490c4cf 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -409,11 +409,30 @@ static void f2fs_write_end_bio(struct bio *bio)
        bio_put(bio);
 }
 
+static void f2fs_write_end_io_work(struct work_struct *work)
+{
+       struct bio *bio = &container_of(work, struct f2fs_bio, work)->bio;
+
+       f2fs_write_end_bio(bio);
+}
+
 static void f2fs_write_end_io(struct bio *bio)
 {
+       struct f2fs_sb_info *sbi;
+
        iostat_update_and_unbind_ctx(bio);
 
-       f2fs_write_end_bio(bio);
+       sbi = bio->bi_private;
+
+       if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) {
+               struct work_struct *w;
+
+               w = &container_of(bio, struct f2fs_bio, bio)->work;
+               INIT_WORK(w, f2fs_write_end_io_work);
+               queue_work(sbi->wq, w);
+       } else {
+               f2fs_write_end_bio(bio);
+       }
 }
 
 #ifdef CONFIG_BLK_DEV_ZONED
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 30353c439d3c..a6a3e01122e1 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1763,6 +1763,8 @@ struct f2fs_sb_info {
        struct f2fs_sm_info *sm_info;           /* segment manager */
 
        /* for bio operations */
+       /* Largest write bio size completed in atomic context (atc). */
+       u32 max_atc_write_bio_size;
        struct f2fs_bio_info *write_io[NR_PAGE_TYPE];   /* for write bios */
        /* keep migration IO order for LFS mode */
        struct f2fs_rwsem io_order_lock;
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 5a100f740b3f..1e822380edb3 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5007,6 +5007,11 @@ static int f2fs_fill_super(struct super_block *sb, 
struct fs_context *fc)
 
        sb->s_fs_info = sbi;
        sbi->raw_super = raw_super;
+       /*
+        * SZ_16K restricts the time spent on completing writes to about 150
+        * microseconds on an Arm Cortex-A520 core.
+        */
+       sbi->max_atc_write_bio_size = SZ_16K;
 
        INIT_WORK(&sbi->s_error_work, f2fs_record_error_work);
        memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS);
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index 352e96ad5c3a..70b2e9be8f8b 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -1266,6 +1266,7 @@ F2FS_SBI_RW_ATTR(gc_idle_interval, 
interval_time[GC_TIME]);
 F2FS_SBI_RW_ATTR(umount_discard_timeout, 
interval_time[UMOUNT_DISCARD_TIMEOUT]);
 F2FS_SBI_RW_ATTR(gc_pin_file_thresh, gc_pin_file_threshold);
 F2FS_SBI_RW_ATTR(gc_reclaimed_segments, gc_reclaimed_segs);
+F2FS_SBI_RW_ATTR(max_atc_write_bio_size, max_atc_write_bio_size);
 F2FS_SBI_GENERAL_RW_ATTR(max_victim_search);
 F2FS_SBI_GENERAL_RW_ATTR(migration_granularity);
 F2FS_SBI_GENERAL_RW_ATTR(migration_window_granularity);
@@ -1508,6 +1509,7 @@ static struct attribute *f2fs_attrs[] = {
        ATTR_LIST(seq_file_ra_mul),
        ATTR_LIST(gc_segment_mode),
        ATTR_LIST(gc_reclaimed_segments),
+       ATTR_LIST(max_atc_write_bio_size),
        ATTR_LIST(max_fragment_chunk),
        ATTR_LIST(max_fragment_hole),
        ATTR_LIST(current_atomic_write),


_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

Reply via email to