From: Chao Yu via Linux-f2fs-devel <linux-f2fs-devel@lists.sourceforge.net>
To: jaegeuk@kernel.org
Cc: linux-kernel@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net
Subject: [f2fs-dev] [PATCH 1/2] f2fs: introduce max_write_bio_{folio, entry}_cnt module parameters
Date: Sat, 17 Oct 2026 02:03:57 +0000 [thread overview]
Message-ID: <20261017020358.305503-1-chao@kernel.org> (raw)
From: Chao Yu <chao@kernel.org>
Commit 3de6b8094115 ("f2fs: Run f2fs_write_end_io() asynchronously")
introduced max_atc_write_bio_size to offload write bio completion to a
workqueue when f2fs_write_end_io() is called in atomic context.
However, relying on bio->bi_iter.bi_size as the threshold has two main
issues:
1. bio->bi_iter.bi_size is not reliable at bio completion time, as its
value depends on the underlying storage stack:
- 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 when f2fs_write_end_io() is
called, and the threshold check is never triggered:
scsi_end_request() / blk_mq_end_request()
-> blk_update_request()
-> bio_advance() /* bio->bi_iter.bi_size becomes 0 */
-> bio_endio()
-> f2fs_write_end_io() /* sees bi_size == 0 */
- On a device-mapper / zoned device path (e.g. dm-linear / dm-crypt),
DM allocates a clone bio and sets clone->bi_iter.bi_size, while the
original bio's bi_size remains intact when completed:
dm_submit_bio()
-> dm_split_and_process_bio()
-> __split_and_process_bio()
-> alloc_tio() /* sets clone->bi_iter.bi_size */
-> __map_bio()
-> dm_submit_bio_remap()
-> submit_bio_noacct(clone)
blk_update_request() /* advances clone->bi_iter.bi_size to 0 */
-> bio_endio(clone)
-> clone_endio()
-> dm_io_dec_pending()
-> dm_io_complete()
-> __dm_io_complete()
-> bio_endio(orig_bio)
-> f2fs_zone_write_end_io()
-> f2fs_write_end_io() /* orig_bio->bi_iter.bi_size intact */
2. With large folios or multi-page bvecs, bi_size (and bi_vcnt) does not
reflect the actual completion latency:
- In f2fs_write_end_bio() and f2fs_cache_write_end_bio(), execution
time is dominated by the completion loop (bio_for_each_folio_all()
for pagecache folios, or while(entry) for cached metadata blocks).
The CPU latency in atomic context scales with the number of entries
in the bio, not its byte size.
- For large folios, a large bio (e.g. 1MB comprised of a few large
folios) only iterates a few times in the loop. Throttling by
bi_size unnecessarily offloads such low-latency bios to the
workqueue, adding context-switch overhead.
- Relying on bio->bi_vcnt is also insufficient because the block
layer (bvec_try_merge_page()) merges physically contiguous
pages/folios into the same bio_vec segment, so a bio with
bi_vcnt == 1 can still contain multiple entries.
Furthermore:
- 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.
- 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 this, track the exact number of entries (folios or cached
blocks) queued to a write bio via entry_cnt in struct f2fs_bio, and
introduce 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.
Signed-off-by: Chao Yu <chao@kernel.org>
---
fs/f2fs/data.c | 28 +++++++++++++++++++++++++++-
fs/f2fs/f2fs.h | 1 +
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 1b0395b45fb7..3caadb30496a 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;
@@ -398,7 +414,8 @@ static void f2fs_write_end_io(struct bio *bio)
sbi = bio->bi_private;
- if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) {
+ if (in_atomic() && (bio->bi_iter.bi_size > sbi->max_atc_write_bio_size ||
+ F2FS_BIO(bio)->entry_cnt > f2fs_max_write_bio_cnt(bio))) {
struct work_struct *w;
w = &container_of(bio, struct f2fs_bio, bio)->work;
@@ -555,6 +572,7 @@ static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages)
fio->type, fio->temp);
bio->bi_write_stream = f2fs_io_type_to_write_stream(bdev, fio->type,
fio->temp);
+ F2FS_BIO(bio)->entry_cnt = 0;
}
iostat_alloc_and_bind_ctx(sbi, bio, NULL);
@@ -867,6 +885,9 @@ int f2fs_submit_page_bio(struct f2fs_io_info *fio)
fio_folio->index, fio, GFP_NOIO);
bio_add_folio_nofail(bio, data_folio, folio_size(data_folio), 0);
+ if (!is_read_io(fio->op))
+ F2FS_BIO(bio)->entry_cnt++;
+
if (fio->io_wbc && !is_read_io(fio->op))
wbc_account_cgroup_owner(fio->io_wbc, fio_folio, PAGE_SIZE);
@@ -937,6 +958,7 @@ static void add_bio_entry(struct f2fs_sb_info *sbi, struct bio *bio,
bio_get(bio);
bio_add_folio_nofail(bio, folio, folio_size(folio), 0);
+ F2FS_BIO(bio)->entry_cnt++;
f2fs_down_write(&io->bio_list_lock);
list_add_tail(&be->list, &io->bio_list);
@@ -977,6 +999,7 @@ static int add_ipu_page(struct f2fs_io_info *fio, struct bio **bio,
fio_folio->mapping->host,
fio_folio->index, fio) &&
bio_add_folio(*bio, folio, folio_size(folio), 0)) {
+ F2FS_BIO(*bio)->entry_cnt++;
ret = 0;
break;
}
@@ -1234,6 +1257,8 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
goto alloc_new;
}
+ F2FS_BIO(io->bio)->entry_cnt++;
+
if (fio->io_wbc)
wbc_account_cgroup_owner(fio->io_wbc, fio->folio,
folio_size(fio->folio));
@@ -1340,6 +1365,7 @@ void f2fs_submit_cache_write(struct f2fs_io_info *fio)
}
f2fs_bio_add_cache(fio, io->bio);
+ F2FS_BIO(io->bio)->entry_cnt++;
io->last_block_in_bio = fio->new_blkaddr;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 362f06cb398d..4333d88cf076 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1778,6 +1778,7 @@ struct f2fs_gc_kthread {
struct f2fs_bio {
struct work_struct work;
struct f2fs_cached_block *entry;
+ unsigned int entry_cnt;
struct bio bio;
};
--
2.49.0
_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
next reply other threads:[~2026-10-17 2:04 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 2:03 Chao Yu via Linux-f2fs-devel [this message]
2026-10-17 2:03 ` [f2fs-dev] [PATCH 2/2] f2fs: remove max_atc_write_bio_size Chao Yu via Linux-f2fs-devel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261017020358.305503-1-chao@kernel.org \
--to=linux-f2fs-devel@lists.sourceforge.net \
--cc=chao@kernel.org \
--cc=jaegeuk@kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.