* [PATCH 2/3] f2fs: introduce max_write_bio_{folio,entry}_cnt module parameters
2026-10-22 12:58 [PATCH 1/3] f2fs: fix to account entry_cnt for all write bios Chao Yu
@ 2026-10-22 12:58 ` Chao Yu
2026-10-22 12:58 ` [PATCH 3/3] f2fs: remove max_atc_write_bio_size Chao Yu
1 sibling, 0 replies; 3+ messages in thread
From: Chao Yu @ 2026-10-22 12:58 UTC (permalink / raw)
To: jaegeuk; +Cc: linux-f2fs-devel, linux-kernel, Chao Yu
From: Chao Yu <chao@kernel.org>
Commit f32744a8b83d ("f2fs: introduce max_atc_write_bio_entry_cnt")
added a per-superblock sysfs entry, max_atc_write_bio_entry_cnt, to
offload the completion of a write bio to sbi->wq if it contains too
many entries and f2fs_write_end_io() is called in atomic context.
However, it has two issues:
1. The limit is used to bound the CPU latency of f2fs_write_end_io() in
atomic context, which is a property of the platform (CPU and storage
completion path) rather than of each f2fs instance, so a global knob
set once at boot fits better than a per-superblock sysfs entry.
2. Pagecache folios and cached metadata blocks are completed in
different loops with different per-entry costs, i.e.
f2fs_write_end_bio() and f2fs_cache_write_end_bio(), so it's better
to tune them separately rather than sharing a single threshold.
To address these issues, let's replace max_atc_write_bio_entry_cnt
sysfs entry with two module parameters:
- max_write_bio_folio_cnt: for write bios of pagecache folios, which
are completed in f2fs_write_end_bio().
- max_write_bio_entry_cnt: for write bios of cached metadata blocks,
which are completed in f2fs_cache_write_end_bio().
Both parameters are read-only at runtime, they can be set via
f2fs.max_write_bio_{folio,entry}_cnt=<n> in the kernel command line, or
via modprobe, and apply to all f2fs instances. The default value is
UINT_MAX, which means this functionality is disabled by default.
Suggested-by: Jaegeuk Kim <jaegeuk@kernel.org>
Signed-off-by: Chao Yu <chao@kernel.org>
---
Documentation/ABI/testing/sysfs-fs-f2fs | 11 -----------
fs/f2fs/data.c | 18 +++++++++++++++++-
fs/f2fs/f2fs.h | 2 --
fs/f2fs/super.c | 1 -
fs/f2fs/sysfs.c | 2 --
5 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index e7e968c6df92..65d45d8edc68 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -1014,17 +1014,6 @@ Description: Every time a write operation completes f2fs_write_end_io() is
(atc) context. The default value for this attribute is UINT_MAX
which means that this functionality is disabled by default.
-What: /sys/fs/f2fs/<disk>/max_atc_write_bio_entry_cnt
-Date: October 2026
-Contact: Chao Yu <chao@kernel.org>
-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 number of entries (folios or cached blocks) in a
- write bio that is completed in atomic (atc) context. The default
- value for this attribute is UINT_MAX which means that this
- functionality is disabled by default.
-
What: /sys/fs/f2fs/<disk>/pinned_area_max_secno
Date: August 2026
Contact: "Daeho Jeong" <daehojeong@google.com>
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 16ab2c693aa9..e2f5b95b5c6d 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -36,6 +36,16 @@ static struct kmem_cache *ffs_entry_slab;
static mempool_t *bio_post_read_ctx_pool;
static struct bio_set f2fs_bioset;
+static unsigned int max_write_bio_folio_cnt = UINT_MAX;
+module_param(max_write_bio_folio_cnt, uint, 0444);
+MODULE_PARM_DESC(max_write_bio_folio_cnt,
+ "Maximum number of folios in a write bio completed in atomic context");
+
+static unsigned int max_write_bio_entry_cnt = UINT_MAX;
+module_param(max_write_bio_entry_cnt, uint, 0444);
+MODULE_PARM_DESC(max_write_bio_entry_cnt,
+ "Maximum number of cache entries in a write bio completed in atomic context");
+
struct f2fs_folio_state {
spinlock_t state_lock;
unsigned int read_pages_pending;
@@ -390,6 +400,12 @@ static void f2fs_write_end_io_work(struct work_struct *work)
f2fs_write_end_bio(bio);
}
+static unsigned int f2fs_max_write_bio_cnt(struct bio *bio)
+{
+ return f2fs_is_cache_bio(bio) ? max_write_bio_entry_cnt :
+ max_write_bio_folio_cnt;
+}
+
static void f2fs_write_end_io(struct bio *bio)
{
struct f2fs_sb_info *sbi;
@@ -400,7 +416,7 @@ static void f2fs_write_end_io(struct bio *bio)
if (bio_in_atomic() &&
(bio->bi_iter.bi_size > sbi->max_atc_write_bio_size ||
- F2FS_BIO(bio)->entry_cnt > sbi->max_atc_write_bio_entry_cnt)) {
+ F2FS_BIO(bio)->entry_cnt > f2fs_max_write_bio_cnt(bio))) {
struct work_struct *w;
w = &container_of(bio, struct f2fs_bio, bio)->work;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index d6b3955f9a6f..05a7e96febdf 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1808,8 +1808,6 @@ struct f2fs_sb_info {
/* for bio operations */
/* Largest write bio size completed in atomic context (atc). */
u32 max_atc_write_bio_size;
- /* Largest write bio entry count completed in atomic context (atc). */
- u32 max_atc_write_bio_entry_cnt;
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 12a051ba51ac..bd79cbbeba20 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5262,7 +5262,6 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
goto free_sb_buf;
}
sbi->max_atc_write_bio_size = UINT_MAX;
- sbi->max_atc_write_bio_entry_cnt = UINT_MAX;
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 430356e500af..3f50c291b355 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -1285,7 +1285,6 @@ 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_RW_ATTR(max_atc_write_bio_entry_cnt, max_atc_write_bio_entry_cnt);
F2FS_SBI_GENERAL_RW_ATTR(max_victim_search);
F2FS_SBI_GENERAL_RW_ATTR(migration_granularity);
F2FS_SBI_GENERAL_RW_ATTR(migration_window_granularity);
@@ -1538,7 +1537,6 @@ static struct attribute *f2fs_attrs[] = {
ATTR_LIST(gc_segment_mode),
ATTR_LIST(gc_reclaimed_segments),
ATTR_LIST(max_atc_write_bio_size),
- ATTR_LIST(max_atc_write_bio_entry_cnt),
ATTR_LIST(max_fragment_chunk),
ATTR_LIST(max_fragment_hole),
ATTR_LIST(current_atomic_write),
--
2.49.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 3/3] f2fs: remove max_atc_write_bio_size
2026-10-22 12:58 [PATCH 1/3] f2fs: fix to account entry_cnt for all write bios Chao Yu
2026-10-22 12:58 ` [PATCH 2/3] f2fs: introduce max_write_bio_{folio,entry}_cnt module parameters Chao Yu
@ 2026-10-22 12:58 ` Chao Yu
1 sibling, 0 replies; 3+ messages in thread
From: Chao Yu @ 2026-10-22 12:58 UTC (permalink / raw)
To: jaegeuk; +Cc: linux-f2fs-devel, linux-kernel, Chao Yu
From: Chao Yu <chao@kernel.org>
Commit 3de6b8094115 ("f2fs: Run f2fs_write_end_io() asynchronously")
introduced max_atc_write_bio_size to offload the completion of large
write bios to sbi->wq when f2fs_write_end_io() is called in atomic
context.
However, relying on bio->bi_iter.bi_size has two issues:
1. bi_size is not reliable at completion time: on a direct blk-mq path
(e.g. SCSI or UFS), blk_update_request() consumes the bio iterator
via bio_advance() before calling bio_endio(), so bi_size is already 0
in f2fs_write_end_io(), whereas on a DM / zoned path (e.g. dm-linear)
only the clone bio is advanced and orig_bio->bi_iter.bi_size stays
intact.
2. With large folios enabled, folio size may be larger than 4KB, so
bi_size cannot indicate how many folios are in a bio.
Since max_write_bio_{folio,entry}_cnt module parameters have been
introduced to limit the number of folios or cached blocks completed in
atomic context without depending on the bio iterator, let's remove
max_atc_write_bio_size logic and its sysfs entry.
Signed-off-by: Chao Yu <chao@kernel.org>
---
Documentation/ABI/testing/sysfs-fs-f2fs | 10 ----------
fs/f2fs/data.c | 3 +--
fs/f2fs/f2fs.h | 2 --
fs/f2fs/super.c | 1 -
fs/f2fs/sysfs.c | 2 --
5 files changed, 1 insertion(+), 17 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 65d45d8edc68..7b7843d3d67b 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -1004,16 +1004,6 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck
- it requires user has CAP_SYS_NICE capability.
- the range is [100, 139], by default the value is 120.
-What: /sys/fs/f2fs/<disk>/max_atc_write_bio_size
-Date: June 2026
-Contact: Bart Van Assche <bvanassche@acm.org>
-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. The default value for this attribute is UINT_MAX
- which means that this functionality is disabled by default.
-
What: /sys/fs/f2fs/<disk>/pinned_area_max_secno
Date: August 2026
Contact: "Daeho Jeong" <daehojeong@google.com>
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index e2f5b95b5c6d..6f7a008c6e21 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -415,8 +415,7 @@ static void f2fs_write_end_io(struct bio *bio)
sbi = bio->bi_private;
if (bio_in_atomic() &&
- (bio->bi_iter.bi_size > sbi->max_atc_write_bio_size ||
- F2FS_BIO(bio)->entry_cnt > f2fs_max_write_bio_cnt(bio))) {
+ F2FS_BIO(bio)->entry_cnt > f2fs_max_write_bio_cnt(bio)) {
struct work_struct *w;
w = &container_of(bio, struct f2fs_bio, bio)->work;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 05a7e96febdf..e638538131de 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1806,8 +1806,6 @@ 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 bd79cbbeba20..b321b9cff351 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5261,7 +5261,6 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
err = -EINVAL;
goto free_sb_buf;
}
- sbi->max_atc_write_bio_size = UINT_MAX;
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 3f50c291b355..046239761550 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -1284,7 +1284,6 @@ 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);
@@ -1536,7 +1535,6 @@ 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),
--
2.49.0
^ permalink raw reply [flat|nested] 3+ messages in thread