[PATCH v2 3/5] f2fs: cache: wake up f2fs_writeback when exceeding threshold

From: Chao Yu

Date: Fri Oct 09 2026 - 05:19:41 EST


From: Chao Yu <chao@xxxxxxxxxx>

This patch introduces cache_wb_dirty_threshold and cache_wb_total_threshold
sysfs nodes under /sys/fs/f2fs/<disk>/, and allows waking up the
f2fs_writeback kthread when both thresholds are exceeded.

By default, cache_wb_total_threshold is zero, so if dirty cache number exceeds
cache_wb_dirty_threshold, it will trigger writebacking.

Signed-off-by: Chao Yu <chao@xxxxxxxxxx>
---
v2:
- don't wake up f2fs_writeback if both thresholds are zero.
Documentation/ABI/testing/sysfs-fs-f2fs | 14 +++++++
fs/f2fs/cache.c | 55 ++++++++++++++++++++++++-
fs/f2fs/cache.h | 5 +++
fs/f2fs/sysfs.c | 8 +++-
4 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 5b196806b3bc..f50739f90ae9 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -1027,3 +1027,17 @@ Contact: "Chao Yu" <chao@xxxxxxxxxx>
Description: This is a writable entry to control writeback interval of
f2fs_writeback-x:y, the range is [100, 30000], by default the value
is 5000, unit is ms.
+
+What: /sys/fs/f2fs/<disk>/cache_wb_dirty_threshold
+Date: October 2026
+Contact: "Chao Yu" <chao@xxxxxxxxxx>
+Description: This is a writable entry for metadata cache, it is used to control
+ dirty cache threshold of to wake up f2fs_writeback-x:y for writeback,
+ by default the value is 8192, unit is blocks.
+
+What: /sys/fs/f2fs/<disk>/cache_wb_total_threshold
+Date: October 2026
+Contact: "Chao Yu" <chao@xxxxxxxxxx>
+Description: This is a writable entry for metadata cache, it is used to control
+ total cache threshold to wake up f2fs_writeback-x:y for writeback,
+ by default the value is 0, unit is blocks.
diff --git a/fs/f2fs/cache.c b/fs/f2fs/cache.c
index f385fe928b5b..986e679f2d16 100644
--- a/fs/f2fs/cache.c
+++ b/fs/f2fs/cache.c
@@ -81,6 +81,7 @@ bool f2fs_mark_cache_dirty(struct f2fs_cached_block *entry)
f2fs_cache_update_tag(entry, F2FS_CACHE_TAG_NONE,
F2FS_CACHE_TAG_DIRTY);
inc_cache_count(cache->sbi, type);
+ f2fs_wake_up_cache_wb(cache->sbi);
return true;
}

@@ -681,6 +682,44 @@ unsigned long f2fs_shrink_node_cache(struct f2fs_sb_info *sbi,
return f2fs_do_shrink_cache(NODE_CACHE(sbi), nr_to_scan);
}

+static inline unsigned long f2fs_total_cached_entries(struct f2fs_sb_info *sbi)
+{
+ unsigned long total = META_CACHE(sbi)->num_entries +
+ NODE_CACHE(sbi)->num_entries;
+#ifdef CONFIG_F2FS_FS_COMPRESSION
+ if (test_opt(sbi, COMPRESS_CACHE))
+ total += COMPRESS_CACHE(sbi)->num_entries;
+#endif
+ return total;
+}
+
+static inline bool f2fs_should_wake_up_cache_wb(struct f2fs_sb_info *sbi)
+{
+ struct f2fs_cache_kthread *cache_thread = &sbi->cache_thread;
+ unsigned int total_threshold;
+ unsigned int dirty_threshold;
+ s64 nr_dirty;
+
+ if (!cache_thread->cache_wb_task)
+ return false;
+
+ total_threshold = cache_thread->cache_wb_total_threshold;
+ dirty_threshold = cache_thread->cache_wb_dirty_threshold;
+
+ if (total_threshold &&
+ f2fs_total_cached_entries(sbi) < total_threshold)
+ return false;
+
+ nr_dirty = get_nr_caches(sbi, F2FS_DIRTY_META) +
+ get_nr_caches(sbi, F2FS_DIRTY_NODES);
+
+ if (dirty_threshold && nr_dirty < dirty_threshold)
+ return false;
+
+ /* don't wake up f2fs_writeback if both thresholds are zero */
+ return total_threshold || dirty_threshold;
+}
+
static int f2fs_cache_writeback_kthread(void *data)
{
struct f2fs_sb_info *sbi = data;
@@ -693,7 +732,8 @@ static int f2fs_cache_writeback_kthread(void *data)
unsigned int interval = cache_thread->cache_wb_interval;

wait_event_freezable_timeout(*wq,
- kthread_should_stop(),
+ kthread_should_stop() ||
+ f2fs_should_wake_up_cache_wb(sbi),
msecs_to_jiffies(interval));

if (kthread_should_stop())
@@ -719,6 +759,17 @@ static int f2fs_cache_writeback_kthread(void *data)
return 0;
}

