Re: [f2fs-dev] [PATCH v2 4/4] f2fs: Run f2fs_write_end_io() asynchronously

Chao Yu via Linux-f2fs-devel <[email protected]> Tue, 28 Jul 2026 10:26:30 +0800
Newsgroups net.sourceforge.lists.linux-f2fs-devel
Message-ID <[email protected]>
On 6/11/26 03:34, Bart Van Assche wrote:
> 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;

Bart,

Can you please update sbi->max_atc_write_bio_size to UINT_MAX to disable new
feature by default? then we can merge this patch first.

And then we can do some tests to figure out what is the preferred number for
max_atc_write_bio_size in product, and set it via sysfs node.

Thanks,

>   
>   	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