* [PATCH v2 00/15] Parallelizing filesystem writeback
[not found] <CGME20250725155458epcas5p3bf5ce4f3ec0b1868588098c52c87d110@epcas5p3.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
[not found] ` <CGME20250725155459epcas5p2488a6d5af262a3a5718ac405dd9bed1c@epcas5p2.samsung.com>
` (14 more replies)
0 siblings, 15 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar
Currently, pagecache writeback is performed by a single thread. Inodes
are added to a dirty list, and delayed writeback is triggered. The single
writeback thread then iterates through the dirty inode list, and executes
the writeback.
This series parallelizes the writeback by allowing multiple writeback
contexts per backing device (bdi). These writebacks contexts are executed
as separate, independent threads, improving overall parallelism.
Design Overview
================
Following Jan Kara's suggestion [1], we have introduced a new bdi
writeback context within the backing_dev_info structure. Specifically,
we have created a new structure, bdi_writeback_context, which contains
its own set of members for each writeback context.
struct bdi_writeback_ctx {
struct bdi_writeback wb;
struct list_head wb_list; /* list of all wbs */
struct radix_tree_root cgwb_tree;
struct rw_semaphore wb_switch_rwsem;
wait_queue_head_t wb_waitq;
};
There can be multiple writeback contexts in a bdi, which helps in
achieving writeback parallelism.
struct backing_dev_info {
...
int nr_wb_ctx;
struct bdi_writeback_ctx **wb_ctx;
...
};
FS geometry and filesystem fragmentation
========================================
The community was concerned that parallelizing writeback would impact
delayed allocation and increase filesystem fragmentation.
Our analysis of XFS delayed allocation behavior showed that merging of
extents occurs within a specific inode. Earlier experiments with multiple
writeback contexts [2] resulted in increased fragmentation due to the
same inode being processed by different threads.
To address this, we now affine an inode to a specific writeback context
ensuring that delayed allocation works effectively.
Number of writeback contexts
============================
As suggested by Christoph we have provided a sysfs interface to change
the number of writebacks. Also we plan to keep the number as 1 for
spinning disk.
IOPS and throughput
===================
We see significant improvement in IOPS across several filesystem on both
PMEM and NVMe devices.
Performance gains:
- On PMEM:
Base XFS : 544 MiB/s
Parallel Writeback XFS : 1015 MiB/s (+86%)
Base EXT4 : 536 MiB/s
Parallel Writeback EXT4 : 1047 MiB/s (+95%)
- On NVMe:
Base XFS : 651 MiB/s
Parallel Writeback XFS : 808 MiB/s (+24%)
Base EXT4 : 494 MiB/s
Parallel Writeback EXT4 : 797 MiB/s (+61%)
We also see that there is no increase in filesystem fragmentation
# of extents:
- On XFS (on PMEM):
Base XFS : 1964
Parallel Writeback XFS : 1384
- On EXT4 (on PMEM):
Base EXT4 : 21
Parallel Writeback EXT4 : 11
We also plan to see the impact on other filesystems.
[1] Jan Kara suggestion :
https://lore.kernel.org/all/gamxtewl5yzg4xwu7lpp7obhp44xh344swvvf7tmbiknvbd3ww@jowphz4h4zmb/
[2] Writeback using unaffined N (# of CPUs) threads :
https://lore.kernel.org/all/20250414102824.9901-1-kundan.kumar@samsung.com/
Changes since v1:
- Added sysfs entry to change the number of writebacks for a bdi
- Added a filesystem interface to fetch 64 bit inode numbers
- Made common helpers to contain writeback specific changes, which were
affecting f2fs, fuse, gfs2 and nfs
- Changed name from wb_ctx_arr to wb_ctx
Kundan Kumar (15):
writeback: add infra for parallel writeback
writeback: add support to initialize and free multiple writeback ctxs
writeback: link bdi_writeback to its corresponding bdi_writeback_ctx
writeback: affine inode to a writeback ctx within a bdi
writeback: modify bdi_writeback search logic to search across all wb
ctxs
writeback: invoke all writeback contexts for flusher and dirtytime
writeback
writeback: modify sync related functions to iterate over all writeback
contexts
writeback: add support to collect stats for all writeback ctxs
f2fs: add support in f2fs to handle multiple writeback contexts
fuse: add support for multiple writeback contexts in fuse
gfs2: add support in gfs2 to handle multiple writeback contexts
nfs: add support in nfs to handle multiple writeback contexts
writeback: set the num of writeback contexts to number of online cpus
writeback: segregated allocation and free of writeback contexts
writeback: added support to change the number of writebacks using a
sysfs attribute
fs/f2fs/node.c | 4 +-
fs/f2fs/segment.h | 2 +-
fs/fs-writeback.c | 148 ++++++++-----
fs/fuse/file.c | 8 +-
fs/gfs2/super.c | 2 +-
fs/nfs/internal.h | 2 +-
fs/nfs/write.c | 4 +-
fs/super.c | 23 ++
fs/xfs/xfs_super.c | 12 ++
include/linux/backing-dev-defs.h | 32 +--
include/linux/backing-dev.h | 77 +++++--
include/linux/fs.h | 3 +-
mm/backing-dev.c | 349 ++++++++++++++++++++++++-------
mm/page-writeback.c | 13 +-
14 files changed, 510 insertions(+), 169 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 01/15] writeback: add infra for parallel writeback
[not found] ` <CGME20250725155459epcas5p2488a6d5af262a3a5718ac405dd9bed1c@epcas5p2.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
This is a prep patch which introduces a new bdi_writeback_ctx structure
that enables us to have multiple writeback contexts for parallel
writeback. Each bdi now can have multiple writeback contexts, with each
writeback context having has its own cgwb tree.
Modify all the functions/places that operate on bdi's wb, wb_list,
cgwb_tree, wb_switch_rwsem, wb_waitq as these fields have now been moved
to bdi_writeback_ctx.
This patch mechanically replaces bdi->wb to bdi->wb_ctx[0]->wb and there
is no functional change.
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
---
fs/f2fs/node.c | 4 +-
fs/f2fs/segment.h | 2 +-
fs/fs-writeback.c | 78 +++++++++++++--------
fs/fuse/file.c | 6 +-
fs/gfs2/super.c | 2 +-
fs/nfs/internal.h | 3 +-
fs/nfs/write.c | 3 +-
include/linux/backing-dev-defs.h | 32 +++++----
include/linux/backing-dev.h | 41 +++++++----
include/linux/fs.h | 1 -
mm/backing-dev.c | 113 +++++++++++++++++++------------
mm/page-writeback.c | 5 +-
12 files changed, 179 insertions(+), 111 deletions(-)
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index bfe104db284e..6c89c8f025c2 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -73,7 +73,7 @@ bool f2fs_available_free_memory(struct f2fs_sb_info *sbi, int type)
if (excess_cached_nats(sbi))
res = false;
} else if (type == DIRTY_DENTS) {
- if (sbi->sb->s_bdi->wb.dirty_exceeded)
+ if (sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
return false;
mem_size = get_pages(sbi, F2FS_DIRTY_DENTS);
res = mem_size < ((avail_ram * nm_i->ram_thresh / 100) >> 1);
@@ -114,7 +114,7 @@ bool f2fs_available_free_memory(struct f2fs_sb_info *sbi, int type)
res = false;
#endif
} else {
- if (!sbi->sb->s_bdi->wb.dirty_exceeded)
+ if (!sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
return true;
}
return res;
diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h
index db619fd2f51a..ca8f8e53c80d 100644
--- a/fs/f2fs/segment.h
+++ b/fs/f2fs/segment.h
@@ -1000,7 +1000,7 @@ static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno)
*/
static inline int nr_pages_to_skip(struct f2fs_sb_info *sbi, int type)
{
- if (sbi->sb->s_bdi->wb.dirty_exceeded)
+ if (sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
return 0;
if (type == DATA)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index cc57367fb641..5165b7cc86e2 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -265,23 +265,26 @@ void __inode_attach_wb(struct inode *inode, struct folio *folio)
{
struct backing_dev_info *bdi = inode_to_bdi(inode);
struct bdi_writeback *wb = NULL;
+ struct bdi_writeback_ctx *bdi_writeback_ctx = bdi->wb_ctx[0];
if (inode_cgwb_enabled(inode)) {
struct cgroup_subsys_state *memcg_css;
if (folio) {
memcg_css = mem_cgroup_css_from_folio(folio);
- wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
+ wb = wb_get_create(bdi, bdi_writeback_ctx, memcg_css,
+ GFP_ATOMIC);
} else {
/* must pin memcg_css, see wb_get_create() */
memcg_css = task_get_css(current, memory_cgrp_id);
- wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
+ wb = wb_get_create(bdi, bdi_writeback_ctx, memcg_css,
+ GFP_ATOMIC);
css_put(memcg_css);
}
}
if (!wb)
- wb = &bdi->wb;
+ wb = &bdi_writeback_ctx->wb;
/*
* There may be multiple instances of this function racing to
@@ -307,7 +310,7 @@ static void inode_cgwb_move_to_attached(struct inode *inode,
WARN_ON_ONCE(inode->i_state & I_FREEING);
inode->i_state &= ~I_SYNC_QUEUED;
- if (wb != &wb->bdi->wb)
+ if (wb != &wb->bdi_wb_ctx->wb)
list_move(&inode->i_io_list, &wb->b_attached);
else
list_del_init(&inode->i_io_list);
@@ -382,14 +385,16 @@ struct inode_switch_wbs_context {
struct inode *inodes[];
};
-static void bdi_down_write_wb_switch_rwsem(struct backing_dev_info *bdi)
+static void
+bdi_down_write_wb_ctx_switch_rwsem(struct bdi_writeback_ctx *bdi_wb_ctx)
{
- down_write(&bdi->wb_switch_rwsem);
+ down_write(&bdi_wb_ctx->wb_switch_rwsem);
}
-static void bdi_up_write_wb_switch_rwsem(struct backing_dev_info *bdi)
+static void
+bdi_up_write_wb_ctx_switch_rwsem(struct bdi_writeback_ctx *bdi_wb_ctx)
{
- up_write(&bdi->wb_switch_rwsem);
+ up_write(&bdi_wb_ctx->wb_switch_rwsem);
}
static bool inode_do_switch_wbs(struct inode *inode,
@@ -490,7 +495,8 @@ static void inode_switch_wbs_work_fn(struct work_struct *work)
{
struct inode_switch_wbs_context *isw =
container_of(to_rcu_work(work), struct inode_switch_wbs_context, work);
- struct backing_dev_info *bdi = inode_to_bdi(isw->inodes[0]);
+ struct bdi_writeback_ctx *bdi_wb_ctx =
+ fetch_bdi_writeback_ctx(isw->inodes[0]);
struct bdi_writeback *old_wb = isw->inodes[0]->i_wb;
struct bdi_writeback *new_wb = isw->new_wb;
unsigned long nr_switched = 0;
@@ -500,7 +506,7 @@ static void inode_switch_wbs_work_fn(struct work_struct *work)
* If @inode switches cgwb membership while sync_inodes_sb() is
* being issued, sync_inodes_sb() might miss it. Synchronize.
*/
- down_read(&bdi->wb_switch_rwsem);
+ down_read(&bdi_wb_ctx->wb_switch_rwsem);
/*
* By the time control reaches here, RCU grace period has passed
@@ -529,7 +535,7 @@ static void inode_switch_wbs_work_fn(struct work_struct *work)
spin_unlock(&new_wb->list_lock);
spin_unlock(&old_wb->list_lock);
- up_read(&bdi->wb_switch_rwsem);
+ up_read(&bdi_wb_ctx->wb_switch_rwsem);
if (nr_switched) {
wb_wakeup(new_wb);
@@ -583,6 +589,7 @@ static bool inode_prepare_wbs_switch(struct inode *inode,
static void inode_switch_wbs(struct inode *inode, int new_wb_id)
{
struct backing_dev_info *bdi = inode_to_bdi(inode);
+ struct bdi_writeback_ctx *bdi_wb_ctx = fetch_bdi_writeback_ctx(inode);
struct cgroup_subsys_state *memcg_css;
struct inode_switch_wbs_context *isw;
@@ -609,7 +616,7 @@ static void inode_switch_wbs(struct inode *inode, int new_wb_id)
if (!memcg_css)
goto out_free;
- isw->new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
+ isw->new_wb = wb_get_create(bdi, bdi_wb_ctx, memcg_css, GFP_ATOMIC);
css_put(memcg_css);
if (!isw->new_wb)
goto out_free;
@@ -678,12 +685,14 @@ bool cleanup_offline_cgwb(struct bdi_writeback *wb)
for (memcg_css = wb->memcg_css->parent; memcg_css;
memcg_css = memcg_css->parent) {
- isw->new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL);
+ isw->new_wb = wb_get_create(wb->bdi, wb->bdi_wb_ctx,
+ memcg_css, GFP_KERNEL);
if (isw->new_wb)
break;
}
+ /* wb_get() is noop for bdi's wb */
if (unlikely(!isw->new_wb))
- isw->new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
+ isw->new_wb = &wb->bdi_wb_ctx->wb;
nr = 0;
spin_lock(&wb->list_lock);
@@ -994,18 +1003,19 @@ static long wb_split_bdi_pages(struct bdi_writeback *wb, long nr_pages)
* total active write bandwidth of @bdi.
*/
static void bdi_split_work_to_wbs(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
struct wb_writeback_work *base_work,
bool skip_if_busy)
{
struct bdi_writeback *last_wb = NULL;
- struct bdi_writeback *wb = list_entry(&bdi->wb_list,
+ struct bdi_writeback *wb = list_entry(&bdi_wb_ctx->wb_list,
struct bdi_writeback, bdi_node);
might_sleep();
restart:
rcu_read_lock();
- list_for_each_entry_continue_rcu(wb, &bdi->wb_list, bdi_node) {
- DEFINE_WB_COMPLETION(fallback_work_done, bdi);
+ list_for_each_entry_continue_rcu(wb, &bdi_wb_ctx->wb_list, bdi_node) {
+ DEFINE_WB_COMPLETION(fallback_work_done, bdi_wb_ctx);
struct wb_writeback_work fallback_work;
struct wb_writeback_work *work;
long nr_pages;
@@ -1103,7 +1113,7 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id,
* And find the associated wb. If the wb isn't there already
* there's nothing to flush, don't create one.
*/
- wb = wb_get_lookup(bdi, memcg_css);
+ wb = wb_get_lookup(bdi->wb_ctx[0], memcg_css);
if (!wb) {
ret = -ENOENT;
goto out_css_put;
@@ -1189,8 +1199,13 @@ fs_initcall(cgroup_writeback_init);
#else /* CONFIG_CGROUP_WRITEBACK */
-static void bdi_down_write_wb_switch_rwsem(struct backing_dev_info *bdi) { }
-static void bdi_up_write_wb_switch_rwsem(struct backing_dev_info *bdi) { }
+static void
+bdi_down_write_wb_ctx_switch_rwsem(struct bdi_writeback_ctx *bdi_wb_ctx)
+{ }
+
+static void
+bdi_up_write_wb_ctx_switch_rwsem(struct bdi_writeback_ctx *bdi_wb_ctx)
+{ }
static void inode_cgwb_move_to_attached(struct inode *inode,
struct bdi_writeback *wb)
@@ -1231,14 +1246,15 @@ static long wb_split_bdi_pages(struct bdi_writeback *wb, long nr_pages)
}
static void bdi_split_work_to_wbs(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
struct wb_writeback_work *base_work,
bool skip_if_busy)
{
might_sleep();
- if (!skip_if_busy || !writeback_in_progress(&bdi->wb)) {
+ if (!skip_if_busy || !writeback_in_progress(&bdi_wb_ctx->wb)) {
base_work->auto_free = 0;
- wb_queue_work(&bdi->wb, base_work);
+ wb_queue_work(&bdi_wb_ctx->wb, base_work);
}
}
@@ -2371,7 +2387,7 @@ static void __wakeup_flusher_threads_bdi(struct backing_dev_info *bdi,
if (!bdi_has_dirty_io(bdi))
return;
- list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node)
+ list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node)
wb_start_writeback(wb, reason);
}
@@ -2427,7 +2443,8 @@ static void wakeup_dirtytime_writeback(struct work_struct *w)
list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) {
struct bdi_writeback *wb;
- list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node)
+ list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list,
+ bdi_node)
if (!list_empty(&wb->b_dirty_time))
wb_wakeup(wb);
}
@@ -2729,7 +2746,7 @@ static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
enum wb_reason reason, bool skip_if_busy)
{
struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi);
+ DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_NONE,
@@ -2743,7 +2760,8 @@ static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
- bdi_split_work_to_wbs(sb->s_bdi, &work, skip_if_busy);
+ bdi_split_work_to_wbs(sb->s_bdi, bdi->wb_ctx[0], &work,
+ skip_if_busy);
wb_wait_for_completion(&done);
}
@@ -2807,7 +2825,7 @@ EXPORT_SYMBOL(try_to_writeback_inodes_sb);
void sync_inodes_sb(struct super_block *sb)
{
struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi);
+ DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_ALL,
@@ -2828,10 +2846,10 @@ void sync_inodes_sb(struct super_block *sb)
WARN_ON(!rwsem_is_locked(&sb->s_umount));
/* protect against inode wb switch, see inode_switch_wbs_work_fn() */
- bdi_down_write_wb_switch_rwsem(bdi);
- bdi_split_work_to_wbs(bdi, &work, false);
+ bdi_down_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
+ bdi_split_work_to_wbs(bdi, bdi->wb_ctx[0], &work, false);
wb_wait_for_completion(&done);
- bdi_up_write_wb_switch_rwsem(bdi);
+ bdi_up_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
wait_sb_inodes(sb);
}
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index f102afc03359..69874c906319 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1795,8 +1795,8 @@ static void fuse_writepage_finish(struct fuse_writepage_args *wpa)
* contention and noticeably improves performance.
*/
folio_end_writeback(ap->folios[i]);
- dec_wb_stat(&bdi->wb, WB_WRITEBACK);
- wb_writeout_inc(&bdi->wb);
+ dec_wb_stat(&bdi->wb_ctx[0]->wb, WB_WRITEBACK);
+ wb_writeout_inc(&bdi->wb_ctx[0]->wb);
}
wake_up(&fi->page_waitq);
@@ -1990,7 +1990,7 @@ static void fuse_writepage_args_page_fill(struct fuse_writepage_args *wpa, struc
ap->descs[folio_index].offset = 0;
ap->descs[folio_index].length = folio_size(folio);
- inc_wb_stat(&inode_to_bdi(inode)->wb, WB_WRITEBACK);
+ inc_wb_stat(&inode_to_bdi(inode)->wb_ctx[0]->wb, WB_WRITEBACK);
}
static struct fuse_writepage_args *fuse_writepage_args_setup(struct folio *folio,
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index 7c518c4ff638..d3f7a11f4ca7 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -447,7 +447,7 @@ static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc)
gfs2_log_flush(GFS2_SB(inode), ip->i_gl,
GFS2_LOG_HEAD_FLUSH_NORMAL |
GFS2_LFC_WRITE_INODE);
- if (bdi->wb.dirty_exceeded)
+ if (bdi->wb_ctx[0]->wb.dirty_exceeded)
gfs2_ail1_flush(sdp, wbc);
else
filemap_fdatawrite(metamapping);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 69c2c10ee658..953c32576778 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -843,7 +843,8 @@ static inline void nfs_folio_mark_unstable(struct folio *folio,
* writeback is happening on the server now.
*/
node_stat_mod_folio(folio, NR_WRITEBACK, nr);
- wb_stat_mod(&inode_to_bdi(inode)->wb, WB_WRITEBACK, nr);
+ wb_stat_mod(&inode_to_bdi(inode)->wb_ctx[0]->wb,
+ WB_WRITEBACK, nr);
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
}
}
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index 374fc6b34c79..dbf5de7d6a75 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -916,9 +916,10 @@ static void nfs_folio_clear_commit(struct folio *folio)
{
if (folio) {
long nr = folio_nr_pages(folio);
+ struct inode *inode = folio->mapping->host;
node_stat_mod_folio(folio, NR_WRITEBACK, -nr);
- wb_stat_mod(&inode_to_bdi(folio->mapping->host)->wb,
+ wb_stat_mod(&inode_to_bdi(inode)->wb_ctx[0]->wb,
WB_WRITEBACK, -nr);
}
}
diff --git a/include/linux/backing-dev-defs.h b/include/linux/backing-dev-defs.h
index 2ad261082bba..692ec7be73e2 100644
--- a/include/linux/backing-dev-defs.h
+++ b/include/linux/backing-dev-defs.h
@@ -75,10 +75,11 @@ struct wb_completion {
* can wait for the completion of all using wb_wait_for_completion(). Work
* items which are waited upon aren't freed automatically on completion.
*/
-#define WB_COMPLETION_INIT(bdi) __WB_COMPLETION_INIT(&(bdi)->wb_waitq)
+#define WB_COMPLETION_INIT(bdi_wb_ctx) \
+ __WB_COMPLETION_INIT(&(bdi_wb_ctx)->wb_waitq)
-#define DEFINE_WB_COMPLETION(cmpl, bdi) \
- struct wb_completion cmpl = WB_COMPLETION_INIT(bdi)
+#define DEFINE_WB_COMPLETION(cmpl, bdi_wb_ctx) \
+ struct wb_completion cmpl = WB_COMPLETION_INIT(bdi_wb_ctx)
/*
* Each wb (bdi_writeback) can perform writeback operations, is measured
@@ -104,6 +105,7 @@ struct wb_completion {
*/
struct bdi_writeback {
struct backing_dev_info *bdi; /* our parent bdi */
+ struct bdi_writeback_ctx *bdi_wb_ctx;
unsigned long state; /* Always use atomic bitops on this */
unsigned long last_old_flush; /* last old data flush */
@@ -160,6 +162,16 @@ struct bdi_writeback {
#endif
};
+struct bdi_writeback_ctx {
+ struct bdi_writeback wb; /* the root writeback info for this bdi */
+ struct list_head wb_list; /* list of all wbs */
+#ifdef CONFIG_CGROUP_WRITEBACK
+ struct radix_tree_root cgwb_tree; /* radix tree of active cgroup wbs */
+ struct rw_semaphore wb_switch_rwsem; /* no cgwb switch while syncing */
+#endif
+ wait_queue_head_t wb_waitq;
+};
+
struct backing_dev_info {
u64 id;
struct rb_node rb_node; /* keyed by ->id */
@@ -183,15 +195,11 @@ struct backing_dev_info {
*/
unsigned long last_bdp_sleep;
- struct bdi_writeback wb; /* the root writeback info for this bdi */
- struct list_head wb_list; /* list of all wbs */
+ int nr_wb_ctx;
+ struct bdi_writeback_ctx **wb_ctx;
#ifdef CONFIG_CGROUP_WRITEBACK
- struct radix_tree_root cgwb_tree; /* radix tree of active cgroup wbs */
struct mutex cgwb_release_mutex; /* protect shutdown of wb structs */
- struct rw_semaphore wb_switch_rwsem; /* no cgwb switch while syncing */
#endif
- wait_queue_head_t wb_waitq;
-
struct device *dev;
char dev_name[64];
struct device *owner;
@@ -216,7 +224,7 @@ struct wb_lock_cookie {
*/
static inline bool wb_tryget(struct bdi_writeback *wb)
{
- if (wb != &wb->bdi->wb)
+ if (wb != &wb->bdi_wb_ctx->wb)
return percpu_ref_tryget(&wb->refcnt);
return true;
}
@@ -227,7 +235,7 @@ static inline bool wb_tryget(struct bdi_writeback *wb)
*/
static inline void wb_get(struct bdi_writeback *wb)
{
- if (wb != &wb->bdi->wb)
+ if (wb != &wb->bdi_wb_ctx->wb)
percpu_ref_get(&wb->refcnt);
}
@@ -246,7 +254,7 @@ static inline void wb_put_many(struct bdi_writeback *wb, unsigned long nr)
return;
}
- if (wb != &wb->bdi->wb)
+ if (wb != &wb->bdi_wb_ctx->wb)
percpu_ref_put_many(&wb->refcnt, nr);
}
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index e721148c95d0..92674543ac8a 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -148,11 +148,20 @@ static inline bool mapping_can_writeback(struct address_space *mapping)
return inode_to_bdi(mapping->host)->capabilities & BDI_CAP_WRITEBACK;
}
+static inline struct bdi_writeback_ctx *
+fetch_bdi_writeback_ctx(struct inode *inode)
+{
+ struct backing_dev_info *bdi = inode_to_bdi(inode);
+
+ return bdi->wb_ctx[0];
+}
+
#ifdef CONFIG_CGROUP_WRITEBACK
-struct bdi_writeback *wb_get_lookup(struct backing_dev_info *bdi,
+struct bdi_writeback *wb_get_lookup(struct bdi_writeback_ctx *bdi_wb_ctx,
struct cgroup_subsys_state *memcg_css);
struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
struct cgroup_subsys_state *memcg_css,
gfp_t gfp);
void wb_memcg_offline(struct mem_cgroup *memcg);
@@ -187,16 +196,18 @@ static inline bool inode_cgwb_enabled(struct inode *inode)
* Must be called under rcu_read_lock() which protects the returend wb.
* NULL if not found.
*/
-static inline struct bdi_writeback *wb_find_current(struct backing_dev_info *bdi)
+static inline struct bdi_writeback *
+wb_find_current(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
struct cgroup_subsys_state *memcg_css;
struct bdi_writeback *wb;
memcg_css = task_css(current, memory_cgrp_id);
if (!memcg_css->parent)
- return &bdi->wb;
+ return &bdi_wb_ctx->wb;
- wb = radix_tree_lookup(&bdi->cgwb_tree, memcg_css->id);
+ wb = radix_tree_lookup(&bdi_wb_ctx->cgwb_tree, memcg_css->id);
/*
* %current's blkcg equals the effective blkcg of its memcg. No
@@ -217,12 +228,13 @@ static inline struct bdi_writeback *wb_find_current(struct backing_dev_info *bdi
* wb_find_current().
*/
static inline struct bdi_writeback *
-wb_get_create_current(struct backing_dev_info *bdi, gfp_t gfp)
+wb_get_create_current(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx, gfp_t gfp)
{
struct bdi_writeback *wb;
rcu_read_lock();
- wb = wb_find_current(bdi);
+ wb = wb_find_current(bdi, bdi_wb_ctx);
if (wb && unlikely(!wb_tryget(wb)))
wb = NULL;
rcu_read_unlock();
@@ -231,7 +243,7 @@ wb_get_create_current(struct backing_dev_info *bdi, gfp_t gfp)
struct cgroup_subsys_state *memcg_css;
memcg_css = task_get_css(current, memory_cgrp_id);
- wb = wb_get_create(bdi, memcg_css, gfp);
+ wb = wb_get_create(bdi, bdi_wb_ctx, memcg_css, gfp);
css_put(memcg_css);
}
return wb;
@@ -265,7 +277,7 @@ static inline struct bdi_writeback *inode_to_wb_wbc(
* If wbc does not have inode attached, it means cgroup writeback was
* disabled when wbc started. Just use the default wb in that case.
*/
- return wbc->wb ? wbc->wb : &inode_to_bdi(inode)->wb;
+ return wbc->wb ? wbc->wb : &fetch_bdi_writeback_ctx(inode)->wb;
}
/**
@@ -325,20 +337,23 @@ static inline bool inode_cgwb_enabled(struct inode *inode)
return false;
}
-static inline struct bdi_writeback *wb_find_current(struct backing_dev_info *bdi)
+static inline struct bdi_writeback *wb_find_current(
+ struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- return &bdi->wb;
+ return &bdi_wb_ctx->wb;
}
static inline struct bdi_writeback *
-wb_get_create_current(struct backing_dev_info *bdi, gfp_t gfp)
+wb_get_create_current(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx, gfp_t gfp)
{
- return &bdi->wb;
+ return &bdi_wb_ctx->wb;
}
static inline struct bdi_writeback *inode_to_wb(struct inode *inode)
{
- return &inode_to_bdi(inode)->wb;
+ return &fetch_bdi_writeback_ctx(inode)->wb;
}
static inline struct bdi_writeback *inode_to_wb_wbc(
diff --git a/include/linux/fs.h b/include/linux/fs.h
index b085f161ed22..8e2a041ce430 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2339,7 +2339,6 @@ struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*free_inode)(struct inode *);
-
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 783904d8c5ef..8b7125349f6c 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -84,13 +84,14 @@ static void collect_wb_stats(struct wb_stats *stats,
}
#ifdef CONFIG_CGROUP_WRITEBACK
+
static void bdi_collect_stats(struct backing_dev_info *bdi,
struct wb_stats *stats)
{
struct bdi_writeback *wb;
rcu_read_lock();
- list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node) {
+ list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node) {
if (!wb_tryget(wb))
continue;
@@ -103,7 +104,7 @@ static void bdi_collect_stats(struct backing_dev_info *bdi,
static void bdi_collect_stats(struct backing_dev_info *bdi,
struct wb_stats *stats)
{
- collect_wb_stats(stats, &bdi->wb);
+ collect_wb_stats(stats, &bdi->wb_ctx[0]->wb);
}
#endif
@@ -149,7 +150,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
stats.nr_io,
stats.nr_more_io,
stats.nr_dirty_time,
- !list_empty(&bdi->bdi_list), bdi->wb.state);
+ !list_empty(&bdi->bdi_list), bdi->wb_ctx[0]->wb.state);
return 0;
}
@@ -193,14 +194,14 @@ static void wb_stats_show(struct seq_file *m, struct bdi_writeback *wb,
static int cgwb_debug_stats_show(struct seq_file *m, void *v)
{
struct backing_dev_info *bdi = m->private;
+ struct bdi_writeback *wb;
unsigned long background_thresh;
unsigned long dirty_thresh;
- struct bdi_writeback *wb;
global_dirty_limits(&background_thresh, &dirty_thresh);
rcu_read_lock();
- list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node) {
+ list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node) {
struct wb_stats stats = { .dirty_thresh = dirty_thresh };
if (!wb_tryget(wb))
@@ -520,6 +521,7 @@ static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
memset(wb, 0, sizeof(*wb));
wb->bdi = bdi;
+ wb->bdi_wb_ctx = bdi->wb_ctx[0];
wb->last_old_flush = jiffies;
INIT_LIST_HEAD(&wb->b_dirty);
INIT_LIST_HEAD(&wb->b_io);
@@ -643,11 +645,12 @@ static void cgwb_release(struct percpu_ref *refcnt)
queue_work(cgwb_release_wq, &wb->release_work);
}
-static void cgwb_kill(struct bdi_writeback *wb)
+static void cgwb_kill(struct bdi_writeback *wb,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
lockdep_assert_held(&cgwb_lock);
- WARN_ON(!radix_tree_delete(&wb->bdi->cgwb_tree, wb->memcg_css->id));
+ WARN_ON(!radix_tree_delete(&bdi_wb_ctx->cgwb_tree, wb->memcg_css->id));
list_del(&wb->memcg_node);
list_del(&wb->blkcg_node);
list_add(&wb->offline_node, &offline_cgwbs);
@@ -662,6 +665,7 @@ static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb)
}
static int cgwb_create(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
struct cgroup_subsys_state *memcg_css, gfp_t gfp)
{
struct mem_cgroup *memcg;
@@ -678,9 +682,9 @@ static int cgwb_create(struct backing_dev_info *bdi,
/* look up again under lock and discard on blkcg mismatch */
spin_lock_irqsave(&cgwb_lock, flags);
- wb = radix_tree_lookup(&bdi->cgwb_tree, memcg_css->id);
+ wb = radix_tree_lookup(&bdi_wb_ctx->cgwb_tree, memcg_css->id);
if (wb && wb->blkcg_css != blkcg_css) {
- cgwb_kill(wb);
+ cgwb_kill(wb, bdi_wb_ctx);
wb = NULL;
}
spin_unlock_irqrestore(&cgwb_lock, flags);
@@ -721,12 +725,13 @@ static int cgwb_create(struct backing_dev_info *bdi,
*/
ret = -ENODEV;
spin_lock_irqsave(&cgwb_lock, flags);
- if (test_bit(WB_registered, &bdi->wb.state) &&
+ if (test_bit(WB_registered, &bdi_wb_ctx->wb.state) &&
blkcg_cgwb_list->next && memcg_cgwb_list->next) {
/* we might have raced another instance of this function */
- ret = radix_tree_insert(&bdi->cgwb_tree, memcg_css->id, wb);
+ ret = radix_tree_insert(&bdi_wb_ctx->cgwb_tree,
+ memcg_css->id, wb);
if (!ret) {
- list_add_tail_rcu(&wb->bdi_node, &bdi->wb_list);
+ list_add_tail_rcu(&wb->bdi_node, &bdi_wb_ctx->wb_list);
list_add(&wb->memcg_node, memcg_cgwb_list);
list_add(&wb->blkcg_node, blkcg_cgwb_list);
blkcg_pin_online(blkcg_css);
@@ -779,16 +784,16 @@ static int cgwb_create(struct backing_dev_info *bdi,
* each lookup. On mismatch, the existing wb is discarded and a new one is
* created.
*/
-struct bdi_writeback *wb_get_lookup(struct backing_dev_info *bdi,
+struct bdi_writeback *wb_get_lookup(struct bdi_writeback_ctx *bdi_wb_ctx,
struct cgroup_subsys_state *memcg_css)
{
struct bdi_writeback *wb;
if (!memcg_css->parent)
- return &bdi->wb;
+ return &bdi_wb_ctx->wb;
rcu_read_lock();
- wb = radix_tree_lookup(&bdi->cgwb_tree, memcg_css->id);
+ wb = radix_tree_lookup(&bdi_wb_ctx->cgwb_tree, memcg_css->id);
if (wb) {
struct cgroup_subsys_state *blkcg_css;
@@ -813,6 +818,7 @@ struct bdi_writeback *wb_get_lookup(struct backing_dev_info *bdi,
* create one. See wb_get_lookup() for more details.
*/
struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
struct cgroup_subsys_state *memcg_css,
gfp_t gfp)
{
@@ -821,8 +827,8 @@ struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,
might_alloc(gfp);
do {
- wb = wb_get_lookup(bdi, memcg_css);
- } while (!wb && !cgwb_create(bdi, memcg_css, gfp));
+ wb = wb_get_lookup(bdi_wb_ctx, memcg_css);
+ } while (!wb && !cgwb_create(bdi, bdi_wb_ctx, memcg_css, gfp));
return wb;
}
@@ -830,36 +836,40 @@ struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,
static int cgwb_bdi_init(struct backing_dev_info *bdi)
{
int ret;
+ struct bdi_writeback_ctx *bdi_wb_ctx = bdi->wb_ctx[0];
- INIT_RADIX_TREE(&bdi->cgwb_tree, GFP_ATOMIC);
+ INIT_RADIX_TREE(&bdi_wb_ctx->cgwb_tree, GFP_ATOMIC);
mutex_init(&bdi->cgwb_release_mutex);
- init_rwsem(&bdi->wb_switch_rwsem);
+ init_rwsem(&bdi_wb_ctx->wb_switch_rwsem);
- ret = wb_init(&bdi->wb, bdi, GFP_KERNEL);
+ ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
if (!ret) {
- bdi->wb.memcg_css = &root_mem_cgroup->css;
- bdi->wb.blkcg_css = blkcg_root_css;
+ bdi_wb_ctx->wb.memcg_css = &root_mem_cgroup->css;
+ bdi_wb_ctx->wb.blkcg_css = blkcg_root_css;
}
return ret;
}
-static void cgwb_bdi_unregister(struct backing_dev_info *bdi)
+/* callers should create a loop and pass bdi_wb_ctx */
+static void cgwb_bdi_unregister(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
struct radix_tree_iter iter;
void **slot;
struct bdi_writeback *wb;
- WARN_ON(test_bit(WB_registered, &bdi->wb.state));
+ WARN_ON(test_bit(WB_registered, &bdi_wb_ctx->wb.state));
spin_lock_irq(&cgwb_lock);
- radix_tree_for_each_slot(slot, &bdi->cgwb_tree, &iter, 0)
- cgwb_kill(*slot);
+ radix_tree_for_each_slot(slot, &bdi_wb_ctx->cgwb_tree, &iter, 0)
+ cgwb_kill(*slot, bdi_wb_ctx);
spin_unlock_irq(&cgwb_lock);
mutex_lock(&bdi->cgwb_release_mutex);
spin_lock_irq(&cgwb_lock);
- while (!list_empty(&bdi->wb_list)) {
- wb = list_first_entry(&bdi->wb_list, struct bdi_writeback,
+ while (!list_empty(&bdi_wb_ctx->wb_list)) {
+ wb = list_first_entry(&bdi_wb_ctx->wb_list,
+ struct bdi_writeback,
bdi_node);
spin_unlock_irq(&cgwb_lock);
wb_shutdown(wb);
@@ -930,7 +940,7 @@ void wb_memcg_offline(struct mem_cgroup *memcg)
spin_lock_irq(&cgwb_lock);
list_for_each_entry_safe(wb, next, memcg_cgwb_list, memcg_node)
- cgwb_kill(wb);
+ cgwb_kill(wb, wb->bdi_wb_ctx);
memcg_cgwb_list->next = NULL; /* prevent new wb's */
spin_unlock_irq(&cgwb_lock);
@@ -950,15 +960,16 @@ void wb_blkcg_offline(struct cgroup_subsys_state *css)
spin_lock_irq(&cgwb_lock);
list_for_each_entry_safe(wb, next, list, blkcg_node)
- cgwb_kill(wb);
+ cgwb_kill(wb, wb->bdi_wb_ctx);
list->next = NULL; /* prevent new wb's */
spin_unlock_irq(&cgwb_lock);
}
-static void cgwb_bdi_register(struct backing_dev_info *bdi)
+static void cgwb_bdi_register(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
spin_lock_irq(&cgwb_lock);
- list_add_tail_rcu(&bdi->wb.bdi_node, &bdi->wb_list);
+ list_add_tail_rcu(&bdi_wb_ctx->wb.bdi_node, &bdi_wb_ctx->wb_list);
spin_unlock_irq(&cgwb_lock);
}
@@ -981,14 +992,18 @@ subsys_initcall(cgwb_init);
static int cgwb_bdi_init(struct backing_dev_info *bdi)
{
- return wb_init(&bdi->wb, bdi, GFP_KERNEL);
+ return wb_init(&bdi->wb_ctx[0]->wb, bdi, GFP_KERNEL);
}
-static void cgwb_bdi_unregister(struct backing_dev_info *bdi) { }
+static void cgwb_bdi_unregister(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
+{ }
-static void cgwb_bdi_register(struct backing_dev_info *bdi)
+/* callers should create a loop and pass bdi_wb_ctx */
+static void cgwb_bdi_register(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- list_add_tail_rcu(&bdi->wb.bdi_node, &bdi->wb_list);
+ list_add_tail_rcu(&bdi_wb_ctx->wb.bdi_node, &bdi_wb_ctx->wb_list);
}
static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb)
@@ -1006,9 +1021,15 @@ int bdi_init(struct backing_dev_info *bdi)
bdi->min_ratio = 0;
bdi->max_ratio = 100 * BDI_RATIO_SCALE;
bdi->max_prop_frac = FPROP_FRAC_BASE;
+ bdi->nr_wb_ctx = 1;
+ bdi->wb_ctx = kcalloc(bdi->nr_wb_ctx,
+ sizeof(struct bdi_writeback_ctx *),
+ GFP_KERNEL);
INIT_LIST_HEAD(&bdi->bdi_list);
- INIT_LIST_HEAD(&bdi->wb_list);
- init_waitqueue_head(&bdi->wb_waitq);
+ bdi->wb_ctx[0] = (struct bdi_writeback_ctx *)
+ kzalloc(sizeof(struct bdi_writeback_ctx), GFP_KERNEL);
+ INIT_LIST_HEAD(&bdi->wb_ctx[0]->wb_list);
+ init_waitqueue_head(&bdi->wb_ctx[0]->wb_waitq);
bdi->last_bdp_sleep = jiffies;
return cgwb_bdi_init(bdi);
@@ -1023,6 +1044,8 @@ struct backing_dev_info *bdi_alloc(int node_id)
return NULL;
if (bdi_init(bdi)) {
+ kfree(bdi->wb_ctx[0]);
+ kfree(bdi->wb_ctx);
kfree(bdi);
return NULL;
}
@@ -1095,11 +1118,11 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (IS_ERR(dev))
return PTR_ERR(dev);
- cgwb_bdi_register(bdi);
+ cgwb_bdi_register(bdi, bdi->wb_ctx[0]);
+ set_bit(WB_registered, &bdi->wb_ctx[0]->wb.state);
bdi->dev = dev;
bdi_debug_register(bdi, dev_name(dev));
- set_bit(WB_registered, &bdi->wb.state);
spin_lock_bh(&bdi_lock);
@@ -1155,8 +1178,8 @@ void bdi_unregister(struct backing_dev_info *bdi)
/* make sure nobody finds us on the bdi_list anymore */
bdi_remove_from_list(bdi);
- wb_shutdown(&bdi->wb);
- cgwb_bdi_unregister(bdi);
+ wb_shutdown(&bdi->wb_ctx[0]->wb);
+ cgwb_bdi_unregister(bdi, bdi->wb_ctx[0]);
/*
* If this BDI's min ratio has been set, use bdi_set_min_ratio() to
@@ -1183,9 +1206,11 @@ static void release_bdi(struct kref *ref)
struct backing_dev_info *bdi =
container_of(ref, struct backing_dev_info, refcnt);
- WARN_ON_ONCE(test_bit(WB_registered, &bdi->wb.state));
WARN_ON_ONCE(bdi->dev);
- wb_exit(&bdi->wb);
+ WARN_ON_ONCE(test_bit(WB_registered, &bdi->wb_ctx[0]->wb.state));
+ wb_exit(&bdi->wb_ctx[0]->wb);
+ kfree(bdi->wb_ctx[0]);
+ kfree(bdi->wb_ctx);
kfree(bdi);
}
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 72b0ff0d4bae..8b4271e75f9e 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -2050,6 +2050,7 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,
{
struct inode *inode = mapping->host;
struct backing_dev_info *bdi = inode_to_bdi(inode);
+ struct bdi_writeback_ctx *bdi_wb_ctx = fetch_bdi_writeback_ctx(inode);
struct bdi_writeback *wb = NULL;
int ratelimit;
int ret = 0;
@@ -2059,9 +2060,9 @@ int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,
return ret;
if (inode_cgwb_enabled(inode))
- wb = wb_get_create_current(bdi, GFP_KERNEL);
+ wb = wb_get_create_current(bdi, bdi_wb_ctx, GFP_KERNEL);
if (!wb)
- wb = &bdi->wb;
+ wb = &bdi_wb_ctx->wb;
ratelimit = current->nr_dirtied_pause;
if (wb->dirty_exceeded)
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 02/15] writeback: add support to initialize and free multiple writeback ctxs
[not found] ` <CGME20250725155500epcas5p225e5103ea402a715cdde2eefd46af987@epcas5p2.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Introduce a new macro for_each_bdi_wb_ctx to iterate over multiple
writeback ctxs. Added logic for allocation, init, free, registration
and unregistration of multiple writeback contexts within a bdi.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
include/linux/backing-dev.h | 4 ++
mm/backing-dev.c | 81 +++++++++++++++++++++++++++----------
2 files changed, 63 insertions(+), 22 deletions(-)
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 92674543ac8a..951ab5497500 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -148,6 +148,10 @@ static inline bool mapping_can_writeback(struct address_space *mapping)
return inode_to_bdi(mapping->host)->capabilities & BDI_CAP_WRITEBACK;
}
+#define for_each_bdi_wb_ctx(bdi, wbctx) \
+ for (int __i = 0; __i < (bdi)->nr_wb_ctx \
+ && ((wbctx) = (bdi)->wb_ctx[__i]) != NULL; __i++)
+
static inline struct bdi_writeback_ctx *
fetch_bdi_writeback_ctx(struct inode *inode)
{
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 8b7125349f6c..47196d326e16 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -835,17 +835,20 @@ struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,
static int cgwb_bdi_init(struct backing_dev_info *bdi)
{
- int ret;
- struct bdi_writeback_ctx *bdi_wb_ctx = bdi->wb_ctx[0];
+ int ret = 0;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
- INIT_RADIX_TREE(&bdi_wb_ctx->cgwb_tree, GFP_ATOMIC);
- mutex_init(&bdi->cgwb_release_mutex);
- init_rwsem(&bdi_wb_ctx->wb_switch_rwsem);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ INIT_RADIX_TREE(&bdi_wb_ctx->cgwb_tree, GFP_ATOMIC);
+ mutex_init(&bdi->cgwb_release_mutex);
+ init_rwsem(&bdi_wb_ctx->wb_switch_rwsem);
- ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
- if (!ret) {
- bdi_wb_ctx->wb.memcg_css = &root_mem_cgroup->css;
- bdi_wb_ctx->wb.blkcg_css = blkcg_root_css;
+ ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
+ if (!ret) {
+ bdi_wb_ctx->wb.memcg_css = &root_mem_cgroup->css;
+ bdi_wb_ctx->wb.blkcg_css = blkcg_root_css;
+ } else
+ return ret;
}
return ret;
}
@@ -992,7 +995,16 @@ subsys_initcall(cgwb_init);
static int cgwb_bdi_init(struct backing_dev_info *bdi)
{
- return wb_init(&bdi->wb_ctx[0]->wb, bdi, GFP_KERNEL);
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ int ret;
+
+ ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
+ if (ret)
+ return ret;
+ }
+ return 0;
}
static void cgwb_bdi_unregister(struct backing_dev_info *bdi,
@@ -1026,10 +1038,19 @@ int bdi_init(struct backing_dev_info *bdi)
sizeof(struct bdi_writeback_ctx *),
GFP_KERNEL);
INIT_LIST_HEAD(&bdi->bdi_list);
- bdi->wb_ctx[0] = (struct bdi_writeback_ctx *)
- kzalloc(sizeof(struct bdi_writeback_ctx), GFP_KERNEL);
- INIT_LIST_HEAD(&bdi->wb_ctx[0]->wb_list);
- init_waitqueue_head(&bdi->wb_ctx[0]->wb_waitq);
+ for (int i = 0; i < bdi->nr_wb_ctx; i++) {
+ bdi->wb_ctx[i] = (struct bdi_writeback_ctx *)
+ kzalloc(sizeof(struct bdi_writeback_ctx), GFP_KERNEL);
+ if (!bdi->wb_ctx[i]) {
+ pr_err("Failed to allocate %d", i);
+ while (--i >= 0)
+ kfree(bdi->wb_ctx[i]);
+ kfree(bdi->wb_ctx);
+ return -ENOMEM;
+ }
+ INIT_LIST_HEAD(&bdi->wb_ctx[i]->wb_list);
+ init_waitqueue_head(&bdi->wb_ctx[i]->wb_waitq);
+ }
bdi->last_bdp_sleep = jiffies;
return cgwb_bdi_init(bdi);
@@ -1038,13 +1059,16 @@ int bdi_init(struct backing_dev_info *bdi)
struct backing_dev_info *bdi_alloc(int node_id)
{
struct backing_dev_info *bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
bdi = kzalloc_node(sizeof(*bdi), GFP_KERNEL, node_id);
if (!bdi)
return NULL;
if (bdi_init(bdi)) {
- kfree(bdi->wb_ctx[0]);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ kfree(bdi_wb_ctx);
+ }
kfree(bdi->wb_ctx);
kfree(bdi);
return NULL;
@@ -1109,6 +1133,7 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
{
struct device *dev;
struct rb_node *parent, **p;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
if (bdi->dev) /* The driver needs to use separate queues per device */
return 0;
@@ -1118,8 +1143,11 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (IS_ERR(dev))
return PTR_ERR(dev);
- cgwb_bdi_register(bdi, bdi->wb_ctx[0]);
- set_bit(WB_registered, &bdi->wb_ctx[0]->wb.state);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ cgwb_bdi_register(bdi, bdi_wb_ctx);
+ set_bit(WB_registered, &bdi_wb_ctx->wb.state);
+ }
+
bdi->dev = dev;
bdi_debug_register(bdi, dev_name(dev));
@@ -1174,12 +1202,17 @@ static void bdi_remove_from_list(struct backing_dev_info *bdi)
void bdi_unregister(struct backing_dev_info *bdi)
{
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
timer_delete_sync(&bdi->laptop_mode_wb_timer);
/* make sure nobody finds us on the bdi_list anymore */
bdi_remove_from_list(bdi);
- wb_shutdown(&bdi->wb_ctx[0]->wb);
- cgwb_bdi_unregister(bdi, bdi->wb_ctx[0]);
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ wb_shutdown(&bdi_wb_ctx->wb);
+ cgwb_bdi_unregister(bdi, bdi_wb_ctx);
+ }
/*
* If this BDI's min ratio has been set, use bdi_set_min_ratio() to
@@ -1205,11 +1238,15 @@ static void release_bdi(struct kref *ref)
{
struct backing_dev_info *bdi =
container_of(ref, struct backing_dev_info, refcnt);
+ struct bdi_writeback_ctx *bdi_wb_ctx;
WARN_ON_ONCE(bdi->dev);
- WARN_ON_ONCE(test_bit(WB_registered, &bdi->wb_ctx[0]->wb.state));
- wb_exit(&bdi->wb_ctx[0]->wb);
- kfree(bdi->wb_ctx[0]);
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ WARN_ON_ONCE(test_bit(WB_registered, &bdi_wb_ctx->wb.state));
+ wb_exit(&bdi_wb_ctx->wb);
+ kfree(bdi_wb_ctx);
+ }
kfree(bdi->wb_ctx);
kfree(bdi);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 03/15] writeback: link bdi_writeback to its corresponding bdi_writeback_ctx
[not found] ` <CGME20250725155501epcas5p1d281c17292ea2fa12e6dcc9c90eee929@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Introduce a bdi_writeback_ctx field in bdi_writeback. This helps in
fetching the writeback context from the bdi_writeback.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
mm/backing-dev.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 47196d326e16..754f2f6c6d7c 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -513,15 +513,16 @@ static void wb_update_bandwidth_workfn(struct work_struct *work)
*/
#define INIT_BW (100 << (20 - PAGE_SHIFT))
-static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
- gfp_t gfp)
+static int wb_init(struct bdi_writeback *wb,
+ struct bdi_writeback_ctx *bdi_wb_ctx,
+ struct backing_dev_info *bdi, gfp_t gfp)
{
int err;
memset(wb, 0, sizeof(*wb));
wb->bdi = bdi;
- wb->bdi_wb_ctx = bdi->wb_ctx[0];
+ wb->bdi_wb_ctx = bdi_wb_ctx;
wb->last_old_flush = jiffies;
INIT_LIST_HEAD(&wb->b_dirty);
INIT_LIST_HEAD(&wb->b_io);
@@ -698,7 +699,7 @@ static int cgwb_create(struct backing_dev_info *bdi,
goto out_put;
}
- ret = wb_init(wb, bdi, gfp);
+ ret = wb_init(wb, bdi_wb_ctx, bdi, gfp);
if (ret)
goto err_free;
@@ -843,7 +844,7 @@ static int cgwb_bdi_init(struct backing_dev_info *bdi)
mutex_init(&bdi->cgwb_release_mutex);
init_rwsem(&bdi_wb_ctx->wb_switch_rwsem);
- ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
+ ret = wb_init(&bdi_wb_ctx->wb, bdi_wb_ctx, bdi, GFP_KERNEL);
if (!ret) {
bdi_wb_ctx->wb.memcg_css = &root_mem_cgroup->css;
bdi_wb_ctx->wb.blkcg_css = blkcg_root_css;
@@ -1000,7 +1001,7 @@ static int cgwb_bdi_init(struct backing_dev_info *bdi)
for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
int ret;
- ret = wb_init(&bdi_wb_ctx->wb, bdi, GFP_KERNEL);
+ ret = wb_init(&bdi_wb_ctx->wb, bdi_wb_ctx, bdi, GFP_KERNEL);
if (ret)
return ret;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 04/15] writeback: affine inode to a writeback ctx within a bdi
[not found] ` <CGME20250725155502epcas5p149ee8859fd9a97523bf876b50deaa84f@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Affine inode to a writeback context. This helps in minimizing the
filesytem fragmentation due to inode being processed by different
threads.
To support parallel writeback, wire up a new superblock operation
get_inode_wb_ctx(). Filesystems with 64 bit inode numbers can implement
this callback to return suitable writeback context for the inode.
In fetch_bdi_writeback_ctx(), if the callback is implemented, it is used
to obtain the context. Otherwise the default fallback computes the
writeback context by taking mod with the 32-bit inode number.
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
---
fs/fs-writeback.c | 3 ++-
fs/xfs/xfs_super.c | 12 ++++++++++++
include/linux/backing-dev.h | 4 +++-
include/linux/fs.h | 1 +
4 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 5165b7cc86e2..e065cd606b80 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -265,7 +265,8 @@ void __inode_attach_wb(struct inode *inode, struct folio *folio)
{
struct backing_dev_info *bdi = inode_to_bdi(inode);
struct bdi_writeback *wb = NULL;
- struct bdi_writeback_ctx *bdi_writeback_ctx = bdi->wb_ctx[0];
+ struct bdi_writeback_ctx *bdi_writeback_ctx =
+ fetch_bdi_writeback_ctx(inode);
if (inode_cgwb_enabled(inode)) {
struct cgroup_subsys_state *memcg_css;
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 0bc4b5489078..645a426d1e41 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -53,6 +53,7 @@
#include <linux/magic.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/backing-dev.h>
static const struct super_operations xfs_super_operations;
@@ -1294,6 +1295,16 @@ xfs_fs_show_stats(
return 0;
}
+static struct bdi_writeback_ctx *
+xfs_get_inode_wb_ctx(
+ struct inode *inode)
+{
+ struct xfs_inode *ip = XFS_I(inode);
+ struct backing_dev_info *bdi = inode_to_bdi(inode);
+
+ return bdi->wb_ctx[ip->i_ino % bdi->nr_wb_ctx];
+}
+
static const struct super_operations xfs_super_operations = {
.alloc_inode = xfs_fs_alloc_inode,
.destroy_inode = xfs_fs_destroy_inode,
@@ -1310,6 +1321,7 @@ static const struct super_operations xfs_super_operations = {
.free_cached_objects = xfs_fs_free_cached_objects,
.shutdown = xfs_fs_shutdown,
.show_stats = xfs_fs_show_stats,
+ .get_inode_wb_ctx = xfs_get_inode_wb_ctx,
};
static int
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 951ab5497500..6f744aadd83e 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -157,7 +157,9 @@ fetch_bdi_writeback_ctx(struct inode *inode)
{
struct backing_dev_info *bdi = inode_to_bdi(inode);
- return bdi->wb_ctx[0];
+ if (inode->i_sb->s_op->get_inode_wb_ctx)
+ return inode->i_sb->s_op->get_inode_wb_ctx(inode);
+ return bdi->wb_ctx[inode->i_ino % bdi->nr_wb_ctx];
}
#ifdef CONFIG_CGROUP_WRITEBACK
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 8e2a041ce430..edcbc5042427 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2367,6 +2367,7 @@ struct super_operations {
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
void (*shutdown)(struct super_block *sb);
+ struct bdi_writeback_ctx *(*get_inode_wb_ctx)(struct inode *inode);
};
/*
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 05/15] writeback: modify bdi_writeback search logic to search across all wb ctxs
[not found] ` <CGME20250725155502epcas5p2efb36021968a5693170d87f7440cb356@epcas5p2.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Since we have multiple cgwb per bdi, embedded in writeback_ctx now, we
iterate over all of them to find the associated writeback.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/fs-writeback.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index e065cd606b80..60c7c912c7f3 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -1090,7 +1090,8 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id,
{
struct backing_dev_info *bdi;
struct cgroup_subsys_state *memcg_css;
- struct bdi_writeback *wb;
+ struct bdi_writeback *wb = NULL;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
struct wb_writeback_work *work;
unsigned long dirty;
int ret;
@@ -1114,7 +1115,11 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id,
* And find the associated wb. If the wb isn't there already
* there's nothing to flush, don't create one.
*/
- wb = wb_get_lookup(bdi->wb_ctx[0], memcg_css);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ wb = wb_get_lookup(bdi_wb_ctx, memcg_css);
+ if (wb)
+ break;
+ }
if (!wb) {
ret = -ENOENT;
goto out_css_put;
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 06/15] writeback: invoke all writeback contexts for flusher and dirtytime writeback
[not found] ` <CGME20250725155503epcas5p142aea80acbddccf89d040249b183378d@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Modify flusher and dirtytime logic to iterate through all the writeback
contexts.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/fs-writeback.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 60c7c912c7f3..1df8dd7af048 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -2389,12 +2389,14 @@ static void __wakeup_flusher_threads_bdi(struct backing_dev_info *bdi,
enum wb_reason reason)
{
struct bdi_writeback *wb;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
if (!bdi_has_dirty_io(bdi))
return;
- list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node)
- wb_start_writeback(wb, reason);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ list_for_each_entry_rcu(wb, &bdi_wb_ctx->wb_list, bdi_node)
+ wb_start_writeback(wb, reason);
}
void wakeup_flusher_threads_bdi(struct backing_dev_info *bdi,
@@ -2444,15 +2446,17 @@ static DECLARE_DELAYED_WORK(dirtytime_work, wakeup_dirtytime_writeback);
static void wakeup_dirtytime_writeback(struct work_struct *w)
{
struct backing_dev_info *bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
rcu_read_lock();
list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) {
struct bdi_writeback *wb;
- list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list,
- bdi_node)
- if (!list_empty(&wb->b_dirty_time))
- wb_wakeup(wb);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ list_for_each_entry_rcu(wb, &bdi_wb_ctx->wb_list,
+ bdi_node)
+ if (!list_empty(&wb->b_dirty_time))
+ wb_wakeup(wb);
}
rcu_read_unlock();
schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ);
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 07/15] writeback: modify sync related functions to iterate over all writeback contexts
[not found] ` <CGME20250725155504epcas5p1c92bb829279a8563dc99e2b475e1a221@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Modify sync related functions to iterate over all writeback contexts.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/fs-writeback.c | 66 +++++++++++++++++++++++++++++++----------------
1 file changed, 44 insertions(+), 22 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 1df8dd7af048..328bf7303df6 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -2752,11 +2752,13 @@ static void wait_sb_inodes(struct super_block *sb)
mutex_unlock(&sb->s_sync_lock);
}
-static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
- enum wb_reason reason, bool skip_if_busy)
+static void __writeback_inodes_sb_nr_ctx(struct super_block *sb,
+ unsigned long nr,
+ enum wb_reason reason,
+ bool skip_if_busy,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
+ DEFINE_WB_COMPLETION(done, bdi_wb_ctx);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_NONE,
@@ -2766,13 +2768,23 @@ static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
.reason = reason,
};
+ bdi_split_work_to_wbs(sb->s_bdi, bdi_wb_ctx, &work, skip_if_busy);
+ wb_wait_for_completion(&done);
+}
+
+static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
+ enum wb_reason reason, bool skip_if_busy)
+{
+ struct backing_dev_info *bdi = sb->s_bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
if (!bdi_has_dirty_io(bdi) || bdi == &noop_backing_dev_info)
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
- bdi_split_work_to_wbs(sb->s_bdi, bdi->wb_ctx[0], &work,
- skip_if_busy);
- wb_wait_for_completion(&done);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ __writeback_inodes_sb_nr_ctx(sb, nr, reason, skip_if_busy,
+ bdi_wb_ctx);
}
/**
@@ -2825,17 +2837,11 @@ void try_to_writeback_inodes_sb(struct super_block *sb, enum wb_reason reason)
}
EXPORT_SYMBOL(try_to_writeback_inodes_sb);
-/**
- * sync_inodes_sb - sync sb inode pages
- * @sb: the superblock
- *
- * This function writes and waits on any dirty inode belonging to this
- * super_block.
- */
-void sync_inodes_sb(struct super_block *sb)
+static void sync_inodes_bdi_wb_ctx(struct super_block *sb,
+ struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
+ DEFINE_WB_COMPLETION(done, bdi_wb_ctx);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_ALL,
@@ -2846,6 +2852,25 @@ void sync_inodes_sb(struct super_block *sb)
.for_sync = 1,
};
+ /* protect against inode wb switch, see inode_switch_wbs_work_fn() */
+ bdi_down_write_wb_ctx_switch_rwsem(bdi_wb_ctx);
+ bdi_split_work_to_wbs(bdi, bdi_wb_ctx, &work, false);
+ wb_wait_for_completion(&done);
+ bdi_up_write_wb_ctx_switch_rwsem(bdi_wb_ctx);
+}
+
+/**
+ * sync_inodes_sb - sync sb inode pages
+ * @sb: the superblock
+ *
+ * This function writes and waits on any dirty inode belonging to this
+ * super_block.
+ */
+void sync_inodes_sb(struct super_block *sb)
+{
+ struct backing_dev_info *bdi = sb->s_bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
/*
* Can't skip on !bdi_has_dirty() because we should wait for !dirty
* inodes under writeback and I_DIRTY_TIME inodes ignored by
@@ -2855,11 +2880,8 @@ void sync_inodes_sb(struct super_block *sb)
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
- /* protect against inode wb switch, see inode_switch_wbs_work_fn() */
- bdi_down_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
- bdi_split_work_to_wbs(bdi, bdi->wb_ctx[0], &work, false);
- wb_wait_for_completion(&done);
- bdi_up_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ sync_inodes_bdi_wb_ctx(sb, bdi, bdi_wb_ctx);
wait_sb_inodes(sb);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 08/15] writeback: add support to collect stats for all writeback ctxs
[not found] ` <CGME20250725155505epcas5p3981ad791c6c044c7abc6d232c05d54f9@epcas5p3.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Modified stats collection to collect stats for all the writeback
contexts within a bdi.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
mm/backing-dev.c | 72 ++++++++++++++++++++++++++++--------------------
1 file changed, 42 insertions(+), 30 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 754f2f6c6d7c..0a772d984ecf 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -50,6 +50,7 @@ struct wb_stats {
unsigned long nr_written;
unsigned long dirty_thresh;
unsigned long wb_thresh;
+ unsigned long state;
};
static struct dentry *bdi_debug_root;
@@ -81,6 +82,7 @@ static void collect_wb_stats(struct wb_stats *stats,
stats->nr_dirtied += wb_stat(wb, WB_DIRTIED);
stats->nr_written += wb_stat(wb, WB_WRITTEN);
stats->wb_thresh += wb_calc_thresh(wb, stats->dirty_thresh);
+ stats->state |= wb->state;
}
#ifdef CONFIG_CGROUP_WRITEBACK
@@ -89,22 +91,27 @@ static void bdi_collect_stats(struct backing_dev_info *bdi,
struct wb_stats *stats)
{
struct bdi_writeback *wb;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
rcu_read_lock();
- list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node) {
- if (!wb_tryget(wb))
- continue;
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ list_for_each_entry_rcu(wb, &bdi_wb_ctx->wb_list, bdi_node) {
+ if (!wb_tryget(wb))
+ continue;
- collect_wb_stats(stats, wb);
- wb_put(wb);
- }
+ collect_wb_stats(stats, wb);
+ wb_put(wb);
+ }
rcu_read_unlock();
}
#else
static void bdi_collect_stats(struct backing_dev_info *bdi,
struct wb_stats *stats)
{
- collect_wb_stats(stats, &bdi->wb_ctx[0]->wb);
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ collect_wb_stats(stats, &bdi_wb_ctx->wb);
}
#endif
@@ -150,7 +157,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
stats.nr_io,
stats.nr_more_io,
stats.nr_dirty_time,
- !list_empty(&bdi->bdi_list), bdi->wb_ctx[0]->wb.state);
+ !list_empty(&bdi->bdi_list), stats.state);
return 0;
}
@@ -195,35 +202,40 @@ static int cgwb_debug_stats_show(struct seq_file *m, void *v)
{
struct backing_dev_info *bdi = m->private;
struct bdi_writeback *wb;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
unsigned long background_thresh;
unsigned long dirty_thresh;
+ struct wb_stats stats;
global_dirty_limits(&background_thresh, &dirty_thresh);
+ stats.dirty_thresh = dirty_thresh;
rcu_read_lock();
- list_for_each_entry_rcu(wb, &bdi->wb_ctx[0]->wb_list, bdi_node) {
- struct wb_stats stats = { .dirty_thresh = dirty_thresh };
-
- if (!wb_tryget(wb))
- continue;
-
- collect_wb_stats(&stats, wb);
-
- /*
- * Calculate thresh of wb in writeback cgroup which is min of
- * thresh in global domain and thresh in cgroup domain. Drop
- * rcu lock because cgwb_calc_thresh may sleep in
- * cgroup_rstat_flush. We can do so here because we have a ref.
- */
- if (mem_cgroup_wb_domain(wb)) {
- rcu_read_unlock();
- stats.wb_thresh = min(stats.wb_thresh, cgwb_calc_thresh(wb));
- rcu_read_lock();
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ list_for_each_entry_rcu(wb, &bdi_wb_ctx->wb_list, bdi_node) {
+ if (!wb_tryget(wb))
+ continue;
+
+ collect_wb_stats(&stats, wb);
+
+ /*
+ * Calculate thresh of wb in writeback cgroup which is
+ * min of thresh in global domain and thresh in cgroup
+ * domain. Drop rcu lock because cgwb_calc_thresh may
+ * sleep in cgroup_rstat_flush. We can do so here
+ * because we have a ref.
+ */
+ if (mem_cgroup_wb_domain(wb)) {
+ rcu_read_unlock();
+ stats.wb_thresh = min(stats.wb_thresh,
+ cgwb_calc_thresh(wb));
+ rcu_read_lock();
+ }
+
+ wb_stats_show(m, wb, &stats);
+
+ wb_put(wb);
}
-
- wb_stats_show(m, wb, &stats);
-
- wb_put(wb);
}
rcu_read_unlock();
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 09/15] f2fs: add support in f2fs to handle multiple writeback contexts
[not found] ` <CGME20250725155506epcas5p3db90b1c2f47ed6f67c18825f0a39c51b@epcas5p3.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Add support to handle multiple writeback contexts and check for
dirty_exceeded across all the writeback contexts.
Made a new helper for same.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/f2fs/node.c | 4 ++--
fs/f2fs/segment.h | 2 +-
include/linux/backing-dev.h | 20 ++++++++++++++++----
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index 6c89c8f025c2..8e0220ffea29 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -73,7 +73,7 @@ bool f2fs_available_free_memory(struct f2fs_sb_info *sbi, int type)
if (excess_cached_nats(sbi))
res = false;
} else if (type == DIRTY_DENTS) {
- if (sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
+ if (bdi_wb_dirty_limit_exceeded(sbi->sb->s_bdi))
return false;
mem_size = get_pages(sbi, F2FS_DIRTY_DENTS);
res = mem_size < ((avail_ram * nm_i->ram_thresh / 100) >> 1);
@@ -114,7 +114,7 @@ bool f2fs_available_free_memory(struct f2fs_sb_info *sbi, int type)
res = false;
#endif
} else {
- if (!sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
+ if (!bdi_wb_dirty_limit_exceeded(sbi->sb->s_bdi))
return true;
}
return res;
diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h
index ca8f8e53c80d..7d13b75c4fdc 100644
--- a/fs/f2fs/segment.h
+++ b/fs/f2fs/segment.h
@@ -1000,7 +1000,7 @@ static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno)
*/
static inline int nr_pages_to_skip(struct f2fs_sb_info *sbi, int type)
{
- if (sbi->sb->s_bdi->wb_ctx[0]->wb.dirty_exceeded)
+ if (bdi_wb_dirty_limit_exceeded(sbi->sb->s_bdi))
return 0;
if (type == DATA)
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 6f744aadd83e..831cd9f51162 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -51,6 +51,22 @@ static inline bool wb_has_dirty_io(struct bdi_writeback *wb)
return test_bit(WB_has_dirty_io, &wb->state);
}
+#define for_each_bdi_wb_ctx(bdi, wbctx) \
+ for (int __i = 0; __i < (bdi)->nr_wb_ctx \
+ && ((wbctx) = (bdi)->wb_ctx[__i]) != NULL; __i++)
+
+
+static inline bool bdi_wb_dirty_limit_exceeded(struct backing_dev_info *bdi)
+{
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ if (bdi_wb_ctx->wb.dirty_exceeded)
+ return true;
+ }
+ return false;
+}
+
static inline bool bdi_has_dirty_io(struct backing_dev_info *bdi)
{
/*
@@ -148,10 +164,6 @@ static inline bool mapping_can_writeback(struct address_space *mapping)
return inode_to_bdi(mapping->host)->capabilities & BDI_CAP_WRITEBACK;
}
-#define for_each_bdi_wb_ctx(bdi, wbctx) \
- for (int __i = 0; __i < (bdi)->nr_wb_ctx \
- && ((wbctx) = (bdi)->wb_ctx[__i]) != NULL; __i++)
-
static inline struct bdi_writeback_ctx *
fetch_bdi_writeback_ctx(struct inode *inode)
{
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 10/15] fuse: add support for multiple writeback contexts in fuse
[not found] ` <CGME20250725155507epcas5p113787f985ae9dcc5ff8c42caf2035047@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Made a helper to fetch writeback context to which an inode is affined.
Use it to perform writeback related operations.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/fuse/file.c | 8 ++++----
include/linux/backing-dev.h | 17 +++++++++++++++++
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 69874c906319..1194fdd24b01 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1785,7 +1785,6 @@ static void fuse_writepage_finish(struct fuse_writepage_args *wpa)
struct fuse_args_pages *ap = &wpa->ia.ap;
struct inode *inode = wpa->inode;
struct fuse_inode *fi = get_fuse_inode(inode);
- struct backing_dev_info *bdi = inode_to_bdi(inode);
int i;
for (i = 0; i < ap->num_folios; i++) {
@@ -1795,8 +1794,9 @@ static void fuse_writepage_finish(struct fuse_writepage_args *wpa)
* contention and noticeably improves performance.
*/
folio_end_writeback(ap->folios[i]);
- dec_wb_stat(&bdi->wb_ctx[0]->wb, WB_WRITEBACK);
- wb_writeout_inc(&bdi->wb_ctx[0]->wb);
+
+ bdi_wb_stat_mod(inode, -1);
+ bdi_wb_writeout_inc(inode);
}
wake_up(&fi->page_waitq);
@@ -1990,7 +1990,7 @@ static void fuse_writepage_args_page_fill(struct fuse_writepage_args *wpa, struc
ap->descs[folio_index].offset = 0;
ap->descs[folio_index].length = folio_size(folio);
- inc_wb_stat(&inode_to_bdi(inode)->wb_ctx[0]->wb, WB_WRITEBACK);
+ bdi_wb_stat_mod(inode, 1);
}
static struct fuse_writepage_args *fuse_writepage_args_setup(struct folio *folio,
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 831cd9f51162..5ed8294e0c0e 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -46,6 +46,9 @@ extern struct list_head bdi_list;
extern struct workqueue_struct *bdi_wq;
+static inline struct bdi_writeback_ctx *
+fetch_bdi_writeback_ctx(struct inode *inode);
+
static inline bool wb_has_dirty_io(struct bdi_writeback *wb)
{
return test_bit(WB_has_dirty_io, &wb->state);
@@ -104,6 +107,20 @@ static inline s64 wb_stat_sum(struct bdi_writeback *wb, enum wb_stat_item item)
extern void wb_writeout_inc(struct bdi_writeback *wb);
+static inline void bdi_wb_stat_mod(struct inode *inode, s64 amount)
+{
+ struct bdi_writeback_ctx *bdi_wb_ctx = fetch_bdi_writeback_ctx(inode);
+
+ wb_stat_mod(&bdi_wb_ctx->wb, WB_WRITEBACK, amount);
+}
+
+static inline void bdi_wb_writeout_inc(struct inode *inode)
+{
+ struct bdi_writeback_ctx *bdi_wb_ctx = fetch_bdi_writeback_ctx(inode);
+
+ wb_writeout_inc(&bdi_wb_ctx->wb);
+}
+
/*
* maximal error of a stat counter.
*/
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 11/15] gfs2: add support in gfs2 to handle multiple writeback contexts
[not found] ` <CGME20250725155508epcas5p20ac32126867cbda41622fdacd6322c5a@epcas5p2.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Add support to handle multiple writeback contexts and check for
dirty_exceeded across all the writeback contexts
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/gfs2/super.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index d3f7a11f4ca7..a053202a176a 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -447,7 +447,7 @@ static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc)
gfs2_log_flush(GFS2_SB(inode), ip->i_gl,
GFS2_LOG_HEAD_FLUSH_NORMAL |
GFS2_LFC_WRITE_INODE);
- if (bdi->wb_ctx[0]->wb.dirty_exceeded)
+ if (bdi_wb_dirty_limit_exceeded(bdi))
gfs2_ail1_flush(sdp, wbc);
else
filemap_fdatawrite(metamapping);
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 12/15] nfs: add support in nfs to handle multiple writeback contexts
[not found] ` <CGME20250725155509epcas5p1875d4078e902601aa5807da9f67501e6@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Fetch writeback context to which an inode is affined. Use it to perform
writeback related operations.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/nfs/internal.h | 3 +--
fs/nfs/write.c | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 953c32576778..40755ef956d3 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -843,8 +843,7 @@ static inline void nfs_folio_mark_unstable(struct folio *folio,
* writeback is happening on the server now.
*/
node_stat_mod_folio(folio, NR_WRITEBACK, nr);
- wb_stat_mod(&inode_to_bdi(inode)->wb_ctx[0]->wb,
- WB_WRITEBACK, nr);
+ bdi_wb_stat_mod(inode, nr);
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
}
}
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index dbf5de7d6a75..8cb9e643a7f8 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -919,8 +919,7 @@ static void nfs_folio_clear_commit(struct folio *folio)
struct inode *inode = folio->mapping->host;
node_stat_mod_folio(folio, NR_WRITEBACK, -nr);
- wb_stat_mod(&inode_to_bdi(inode)->wb_ctx[0]->wb,
- WB_WRITEBACK, -nr);
+ bdi_wb_stat_mod(inode, -nr);
}
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 13/15] writeback: set the num of writeback contexts to number of online cpus
[not found] ` <CGME20250725155510epcas5p18d4d8948dc80a8324e89746090f27e9f@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
We create N number of writeback contexts, N = number of online cpus. The
inodes gets distributed across different writeback contexts, enabling
parallel writeback.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
mm/backing-dev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 0a772d984ecf..d32b13234cdd 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1046,7 +1046,7 @@ int bdi_init(struct backing_dev_info *bdi)
bdi->min_ratio = 0;
bdi->max_ratio = 100 * BDI_RATIO_SCALE;
bdi->max_prop_frac = FPROP_FRAC_BASE;
- bdi->nr_wb_ctx = 1;
+ bdi->nr_wb_ctx = num_online_cpus();
bdi->wb_ctx = kcalloc(bdi->nr_wb_ctx,
sizeof(struct bdi_writeback_ctx *),
GFP_KERNEL);
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 14/15] writeback: segregated allocation and free of writeback contexts
[not found] ` <CGME20250725155510epcas5p1a39021270ba9952f9b520f2fcfd2c5ed@epcas5p1.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
The independent functions of alloc and free will be used while changing
the number of writeback contexts.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
mm/backing-dev.c | 73 +++++++++++++++++++++++++++++++-----------------
1 file changed, 47 insertions(+), 26 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index d32b13234cdd..7c6fbf57e7ef 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1038,51 +1038,72 @@ static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb)
#endif /* CONFIG_CGROUP_WRITEBACK */
-int bdi_init(struct backing_dev_info *bdi)
+static struct bdi_writeback_ctx **wb_ctx_alloc(struct backing_dev_info *bdi,
+ int num_ctxs)
{
- bdi->dev = NULL;
+ struct bdi_writeback_ctx **wb_ctx;
- kref_init(&bdi->refcnt);
- bdi->min_ratio = 0;
- bdi->max_ratio = 100 * BDI_RATIO_SCALE;
- bdi->max_prop_frac = FPROP_FRAC_BASE;
- bdi->nr_wb_ctx = num_online_cpus();
- bdi->wb_ctx = kcalloc(bdi->nr_wb_ctx,
- sizeof(struct bdi_writeback_ctx *),
- GFP_KERNEL);
- INIT_LIST_HEAD(&bdi->bdi_list);
- for (int i = 0; i < bdi->nr_wb_ctx; i++) {
- bdi->wb_ctx[i] = (struct bdi_writeback_ctx *)
- kzalloc(sizeof(struct bdi_writeback_ctx), GFP_KERNEL);
- if (!bdi->wb_ctx[i]) {
+ wb_ctx = kcalloc(num_ctxs, sizeof(struct bdi_writeback_ctx *),
+ GFP_KERNEL);
+ if (!wb_ctx)
+ return NULL;
+
+ for (int i = 0; i < num_ctxs; i++) {
+ wb_ctx[i] = (struct bdi_writeback_ctx *)
+ kzalloc(sizeof(struct bdi_writeback_ctx), GFP_KERNEL);
+ if (!wb_ctx[i]) {
pr_err("Failed to allocate %d", i);
while (--i >= 0)
- kfree(bdi->wb_ctx[i]);
- kfree(bdi->wb_ctx);
- return -ENOMEM;
+ kfree(wb_ctx[i]);
+ kfree(wb_ctx);
+ return NULL;
}
- INIT_LIST_HEAD(&bdi->wb_ctx[i]->wb_list);
- init_waitqueue_head(&bdi->wb_ctx[i]->wb_waitq);
+ INIT_LIST_HEAD(&wb_ctx[i]->wb_list);
+ init_waitqueue_head(&wb_ctx[i]->wb_waitq);
+ }
+ return wb_ctx;
+}
+
+static void wb_ctx_free(struct backing_dev_info *bdi)
+{
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ kfree(bdi_wb_ctx);
}
+ kfree(bdi->wb_ctx);
+}
+
+int bdi_init(struct backing_dev_info *bdi)
+{
+ int ret;
+
+ bdi->dev = NULL;
+
+ INIT_LIST_HEAD(&bdi->bdi_list);
+ bdi->nr_wb_ctx = num_online_cpus();
+
+ bdi->wb_ctx = wb_ctx_alloc(bdi, bdi->nr_wb_ctx);
+ if (!bdi->wb_ctx)
+ return -ENOMEM;
+
bdi->last_bdp_sleep = jiffies;
- return cgwb_bdi_init(bdi);
+ ret = cgwb_bdi_init(bdi);
+ if (ret)
+ wb_ctx_free(bdi);
+ return ret;
}
struct backing_dev_info *bdi_alloc(int node_id)
{
struct backing_dev_info *bdi;
- struct bdi_writeback_ctx *bdi_wb_ctx;
bdi = kzalloc_node(sizeof(*bdi), GFP_KERNEL, node_id);
if (!bdi)
return NULL;
if (bdi_init(bdi)) {
- for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
- kfree(bdi_wb_ctx);
- }
- kfree(bdi->wb_ctx);
kfree(bdi);
return NULL;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 15/15] writeback: added support to change the number of writebacks using a sysfs attribute
[not found] ` <CGME20250725155511epcas5p2498b51a3391fa4a8cf0354d6966686ac@epcas5p2.samsung.com>
@ 2025-07-25 15:54 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-07-25 15:54 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
User can change the number of writeback contexts with values 1 to num
cpus using the new sysfs attribute
echo <num_writbacks> > /sys/class/bdi/<maj>:<min>/nwritebacks
The sequence of operations when number of writebacks is changed :
- fetch the superblock for a bdi
- freezes the filesystem
- iterate through inodes of the superblock and flush the pages
- shutdown and free the writeback threads
- allocate and registter the wb threads
- thaw the filesystem
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/super.c | 23 ++++++++++
include/linux/backing-dev.h | 1 +
include/linux/fs.h | 1 +
mm/backing-dev.c | 91 +++++++++++++++++++++++++++++++++++++
mm/page-writeback.c | 8 ++++
5 files changed, 124 insertions(+)
diff --git a/fs/super.c b/fs/super.c
index 80418ca8e215..097d10e69e18 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -2061,6 +2061,29 @@ static inline bool may_unfreeze(struct super_block *sb, enum freeze_holder who,
return false;
}
+struct super_block *freeze_bdi_super(struct backing_dev_info *bdi)
+{
+ struct super_block *sb_iter;
+ struct super_block *sb = NULL;
+
+ spin_lock(&sb_lock);
+ list_for_each_entry(sb_iter, &super_blocks, s_list) {
+ if (sb_iter->s_bdi == bdi) {
+ sb = sb_iter;
+ break;
+ }
+ }
+ spin_unlock(&sb_lock);
+
+ if (sb) {
+ atomic_inc(&sb->s_active);
+ freeze_super(sb, FREEZE_HOLDER_KERNEL, NULL);
+ }
+
+ return sb;
+}
+EXPORT_SYMBOL(freeze_bdi_super);
+
/**
* freeze_super - lock the filesystem and force it into a consistent state
* @sb: the super to lock
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 5ed8294e0c0e..9c2d70d65277 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -145,6 +145,7 @@ int bdi_set_max_ratio_no_scale(struct backing_dev_info *bdi, unsigned int max_ra
int bdi_set_min_bytes(struct backing_dev_info *bdi, u64 min_bytes);
int bdi_set_max_bytes(struct backing_dev_info *bdi, u64 max_bytes);
int bdi_set_strict_limit(struct backing_dev_info *bdi, unsigned int strict_limit);
+int bdi_set_nwritebacks(struct backing_dev_info *bdi, unsigned int nwritebacks);
/*
* Flags in backing_dev_info::capability
diff --git a/include/linux/fs.h b/include/linux/fs.h
index edcbc5042427..f82f7ff2381f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2758,6 +2758,7 @@ extern int unregister_filesystem(struct file_system_type *);
extern int vfs_statfs(const struct path *, struct kstatfs *);
extern int user_statfs(const char __user *, struct kstatfs *);
extern int fd_statfs(int, struct kstatfs *);
+struct super_block *freeze_bdi_super(struct backing_dev_info *bdi);
int freeze_super(struct super_block *super, enum freeze_holder who,
const void *freeze_owner);
int thaw_super(struct super_block *super, enum freeze_holder who,
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 7c6fbf57e7ef..51fe40af4f98 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -35,6 +35,17 @@ LIST_HEAD(bdi_list);
/* bdi_wq serves all asynchronous writeback tasks */
struct workqueue_struct *bdi_wq;
+static int cgwb_bdi_init(struct backing_dev_info *bdi);
+static void cgwb_bdi_register(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx);
+static void cgwb_bdi_unregister(struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx);
+static void wb_shutdown(struct bdi_writeback *wb);
+static void wb_exit(struct bdi_writeback *wb);
+static struct bdi_writeback_ctx **wb_ctx_alloc(struct backing_dev_info *bdi,
+ int num_ctxs);
+static void wb_ctx_free(struct backing_dev_info *bdi);
+
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
@@ -469,6 +480,85 @@ static ssize_t strict_limit_show(struct device *dev,
}
static DEVICE_ATTR_RW(strict_limit);
+static ssize_t nwritebacks_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct backing_dev_info *bdi = dev_get_drvdata(dev);
+ unsigned int nwritebacks;
+ ssize_t ret;
+ struct super_block *sb = NULL;
+ struct bdi_writeback_ctx **wb_ctx;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+ struct inode *inode;
+
+ ret = kstrtouint(buf, 10, &nwritebacks);
+ if (ret < 0)
+ return ret;
+
+ if (nwritebacks < 1 || nwritebacks > num_online_cpus())
+ return -EINVAL;
+
+ if (nwritebacks == bdi->nr_wb_ctx)
+ return count;
+
+ wb_ctx = wb_ctx_alloc(bdi, nwritebacks);
+ if (!wb_ctx)
+ return -ENOMEM;
+
+ sb = freeze_bdi_super(bdi);
+ if (!sb)
+ return -EBUSY;
+
+ spin_lock(&sb->s_inode_list_lock);
+ list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+ filemap_write_and_wait(inode->i_mapping);
+ truncate_inode_pages_final(inode->i_mapping);
+ if (inode->i_wb) {
+ WARN_ON_ONCE(!(inode->i_state & I_CLEAR));
+ wb_put(inode->i_wb);
+ inode->i_wb = NULL;
+ }
+ }
+ spin_unlock(&sb->s_inode_list_lock);
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ wb_shutdown(&bdi_wb_ctx->wb);
+ cgwb_bdi_unregister(bdi, bdi_wb_ctx);
+ }
+
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ WARN_ON_ONCE(test_bit(WB_registered, &bdi_wb_ctx->wb.state));
+ wb_exit(&bdi_wb_ctx->wb);
+ kfree(bdi_wb_ctx);
+ }
+ kfree(bdi->wb_ctx);
+
+ ret = bdi_set_nwritebacks(bdi, nwritebacks);
+
+ bdi->wb_ctx = wb_ctx;
+
+ cgwb_bdi_init(bdi);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx) {
+ cgwb_bdi_register(bdi, bdi_wb_ctx);
+ set_bit(WB_registered, &bdi_wb_ctx->wb.state);
+ }
+
+ thaw_super(sb, FREEZE_HOLDER_KERNEL, NULL);
+ deactivate_super(sb);
+
+ return ret;
+}
+
+static ssize_t nwritebacks_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backing_dev_info *bdi = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", bdi->nr_wb_ctx);
+}
+static DEVICE_ATTR_RW(nwritebacks);
+
static struct attribute *bdi_dev_attrs[] = {
&dev_attr_read_ahead_kb.attr,
&dev_attr_min_ratio.attr,
@@ -479,6 +569,7 @@ static struct attribute *bdi_dev_attrs[] = {
&dev_attr_max_bytes.attr,
&dev_attr_stable_pages_required.attr,
&dev_attr_strict_limit.attr,
+ &dev_attr_nwritebacks.attr,
NULL,
};
ATTRIBUTE_GROUPS(bdi_dev);
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 8b4271e75f9e..a28845d0171a 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -818,6 +818,14 @@ int bdi_set_strict_limit(struct backing_dev_info *bdi, unsigned int strict_limit
return 0;
}
+int bdi_set_nwritebacks(struct backing_dev_info *bdi, unsigned int nwritebacks)
+{
+ spin_lock_bh(&bdi_lock);
+ bdi->nr_wb_ctx = nwritebacks;
+ spin_unlock_bh(&bdi_lock);
+ return 0;
+}
+
static unsigned long dirty_freerun_ceiling(unsigned long thresh,
unsigned long bg_thresh)
{
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v2 07/15] writeback: modify sync related functions to iterate over all writeback contexts
[not found] ` <CGME20250807045822epcas5p3b66fb5dcc2e4b6b4a758a5eedb5b2fe9@epcas5p3.samsung.com>
@ 2025-08-07 4:56 ` Kundan Kumar
0 siblings, 0 replies; 17+ messages in thread
From: Kundan Kumar @ 2025-08-07 4:56 UTC (permalink / raw)
To: mcgrof; +Cc: patches, Kundan Kumar, Anuj Gupta
Modify sync related functions to iterate over all writeback contexts.
Signed-off-by: Kundan Kumar <kundan.kumar@samsung.com>
Signed-off-by: Anuj Gupta <anuj20.g@samsung.com>
---
fs/fs-writeback.c | 66 +++++++++++++++++++++++++++++++----------------
1 file changed, 44 insertions(+), 22 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 1df8dd7af048..328bf7303df6 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -2752,11 +2752,13 @@ static void wait_sb_inodes(struct super_block *sb)
mutex_unlock(&sb->s_sync_lock);
}
-static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
- enum wb_reason reason, bool skip_if_busy)
+static void __writeback_inodes_sb_nr_ctx(struct super_block *sb,
+ unsigned long nr,
+ enum wb_reason reason,
+ bool skip_if_busy,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
+ DEFINE_WB_COMPLETION(done, bdi_wb_ctx);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_NONE,
@@ -2766,13 +2768,23 @@ static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
.reason = reason,
};
+ bdi_split_work_to_wbs(sb->s_bdi, bdi_wb_ctx, &work, skip_if_busy);
+ wb_wait_for_completion(&done);
+}
+
+static void __writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr,
+ enum wb_reason reason, bool skip_if_busy)
+{
+ struct backing_dev_info *bdi = sb->s_bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
if (!bdi_has_dirty_io(bdi) || bdi == &noop_backing_dev_info)
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
- bdi_split_work_to_wbs(sb->s_bdi, bdi->wb_ctx[0], &work,
- skip_if_busy);
- wb_wait_for_completion(&done);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ __writeback_inodes_sb_nr_ctx(sb, nr, reason, skip_if_busy,
+ bdi_wb_ctx);
}
/**
@@ -2825,17 +2837,11 @@ void try_to_writeback_inodes_sb(struct super_block *sb, enum wb_reason reason)
}
EXPORT_SYMBOL(try_to_writeback_inodes_sb);
-/**
- * sync_inodes_sb - sync sb inode pages
- * @sb: the superblock
- *
- * This function writes and waits on any dirty inode belonging to this
- * super_block.
- */
-void sync_inodes_sb(struct super_block *sb)
+static void sync_inodes_bdi_wb_ctx(struct super_block *sb,
+ struct backing_dev_info *bdi,
+ struct bdi_writeback_ctx *bdi_wb_ctx)
{
- struct backing_dev_info *bdi = sb->s_bdi;
- DEFINE_WB_COMPLETION(done, bdi->wb_ctx[0]);
+ DEFINE_WB_COMPLETION(done, bdi_wb_ctx);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_ALL,
@@ -2846,6 +2852,25 @@ void sync_inodes_sb(struct super_block *sb)
.for_sync = 1,
};
+ /* protect against inode wb switch, see inode_switch_wbs_work_fn() */
+ bdi_down_write_wb_ctx_switch_rwsem(bdi_wb_ctx);
+ bdi_split_work_to_wbs(bdi, bdi_wb_ctx, &work, false);
+ wb_wait_for_completion(&done);
+ bdi_up_write_wb_ctx_switch_rwsem(bdi_wb_ctx);
+}
+
+/**
+ * sync_inodes_sb - sync sb inode pages
+ * @sb: the superblock
+ *
+ * This function writes and waits on any dirty inode belonging to this
+ * super_block.
+ */
+void sync_inodes_sb(struct super_block *sb)
+{
+ struct backing_dev_info *bdi = sb->s_bdi;
+ struct bdi_writeback_ctx *bdi_wb_ctx;
+
/*
* Can't skip on !bdi_has_dirty() because we should wait for !dirty
* inodes under writeback and I_DIRTY_TIME inodes ignored by
@@ -2855,11 +2880,8 @@ void sync_inodes_sb(struct super_block *sb)
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
- /* protect against inode wb switch, see inode_switch_wbs_work_fn() */
- bdi_down_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
- bdi_split_work_to_wbs(bdi, bdi->wb_ctx[0], &work, false);
- wb_wait_for_completion(&done);
- bdi_up_write_wb_ctx_switch_rwsem(bdi->wb_ctx[0]);
+ for_each_bdi_wb_ctx(bdi, bdi_wb_ctx)
+ sync_inodes_bdi_wb_ctx(sb, bdi, bdi_wb_ctx);
wait_sb_inodes(sb);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-08-07 4:58 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20250725155458epcas5p3bf5ce4f3ec0b1868588098c52c87d110@epcas5p3.samsung.com>
2025-07-25 15:54 ` [PATCH v2 00/15] Parallelizing filesystem writeback Kundan Kumar
[not found] ` <CGME20250725155459epcas5p2488a6d5af262a3a5718ac405dd9bed1c@epcas5p2.samsung.com>
2025-07-25 15:54 ` [PATCH v2 01/15] writeback: add infra for parallel writeback Kundan Kumar
[not found] ` <CGME20250725155500epcas5p225e5103ea402a715cdde2eefd46af987@epcas5p2.samsung.com>
2025-07-25 15:54 ` [PATCH v2 02/15] writeback: add support to initialize and free multiple writeback ctxs Kundan Kumar
[not found] ` <CGME20250725155501epcas5p1d281c17292ea2fa12e6dcc9c90eee929@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 03/15] writeback: link bdi_writeback to its corresponding bdi_writeback_ctx Kundan Kumar
[not found] ` <CGME20250725155502epcas5p149ee8859fd9a97523bf876b50deaa84f@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 04/15] writeback: affine inode to a writeback ctx within a bdi Kundan Kumar
[not found] ` <CGME20250725155502epcas5p2efb36021968a5693170d87f7440cb356@epcas5p2.samsung.com>
2025-07-25 15:54 ` [PATCH v2 05/15] writeback: modify bdi_writeback search logic to search across all wb ctxs Kundan Kumar
[not found] ` <CGME20250725155503epcas5p142aea80acbddccf89d040249b183378d@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 06/15] writeback: invoke all writeback contexts for flusher and dirtytime writeback Kundan Kumar
[not found] ` <CGME20250725155504epcas5p1c92bb829279a8563dc99e2b475e1a221@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 07/15] writeback: modify sync related functions to iterate over all writeback contexts Kundan Kumar
[not found] ` <CGME20250725155505epcas5p3981ad791c6c044c7abc6d232c05d54f9@epcas5p3.samsung.com>
2025-07-25 15:54 ` [PATCH v2 08/15] writeback: add support to collect stats for all writeback ctxs Kundan Kumar
[not found] ` <CGME20250725155506epcas5p3db90b1c2f47ed6f67c18825f0a39c51b@epcas5p3.samsung.com>
2025-07-25 15:54 ` [PATCH v2 09/15] f2fs: add support in f2fs to handle multiple writeback contexts Kundan Kumar
[not found] ` <CGME20250725155507epcas5p113787f985ae9dcc5ff8c42caf2035047@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 10/15] fuse: add support for multiple writeback contexts in fuse Kundan Kumar
[not found] ` <CGME20250725155508epcas5p20ac32126867cbda41622fdacd6322c5a@epcas5p2.samsung.com>
2025-07-25 15:54 ` [PATCH v2 11/15] gfs2: add support in gfs2 to handle multiple writeback contexts Kundan Kumar
[not found] ` <CGME20250725155509epcas5p1875d4078e902601aa5807da9f67501e6@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 12/15] nfs: add support in nfs " Kundan Kumar
[not found] ` <CGME20250725155510epcas5p18d4d8948dc80a8324e89746090f27e9f@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 13/15] writeback: set the num of writeback contexts to number of online cpus Kundan Kumar
[not found] ` <CGME20250725155510epcas5p1a39021270ba9952f9b520f2fcfd2c5ed@epcas5p1.samsung.com>
2025-07-25 15:54 ` [PATCH v2 14/15] writeback: segregated allocation and free of writeback contexts Kundan Kumar
[not found] ` <CGME20250725155511epcas5p2498b51a3391fa4a8cf0354d6966686ac@epcas5p2.samsung.com>
2025-07-25 15:54 ` [PATCH v2 15/15] writeback: added support to change the number of writebacks using a sysfs attribute Kundan Kumar
2025-08-07 4:56 [PATCH v2 00/15] Test patch Parallelizing filesystem writeback Kundan Kumar
[not found] ` <CGME20250807045822epcas5p3b66fb5dcc2e4b6b4a758a5eedb5b2fe9@epcas5p3.samsung.com>
2025-08-07 4:56 ` [PATCH v2 07/15] writeback: modify sync related functions to iterate over all writeback contexts Kundan Kumar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).