+void f2fs_wake_up_cache_wb(struct f2fs_sb_info *sbi)
+{
+ struct f2fs_cache_kthread *cache_thread = &sbi->cache_thread;
+
+ if (!f2fs_should_wake_up_cache_wb(sbi))
+ return;
+
+ if (wq_has_sleeper(&cache_thread->cache_wb_wq))
+ wake_up(&cache_thread->cache_wb_wq);
+}
+
int f2fs_start_cache_wb_thread(struct f2fs_sb_info *sbi)
{
struct f2fs_cache_kthread *cache_thread = &sbi->cache_thread;
@@ -731,6 +782,8 @@ int f2fs_start_cache_wb_thread(struct f2fs_sb_info *sbi)

init_waitqueue_head(&cache_thread->cache_wb_wq);
cache_thread->cache_wb_interval = DEF_DIRTY_CACHE_TIMEOUT;
+ cache_thread->cache_wb_dirty_threshold = DEF_CACHE_WB_DIRTY_THRESH;
+ cache_thread->cache_wb_total_threshold = 0;
snprintf(name, sizeof(name), "f2fs_writeback-%u:%u",
MAJOR(dev), MINOR(dev));

diff --git a/fs/f2fs/cache.h b/fs/f2fs/cache.h
index 3df3a562c46f..7a94fb8139d3 100644
--- a/fs/f2fs/cache.h
+++ b/fs/f2fs/cache.h
@@ -234,13 +234,18 @@ unsigned long f2fs_shrink_node_cache(struct f2fs_sb_info *sbi,
#define MIN_DIRTY_CACHE_TIMEOUT 100
#define MAX_DIRTY_CACHE_TIMEOUT 30000

+#define DEF_CACHE_WB_DIRTY_THRESH (8192)
+
struct f2fs_cache_kthread {
struct task_struct *cache_wb_task;
wait_queue_head_t cache_wb_wq;
unsigned int cache_wb_interval;
+ unsigned int cache_wb_dirty_threshold;
+ unsigned int cache_wb_total_threshold;
};

int f2fs_start_cache_wb_thread(struct f2fs_sb_info *sbi);
void f2fs_stop_cache_wb_thread(struct f2fs_sb_info *sbi);
+void f2fs_wake_up_cache_wb(struct f2fs_sb_info *sbi);

#endif /* _LINUX_F2FS_CACHE_H */
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index afce74744d4d..95f8dc0c4218 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -1019,7 +1019,9 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a,
a->struct_type == GC_THREAD);
bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio") ||
!strcmp(a->attr.name, "critical_task_priority") ||
- !strcmp(a->attr.name, "cache_wb_interval");
+ !strcmp(a->attr.name, "cache_wb_interval") ||
+ !strcmp(a->attr.name, "cache_wb_dirty_threshold") ||
+ !strcmp(a->attr.name, "cache_wb_total_threshold");

if (gc_entry || thread_entry) {
if (!down_read_trylock(&sbi->sb->s_umount))
@@ -1363,6 +1365,8 @@ ATGC_INFO_RW_ATTR(atgc_age_threshold, age_threshold);

/* WB_THREAD ATTR */
WB_THREAD_RW_ATTR(cache_wb_interval, cache_wb_interval);
+WB_THREAD_RW_ATTR(cache_wb_dirty_threshold, cache_wb_dirty_threshold);
+WB_THREAD_RW_ATTR(cache_wb_total_threshold, cache_wb_total_threshold);

F2FS_GENERAL_RO_ATTR(dirty_segments);
F2FS_GENERAL_RO_ATTR(free_segments);
@@ -1552,6 +1556,8 @@ static struct attribute *f2fs_attrs[] = {
ATTR_LIST(adjust_lock_priority),
ATTR_LIST(critical_task_priority),
ATTR_LIST(cache_wb_interval),
+ ATTR_LIST(cache_wb_dirty_threshold),
+ ATTR_LIST(cache_wb_total_threshold),
NULL,
};
ATTRIBUTE_GROUPS(f2fs);
--
2.49.0