From: Chao Yu <chao@kernel.org>
To: jaegeuk@kernel.org
Cc: linux-f2fs-devel@lists.sourceforge.net,
linux-kernel@vger.kernel.org, Chao Yu <chao@kernel.org>
Subject: [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
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 [this message]
2026-10-17 2:03 ` [PATCH 2/2] f2fs: remove max_atc_write_bio_size Chao Yu
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=chao@kernel.org \
--cc=jaegeuk@kernel.org \
--cc=linux-f2fs-devel@lists.sourceforge.net \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®