Add a super operation for writeback
Subsystems (2)
RFC PATCH 1/2 Add a super operation for writeback
Hi,
This is the first of four core changes we would like for Tux3. We start with a hard one and suggest a simple solution.
The first patch in this series adds a new super operation to write back multiple inodes in a single call. The second patch applies to our linux-tux3 repository at git.kernel.org to demonstrate how this interface is used, and removes about 450 lines of workaround code.
Traditionally, core kernel tells each mounted filesystems which dirty pages of which inodes should be flushed to disk, but unfortunately, is blissfully ignorant of filesystem-specific ordering constraints. This scheme was really invented for Ext2 and has not evolved much recently. Tux3, with its strong ordering and optimized delta commit, cannot tolerate random flushing and therefore takes responsibility for flush ordering itself. On the other hand, Tux3 has no good way to know when is the right time to flush, but core is good at that. This proposed API harmonizes those two competencies so that Tux3 and core each take care of what they are good at, and not more.
The API extension consists of a new writeback hook and two helpers to be uses within the hook. The hook sits about halfway down the fs-writeback stack, just after core has determined that some dirty inodes should be flushed to disk and just before it starts thinking about which inodes those should be. At that point, core calls Tux3 instead of continuing on down the usual do_writepages path. Tux3 responds by staging a new delta commit, using the new helpers to tell core which inodes were flushed versus being left dirty in cache. This is pretty much the same behavior as the traditional writeout path, but less convoluted, probably more efficient, and certainly easier to analyze.
The new writeback hook looks like:
progress = sb->s_op->writeback(sb, &writeback_control, &nr_pages);
This should be self-explanatory: nr_pages and progress have the semantics of existing usage in fs-writeback; writeback_control is ignored by Tux3, but that is only because Tux3 always flushes everything and does not require hints for now. We can safely assume that &wbc or equivalent is wanted here. An obvious wart is the overlap between "progress" and "nr_pages", but fs-writeback thinks that way, so it would not make much sense to improve one without improving the other.
Tux3 necessarily keeps its own dirty inode list, which is an area of overlap with fs-writeback. In a perfect world, there would be just one dirty inode list per superblock, on which both fs-writeback and Tux3 would operate. That would be a deeper core change than seems appropriate right now.
Potential races are a big issue with this API, which is no surprise. The fs-writeback scheme requires keeping several kinds of object in
sync: tux3 dirty inode lists, fs-writeback dirty inode lists and inode dirty state. The new helpers inode_writeback_done(inode) and inode_writeback_touch(inode) take care of that while hiding internal details of the fs-writeback implementation.
Tux3 calls inode_writeback_done when it has flushed an inode and marked it clean, or calls inode_writeback_touch if it intends to retain a dirty inode in cache. These have simple implementations. The former just removes a clean inode from any fs-writeback list. The latter updates the inode's dirty timestamp so that fs-writeback does not keep trying flush it. Both these things could be done more efficiently by re-engineering fs-writeback, but we prefer to work with the existing scheme for now.
Hirofumi's masterful hack nicely avoided racy removal of inodes from the writeback list by taking an internal fs-writeback lock inside filesystem code. The new helper requires dropping i_lock inside filesystem code and retaking it in the helper, so inode redirty can race with writeback list removal. This does not seem to present a problem because filesystem code is able to enforce strict alternation of cleaning and calling the helper. As an offsetting advantage, writeback lock contention is reduced.
Compared to Hirofumi's hack, the cost of this interface is one additional spinlock per inode_writeback_done, which is insignificant compared to the convoluted code path that is avoided. Regards,
Daniel
---
From: Daniel Phillips <[email protected]>
Subject: [PATCH] Add a super operation for writeback
Add a "writeback" super operation to be called in the form:
progress = sb->s_op->writeback(sb, &wbc, &pages);
The filesystem is expected to flush some inodes to disk and return progress of at least 1, or if no inodes are flushed, return progress of zero. The filesystem should try to flush at least the number of pages specified in *pages, or if that is not possible, return approximately the number of pages not flushed into *pages.
Within the ->writeback callback, the filesystem should call inode_writeback_done(inode) for each inode flushed (and therefore set clean) or inode_writeback_touch(inode) for any inode that will be retained dirty in cache.
---
fs/fs-writeback.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++--- include/linux/fs.h | 4 +++ 2 files changed, 60 insertions(+), 3 deletions(-)
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +56 -3
@@ -192,6 +192,35 @@ void inode_wb_list_del(struct inode *ino }/* +* Remove inode from writeback list if clean. +*/ +void inode_writeback_done (struct inode *inode) +{ +struct backing_dev_info *bdi =inode_to_bdi (inode); + +spin_lock (&bdi->wb.list_lock); +spin_lock (&inode->i_lock); +if (!(inode->i_state &I_DIRTY )) +list_del_init (&inode->i_wb_list); +spin_unlock (&inode->i_lock); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_done); + +/* +* Add inode to writeback dirty list with current time. +*/ +void inode_writeback_touch (struct inode *inode) +{ +struct backing_dev_info *bdi = inode->i_sb->s_bdi; +spin_lock (&bdi->wb.list_lock); + inode->dirtied_when = jiffies; +list_move (&inode->i_wb_list, &bdi->wb.b_dirty); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_touch); + +/* * Redirty an inode: set its when-it-was dirtied timestamp and move it to the * furthest end of its superblock's dirty-inode list. *
@@ -593,9 +622,9 @@ static long writeback_chunk_size(struct* * Return the number of pages and/or inodes written. */ -static long writeback_sb_inodes (struct super_block *sb, -struct bdi_writeback *wb, -struct wb_writeback_work *work) +static long __writeback_sb_inodes (struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) {struct writeback_control wbc = { .sync_mode = work->sync_mode,
@@ -710,6 +739,30 @@ static long writeback_sb_inodes(struct sreturn wrote; } +static long writeback_sb_inodes (struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) +{ +if (sb->s_op->writeback) { +struct writeback_control wbc = { + .sync_mode = work->sync_mode, + .tagged_writepages = work->tagged_writepages, + .for_kupdate = work->for_kupdate, + .for_background = work->for_background, + .for_sync = work->for_sync, + .range_cyclic = work->range_cyclic, + }; +long ret; + +spin_unlock (&wb->list_lock); + ret = sb->s_op->writeback (sb, &wbc, &work->nr_pages); +spin_lock (&wb->list_lock); +return ret; + } + +return __writeback_sb_inodes (sb, wb, work); +} +static long __writeback_inodes_wb (struct bdi_writeback *wb,struct wb_writeback_work *work) {
include/linux/fs.h renamed from include/linux/fs.h~core-writeback +4 -0
@@ -1536,6 +1536,8 @@ struct super_operations {int (*drop_inode) (struct inode *);void (*evict_inode) (struct inode *);void (*put_super) (struct super_block *); +long (*writeback)(struct super_block *super, +struct writeback_control *wbc,long *nr_pages);int (*sync_fs)(struct super_block *sb,int wait);int (*freeze_fs) (struct super_block *);int (*unfreeze_fs) (struct super_block *);
@@ -1737,6 +1739,8 @@ static inline void file_accessed(structtouch_atime (&file->f_path); } +void inode_writeback_done (struct inode *inode); +void inode_writeback_touch (struct inode *inode);int sync_inode (struct inode *inode,struct writeback_control *wbc);int sync_inode_metadata (struct inode *inode,int wait);
RFC PATCH 2/2 tux3: Use writeback hook to remove duplicated core code
Instead of re-implementing part of fs/fs-writeback.c, use a proposed net ->writeback super operation to drive delta writeback. For each inode that is cleaned, call inode_writeback_done(inode). For each inode that will be kept dirty in cache, call inode_writeback_touch so that the inode appears young to fs-writeback and does not trigger repeated ->writeback flushes.
--- fs/tux3/Makefile | 2 +- fs/tux3/commit.c | 1 - fs/tux3/commit_flusher.c | 180 ++++++++++-------- fs/tux3/commit_flusher.h | 16 -- fs/tux3/commit_flusher_hack.c | 423 ------------------------------------------ fs/tux3/inode.c | 2 - fs/tux3/super.c | 17 +- fs/tux3/tux3.h | 11 +- fs/tux3/writeback.c | 75 ++------ 11 files changed, 128 insertions(+), 599 deletions(-) delete mode 100644 fs/tux3/commit_flusher.h delete mode 100644 fs/tux3/commit_flusher_hack.c
fs/tux3/Makefile +1 -1
@@ -13,7 +13,7 @@ tux3-objs += balloc.o btree.o buffer.o commit.o dir.o dleaf.o \ EXTRA_CFLAGS += -Werror -std=gnu99 -Wno-declaration-after-statement #EXTRA_CFLAGS += -DTUX3_FLUSHER=TUX3_FLUSHER_SYNC #EXTRA_CFLAGS += -DTUX3_FLUSHER=TUX3_FLUSHER_ASYNC_OWN -EXTRA_CFLAGS += -DTUX3_FLUSHER=TUX3_FLUSHER_ASYNC_HACK +EXTRA_CFLAGS += -DTUX3_FLUSHER=TUX3_FLUSHER_ASYNC obj-$(CONFIG_TUX3_MMAP) += mmap_builtin_hack.o endif
fs/tux3/commit.c +0 -1
@@ -638,7 +638,6 @@ static void delta_transition(struct sb *sb) ((int)(a) - (int)(b) >= 0))#include "commit_flusher.c" -#include "commit_flusher_hack.c" int force_unify (struct sb *sb) {
fs/tux3/commit_flusher.c +105 -75
220 lines not on this page · raw message
fs/tux3/commit_flusher.h deleted +0 -16
@@ -1,16 +0,0 @@ -#ifndef TUX3_COMMIT_FLUSHER_H -#define TUX3_COMMIT_FLUSHER_H - -/* FIXME: Remove this file after implement of flusher interface */ - -#if TUX3_FLUSHER ==TUX3_FLUSHER_ASYNC_HACK -/* Hack for BDI_CAP_NO_WRITEBACK */ -void tux3_set_mapping_bdi (struct inode *inode); -#else -static inline void tux3_set_mapping_bdi (struct inode *inode) { } -#endif - -int tux3_init_flusher (struct sb *sb); -void tux3_exit_flusher (struct sb *sb); - -#endif /* !TUX3_COMMIT_FLUSHER_H */
fs/tux3/commit_flusher_hack.c deleted +0 -423
423 lines not on this page · raw message
fs/tux3/inode.c +0 -2
@@ -932,8 +932,6 @@ static void tux_setup_inode(struct inode *inode)assert (tux_inode (inode)->inum !=TUX_INVALID_INO ); -tux3_set_mapping_bdi (inode); -// inode->i_generation = 0; // inode->i_flags = 0;
fs/tux3/super.c +4 -13
@@ -126,9 +126,6 @@ static void __tux3_put_super(struct sb *sbi)iput (sbi->volmap); sbi->volmap =NULL ; -/* Cleanup flusher after inode was evicted */ -tux3_exit_flusher (sbi); -tux3_free_idefer_map (sbi->idefer_map); sbi->idefer_map =NULL ;/* FIXME: add more sanity check */
@@ -178,13 +175,6 @@ struct replay *tux3_init_fs(struct sb *sbi)char *name;int err; -/* Initialize flusher before setup inode */ - err =tux3_init_flusher (sbi); -if (err) { -tux3_err (sbi,"failed to initialize flusher" ); - goto error; - } - err = -ENOMEM ;/* Prepare non on-disk inodes */
@@ -375,7 +365,7 @@ static void tux3_destroy_inode(struct inode *inode)call_rcu (&inode->i_rcu, tux3_i_callback); } -#if TUX3_FLUSHER !=TUX3_FLUSHER_ASYNC_HACK +#if TUX3_FLUSHER !=TUX3_FLUSHER_ASYNC static int tux3_sync_fs (struct super_block *sb,int wait) {/* FIXME: We should support "wait" parameter. */
@@ -423,12 +413,13 @@ static const struct super_operations tux3_super_ops = { .evict_inode = tux3_evict_inode,/* FIXME: we have to handle write_inode of sync (e.g. cache pressure) */ // .write_inode = tux3_write_inode, -#if TUX3_FLUSHER !=TUX3_FLUSHER_ASYNC_HACK -/* If TUX3_FLUSHER_ASYNC_HACK, normal kernel flush request does all */ +#if TUX3_FLUSHER !=TUX3_FLUSHER_ASYNC +/* If TUX3_FLUSHER_ASYNC, normal kernel flush request does all */ .sync_fs = tux3_sync_fs, #endif .put_super = tux3_put_super, .statfs = tux3_statfs, + .writeback = tux3_writeback, };static int tux3_fill_super (struct super_block *sb,void *data,int silent)
fs/tux3/tux3.h +4 -7
@@ -222,7 +222,7 @@ struct stash { struct flink_head head; u64 *pos, *top; };/* Flush asynchronously by own timing */ #define TUX3_FLUSHER_ASYNC_OWN 2/* Flush asynchronously by kernel normal timing (by hackish way) */ -#define TUX3_FLUSHER_ASYNC_HACK 3 +#define TUX3_FLUSHER_ASYNC 3/* Refcount for delta */ struct delta_ref {
@@ -271,9 +271,6 @@ struct sb {#if TUX3_FLUSHER ==TUX3_FLUSHER_ASYNC_OWN struct task_struct *flush_task;/* work to flush delta */ #endif -#if TUX3_FLUSHER ==TUX3_FLUSHER_ASYNC_HACK -struct backing_dev_info bdi; -#endif struct btree itree;/* Inode btree */ struct btree otree;/* Orphan btree */
@@ -793,9 +790,6 @@ int change_end(struct sb *sb);void change_begin_if_needed (struct sb *sb,int need_sep);void change_end_if_needed (struct sb *sb); -/* commit_flusher.c */ -#include "commit_flusher.h" -/* dir.c */ void tux_set_entry (struct buffer_head *buffer,struct tux3_dirent *entry,inum_t inum,umode_t mode);
@@ -978,6 +972,9 @@ static inline void tux3_mark_inode_dirty_sync(struct inode *inode)__tux3_mark_inode_dirty (inode,I_DIRTY_SYNC ); } +struct super_block ; +struct writeback_control ; +long tux3_writeback (struct super_block *super,struct writeback_control *wbc,long *nr_pages);void tux3_dirty_inode (struct inode *inode,int flags);void tux3_mark_inode_to_delete (struct inode *inode);void tux3_iattrdirty (struct inode *inode);
fs/tux3/writeback.c +14 -61
119 lines not on this page · raw message
Quoted 53 lines · Daniel Phillips
---
From: Daniel Phillips <[email protected]>
Subject: [PATCH] Add a super operation for writebackAdd a "writeback" super operation to be called in the form:
progress = sb->s_op->writeback(sb, &wbc, &pages);The filesystem is expected to flush some inodes to disk and return progress of at least 1, or if no inodes are flushed, return progress of zero. The filesystem should try to flush at least the number of pages specified in *pages, or if that is not possible, return approximately the number of pages not flushed into *pages.
Within the ->writeback callback, the filesystem should call
inode_writeback_done(inode)for each inode flushed (and therefore set clean) orinode_writeback_touch(inode)for any inode that will be retained dirty in cache.Signed-off-by Daniel Phillips <[email protected]>Signed-off-by OGAWA Hirofumi <[email protected]>---
fs/fs-writeback.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++--- include/linux/fs.h | 4 +++ 2 files changed, 60 insertions(+), 3 deletions(-)diff -puN
fs/fs-writeback.c~core-writebackfs/fs-writeback.cfs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +17
-0@@ -192,6 +192,35 @@ void inode_wb_list_del(struct inode *ino }/* +* Remove inode from writeback list if clean. +*/ +void inode_writeback_done (struct inode *inode) +{ +struct backing_dev_info *bdi =inode_to_bdi (inode); + +spin_lock (&bdi->wb.list_lock); +spin_lock (&inode->i_lock); +if (!(inode->i_state &I_DIRTY )) +list_del_init (&inode->i_wb_list); +spin_unlock (&inode->i_lock); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_done); + +/* +* Add inode to writeback dirty list with current time.
fs/fs-writeback.c void inode_wb_list_del(struct inode *ino · line 221
Quoted 10 lines
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +10
-0+*/ +void inode_writeback_touch (struct inode *inode) +{ +struct backing_dev_info *bdi = inode->i_sb->s_bdi; +spin_lock (&bdi->wb.list_lock); + inode->dirtied_when = jiffies; +list_move (&inode->i_wb_list, &bdi->wb.b_dirty); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_touch);
You should be able to use redirty_tail() for this....
Hmmmm - this is using the wb dirty lists and locks, but you don't pass the wb structure to the writeback callout? That seem wrong to me - why would you bother manipulating these lists if you aren't using them to track dirty inodes in the first place?
Quoted 35 lines
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +18
-3+ +/* * Redirty an inode: set its when-it-was dirtied timestamp and move it to the * furthest end of its superblock's dirty-inode list. * @@ -593,9 +622,9 @@ static long writeback_chunk_size(struct* * Return the number of pages and/or inodes written. */ -static long writeback_sb_inodes (struct super_block *sb, -struct bdi_writeback *wb, -struct wb_writeback_work *work) +static long __writeback_sb_inodes (struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) {struct writeback_control wbc = { .sync_mode = work->sync_mode,@@ -710,6 +739,30 @@ static long writeback_sb_inodes(struct sreturn wrote; } +static long writeback_sb_inodes (struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) +{ +if (sb->s_op->writeback) { +struct writeback_control wbc = { + .sync_mode = work->sync_mode, + .tagged_writepages = work->tagged_writepages, + .for_kupdate = work->for_kupdate, + .for_background = work->for_background, + .for_sync = work->for_sync, + .range_cyclic = work->range_cyclic, + };
fs/fs-writeback.c static long writeback_sb_inodes(struct s · line 764
Quoted 10 lines
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +10
-0+long ret; + +spin_unlock (&wb->list_lock); + ret = sb->s_op->writeback (sb, &wbc, &work->nr_pages); +spin_lock (&wb->list_lock); +return ret; + } + +return __writeback_sb_inodes (sb, wb, work); +}
The first thing that __writeback_sb_inodes() does is create a struct writeback_control from the wb_writeback_work. That should be done here and passed to __writeback_sb_inodes(), which should be renamed "generic_writeback_sb_inodes()". Also, all the fields in the wbc need to be initialised correctly (i.e including range_start/end).
Further, a writeback implementation will need to use the generic bdi list and lock structures and so we need to pass the bdi_writeback. Similarly, if we are going to pass nr_pages, we might as well pass the entire work structure.
Finally, I don't like the way the wb->list_lock is treated differently by this code. I suspect that we need to rationalise the layering of the wb->list_lock as it is currently not clear what it protects and what (nested) layers of the writeback code actually require it.
What I'd like to see is this work:
struct super_ops ... = {
....
.writeback = generic_writeback_sb_inodes,
....
And that means writeback_sb_inodes() would become:
static long writeback_sb_inodes(struct super_block *sb,
struct bdi_writeback *wb, struct wb_writeback_work *work)
{
struct writeback_control wbc = {
.sync_mode = work->sync_mode,
.tagged_writepages = work->tagged_writepages,
.for_kupdate = work->for_kupdate,
.for_background = work->for_background,
.for_sync = work->for_sync,
.range_cyclic = work->range_cyclic,
.range_start = 0,
.range_end = LLONG_MAX,
};
if (sb->s_op->writeback)
return sb->s_op->writeback(sb, wb, work, &wbc);
return generic_writeback_sb_inodes(sb, wb, work, &wbc);
}And the higher/lower layers deal with wb->list_lock appropriately.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Re: [RFC][PATCH 2/2] tux3: Use writeback hook to remove duplicated core code
In reply to Daniel PhillipsQuoted 8 lines · Daniel Phillips
Instead of re-implementing part of
fs/fs-writeback.c, use a proposed net ->writeback super operation to drive delta writeback. For each inode that is cleaned, callinode_writeback_done(inode). For each inode that will be kept dirty in cache, callinode_writeback_touchso that the inode appears young to fs-writeback and does not trigger repeated ->writeback flushes.Signed-off-by Daniel Phillips <[email protected]>
I have not looked at the sanity of the tux3 writeback algorithm, so I'm not commenting on whether it works or not. However, this caught my eye:
fs/tux3/writeback.c static void tux3_clear_dirty_inode_nolock(struct inode *inode, unsigned delta, · line 263
Quoted 12 lines
static void
__tux3_clear_dirty_inode(struct inode *inode, unsigned delta){ struct tux3_inode *tuxnode = tux_inode(inode);fs/tux3/writeback.c +1
-2-tux3_inode_wb_lock (inode);spin_lock (&inode->i_lock);spin_lock (&tuxnode->lock);tux3_clear_dirty_inode_nolock (inode, delta,0 );spin_unlock (&tuxnode->lock);spin_unlock (&inode->i_lock); -tux3_inode_wb_unlock (inode); +inode_writeback_done (inode); }
I get very worried whenever I see locks inside inode->i_lock. In general, i_lock is supposed to be the innermost lock that is taken, and there are very few exceptions to that - the inode LRU list is one of the few.
I don't know what the tuxnode->lock is, but I found this:
* inode->i_lock
* tuxnode->lock (to protect tuxnode data)
* tuxnode->dirty_inodes_lock (for i_ddc->dirty_inodes,
* Note: timestamp can be updated
* outside inode->i_mutex)
and this:
* inode->i_lock
* tuxnode->lock
* sb->dirty_inodes_lock
Which indicates that you take a filesystem global lock a couple of layers underneath the VFS per-inode i_lock. I'd suggest you want to separate the use of the vfs inode ilock from the locking heirarchy of the tux3 inode....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 78 lines · Daniel Phillips
Hi,
This is the first of four core changes we would like for Tux3. We start with a hard one and suggest a simple solution.
The first patch in this series adds a new super operation to write back multiple inodes in a single call. The second patch applies to our linux-tux3 repository at git.kernel.org to demonstrate how this interface is used, and removes about 450 lines of workaround code.
Traditionally, core kernel tells each mounted filesystems which dirty pages of which inodes should be flushed to disk, but unfortunately, is blissfully ignorant of filesystem-specific ordering constraints. This scheme was really invented for Ext2 and has not evolved much recently. Tux3, with its strong ordering and optimized delta commit, cannot tolerate random flushing and therefore takes responsibility for flush ordering itself. On the other hand, Tux3 has no good way to know when is the right time to flush, but core is good at that. This proposed API harmonizes those two competencies so that Tux3 and core each take care of what they are good at, and not more.
The API extension consists of a new writeback hook and two helpers to be uses within the hook. The hook sits about halfway down the fs-writeback stack, just after core has determined that some dirty inodes should be flushed to disk and just before it starts thinking about which inodes those should be. At that point, core calls Tux3 instead of continuing on down the usual
do_writepagespath. Tux3 responds by staging a new delta commit, using the new helpers to tell core which inodes were flushed versus being left dirty in cache. This is pretty much the same behavior as the traditional writeout path, but less convoluted, probably more efficient, and certainly easier to analyze.The new writeback hook looks like:
progress = sb->s_op->writeback(sb,&writeback_control,&nr_pages);This should be self-explanatory:
nr_pagesand progress have the semantics of existing usage in fs-writeback;writeback_controlis ignored by Tux3, but that is only because Tux3 always flushes everything and does not require hints for now. We can safely assume that&wbc or equivalent is wanted here. An obvious wart is the overlap between "progress" and "nr_pages", but fs-writeback thinks that way, so it would not make much sense to improve one without improving the other.Tux3 necessarily keeps its own dirty inode list, which is an area of overlap with fs-writeback. In a perfect world, there would be just one dirty inode list per superblock, on which both fs-writeback and Tux3 would operate. That would be a deeper core change than seems appropriate right now.
Potential races are a big issue with this API, which is no surprise. The fs-writeback scheme requires keeping several kinds of object in
sync: tux3 dirty inode lists, fs-writeback dirty inode lists and inode dirty state. The new helpersinode_writeback_done(inode)andinode_writeback_touch(inode)take care of that while hiding internal details of the fs-writeback implementation.Tux3 calls
inode_writeback_donewhen it has flushed an inode and marked it clean, or callsinode_writeback_touchif it intends to retain a dirty inode in cache. These have simple implementations. The former just removes a clean inode from any fs-writeback list. The latter updates the inode's dirty timestamp so that fs-writeback does not keep trying flush it. Both these things could be done more efficiently by re-engineering fs-writeback, but we prefer to work with the existing scheme for now.Hirofumi's masterful hack nicely avoided racy removal of inodes from the writeback list by taking an internal fs-writeback lock inside filesystem code. The new helper requires dropping
i_lockinside filesystem code and retaking it in the helper, so inode redirty can race with writeback list removal. This does not seem to present a problem because filesystem code is able to enforce strict alternation of cleaning and calling the helper. As an offsetting advantage, writeback lock contention is reduced.
Quoted 6 lines
Compared to Hirofumi's hack, the cost of this interface is one additional spinlock per
inode_writeback_done, which is insignificant compared to the convoluted code path that is avoided. Regards,Daniel
When I followed the advice of Dave Chinner:
"We're not going to merge that page forking stuff (like you were told at LSF 2013 more than a year ago: http://lwn.net/Articles/548091/) without rigorous design review and a demonstration of the solutions to all the hard corner cases it has" given in his e-mail related with the presentation of the latest version of the Tux3 file system (see [1]) and read the linked article, I found in the second comments:
"Parts of this almost sound like it either a.) overlaps with or b.) would benefit greatly from something similar to Featherstitch [[2]]."
Could it be that we have with Featherstitch a general solution already that is said to be even "file system agnostic"? Honestly, I thought that something like this would make its way into the Linux code base.
Have fun
Christian
[1] Dave Chinner Re: [RFC] Tux3 for review https://lkml.org/lkml/2014/5/18/158
[2] Featherstitch http://featherstitch.cs.ucla.edu/
Quoted 15 lines · Dave Chinner
fs/fs-writeback.c void inode_wb_list_del(struct inode *ino · line 221
Quoted 13 lines · On Sun, Jun 01, 2014 at 02:41:02PM -0700, I wrote:
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +13
-0+ +/* +* Add inode to writeback dirty list with current time. +*/ +void inode_writeback_touch (struct inode *inode) +{ +struct backing_dev_info *bdi = inode->i_sb->s_bdi; +spin_lock (&bdi->wb.list_lock); + inode->dirtied_when = jiffies; +list_move (&inode->i_wb_list, &bdi->wb.b_dirty); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_touch);You should be able to use
redirty_tail()for this....
Redirty_tail nearly works, but "if (!list_empty(&wb->b_dirty))" is not correct because the inode needs to end up on the dirty list whether it was already there or not. This requirement is analogous to __mark_inode_dirty() and must tolerate similar races. At the microoptimization level, calling redirty_tail from inode_writeback_touch would be less efficient and more bulky. Another small issue is, redirty_tail does not always update the timestamp, which could trigger some bogus writeback events.
Quoted 6 lines
Hmmmm - this is using the wb dirty lists and locks, but you don't pass the wb structure to the writeback callout? That seem wrong to me - why would you bother manipulating these lists if you aren't using them to track dirty inodes in the first place?
From Tux3's point of view, the wb lists just allow fs-writeback to determine when to call
->writeback(). We agree that inode lists are a suboptimal mechanism, but that is how fs-writeback currently thinks. It would be better if our inodes never go onto wb lists in the first place, provided that fs-writeback can still drive writeback appropriately.
Perhaps fs-writeback should have an option to work without inode lists at all, and just maintain writeback scheduling statistics in the superblock or similar. That would be a big change, more on the experimental side. We would be happy to take it on after merge, hopefully for the benefit of other filesystems besides Tux3.
What we pass to ->writeback() is just a matter of taste at the moment because we currently ignore everything except &work->nr_pages. Anything sane is fine. Note that bdi_writeback is already available from bdi->wb, the "default writeback info", whatever that means. A quick tour of existing usage suggests that you can reliably obtain the wb that way, but a complete audit would be needed.
Most probably, this API will evolve as new users arrive, and also as our Tux3 usage becomes more sophisticated. For now, Tux3 just flushes everything without asking questions. Exactly how that might change in the future is hard to predict. You are in a better position to know what XFS would require in order to use this interface.
Quoted 5 lines
The first thing that
__writeback_sb_inodes()does is create a structwriteback_controlfrom thewb_writeback_work. That should be done here and passed to__writeback_sb_inodes(), which should be renamed "generic_writeback_sb_inodes()". Also, all the fields in the wbc need to be initialised correctly (i.e including range_start/end).
Good point. I will try that generic_writeback_sb_inode concept for the next iteration, which will need a day or two including regression testing.
Quoted 10 lines
Further, a writeback implementation will need to use the generic bdi list and lock structures and so we need to pass the
bdi_writeback. Similarly, if we are going to passnr_pages, we might as well pass the entire work structure.Finally, I don't like the way the
wb->list_lockis treated differently by this code. I suspect that we need to rationalise the layering of thewb->list_lockas it is currently not clear what it protects and what (nested) layers of the writeback code actually require it.
One design goal of this proposed writeback interface is to hide the wb list lock entirely from the filesystem so core writeback can evolve more easily. This is not cast in stone, but it smells like decent factoring. We can save some spinlocks by violating that factoring (as Hirofumi's original hack did) at the cost of holding a potentially busy wb lock longer, which does not seem like a good trade.
I agree that writeback list locking is murky, and fs-writeback is murky in general. We would like Tux3 to be part of the solution, not the problem.
Quoted 30 lines
What I'd like to see is this work:
struct
super_ops... = {
....
.writeback =generic_writeback_sb_inodes,
....And that means
writeback_sb_inodes()would become:static long writeback_sb_inodes(struct
super_block*sb,
structbdi_writeback*wb, structwb_writeback_work*work){ struct writeback_control wbc = { .sync_mode = work->sync_mode, .tagged_writepages = work->tagged_writepages, .for_kupdate = work->for_kupdate, .for_background = work->for_background, .for_sync = work->for_sync, .range_cyclic = work->range_cyclic, .range_start = 0, .range_end = LLONG_MAX, }; if (sb->s_op->writeback) return sb->s_op->writeback(sb, wb, work, &wbc); return generic_writeback_sb_inodes(sb, wb, work, &wbc); }
Quoted 2 lines
And the higher/lower layers deal with
wb->list_lockappropriately.
Looks good to me. As noted above, I am not sure that *work is actually required but it does no harm, so...
Regards,
Daniel
Re: [RFC][PATCH 2/2] tux3: Use writeback hook to remove duplicated core code
In reply to Dave ChinnerQuoted 4 lines · Dave Chinner
I get very worried whenever I see locks inside
inode->i_lock. In general,i_lockis supposed to be the innermost lock that is taken, and there are very few exceptions to that - the inode LRU list is one of the few.
I generally trust Hirofumi to ensure that our locking is sane, but please point out any specific issue. We are well aware of the need to get out of our critical section fast, as is apparent in tux3_clear_dirty_inode_nolock. Hogging our own i_locks would mainly hurt our own benchmarks.
For what it is worth, the proposed writeback API improves our SMP situation with respect to other filesystems by moving tux3_clear_dirty_inode_nolock outside the wb list lock.
Quoted 10 lines
I don't know what the
tuxnode->lockis, but I found this:*
inode->i_lock
*tuxnode->lock(to protect tuxnode data)
*tuxnode->dirty_inodes_lock(fori_ddc->dirty_inodes,
* Note: timestamp can be updated
* outsideinode->i_mutex)and this:
Quoted 9 lines
*
inode->i_lock
*tuxnode->lock
*sb->dirty_inodes_lockWhich indicates that you take a filesystem global lock a couple of layers underneath the VFS per-inode
i_lock. I'd suggest you want to separate the use of the vfs inode ilock from the locking heirarchy of the tux3 inode....
Our nested locks synchronize VFS state with Tux3 state, which is not optional. Alternatively, we could rely on i_lock alone, which would increase contention.
The sb->dirty_inodes_lock is held briefly, which you can see in tux3_dirty_inode and tux3_clear_dirty_inode_nolock. If it shows up in a profile we could break it up.
Regards,
Daniel
[ please line wrap at something sane like 68 columns ]
Quoted 17 lines · Daniel Phillips
Quoted 15 lines · Dave Chinner
fs/fs-writeback.c void inode_wb_list_del(struct inode *ino · line 221
Quoted 13 lines · On Sun, Jun 01, 2014 at 02:41:02PM -0700, I wrote:
fs/fs-writeback.c renamed from fs/fs-writeback.c~core-writeback +13
-0+ +/* +* Add inode to writeback dirty list with current time. +*/ +void inode_writeback_touch (struct inode *inode) +{ +struct backing_dev_info *bdi = inode->i_sb->s_bdi; +spin_lock (&bdi->wb.list_lock); + inode->dirtied_when = jiffies; +list_move (&inode->i_wb_list, &bdi->wb.b_dirty); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_touch);You should be able to use
redirty_tail()for this....
Quoted 3 lines
Redirty_tailnearly works, but "if (!list_empty(&wb->b_dirty))" is not correct because the inode needs to end up on the dirty list whether it was already there or not.
redirty_tail() always moves the inode to the end of the dirty list.
Quoted 6 lines
This requirement is analogous to
__mark_inode_dirty()and must tolerate similar races. At the microoptimization level, callingredirty_tailfrominode_writeback_touchwould be less efficient and more bulky. Another small issue is,redirty_taildoes not always update the timestamp, which could trigger some bogus writeback events.
redirty_tail does not update the timestamp when it doesn't need to change. If it needs to be changed because the current value would violate the time ordering requirements of the list, it rewrites it.
So there is essentially no functional difference between the new function and redirty_tail....
Quoted 11 lines
Quoted 4 lines
Hmmmm - this is using the wb dirty lists and locks, but you don't pass the wb structure to the writeback callout? That seem wrong to me - why would you bother manipulating these lists if you aren't using them to track dirty inodes in the first place?
From Tux3's point of view, the wb lists just allow fs-writeback to determine when to call ->writeback(). We agree that inode lists are a suboptimal mechanism, but that is how fs-writeback currently thinks. It would be better if our inodes never go onto wb lists in the first place, provided that fs-writeback can still drive writeback appropriately.
It can't, and definitely not with the callout you added.
However, we already avoid the VFS writeback lists for certain filesystems for pure metadata. e.g. XFS does not use the VFS dirty inode lists for inode metadata changes. They get tracked internally by the transaction subsystem which does it's own writeback according to the requirements of journal space availability.
This is done simply by not calling mark_inode_dirty() on any metadata only change. If we want to do the same for data, then we'd simply not call mark_inode_dirty() in the data IO path. That requires a custom ->set_page_dirty method to be provided by the filesystem that didn't call
__mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
and instead did it's own thing.
So the per-superblock dirty tracking is something we can do right now, and some filesystems do it for metadata. The missing piece for data is writeback infrastructure capable of deferring to superblocks for writeback rather than BDIs....
Quoted 5 lines
Perhaps fs-writeback should have an option to work without inode lists at all, and just maintain writeback scheduling statistics in the superblock or similar. That would be a big change, more on the experimental side. We would be happy to take it on after merge, hopefully for the benefit of other filesystems besides Tux3.
Well, I don't see it that way. If you have a requirement to be able to track dirty inodes internally, then lets move to that implement that infrastructure rather than hacking around with callouts that only have a limited shelf-life.
Quoted 8 lines
What we pass to ->writeback() is just a matter of taste at the moment because we currently ignore everything except
&work->nr_pages. Anything sane is fine. Note thatbdi_writebackis already available frombdi->wb, the "default writeback info", whatever that means. A quick tour of existing usage suggests that you can reliably obtain the wb that way, but a complete audit would be needed.
Quoted 6 lines
Most probably, this API will evolve as new users arrive, and also as our Tux3 usage becomes more sophisticated. For now, Tux3 just flushes everything without asking questions. Exactly how that might change in the future is hard to predict. You are in a better position to know what XFS would require in order to use this interface.
XFS already has everything it needs internally to track dirty inodes. In fact, we used to do data writeback from within XFS and we slowly removed it as the generic writeback code was improved made the XFS code redundant.
That said, parallelising writeback so we can support hundreds of GB/s of delayed allocation based writeback is something we eventually need to do, and that pretty much means we need to bring dirty data inode tracking back into XFS.
So what we really need is writeback infrastructure that is:
a) independent of the dirty inode lists
b) implements background, periodic and immediate writeback
c) can be driven by BDI or superblock
IOWs, the abstraction we need for this writeback callout is not at the writeback_sb_inodes() layer, it's above the dirty inode list queuing layer. IOWs, the callout needs to be closer to the wb_do_writeback() layer which drives all the writeback work including periodic and background writeback, and the interactions between foreground and background work that wb_writeback() uses needs to be turned into a bunch of helpers...
Quoted 9 lines
Quoted 5 lines
Finally, I don't like the way the
wb->list_lockis treated differently by this code. I suspect that we need to rationalise the layering of thewb->list_lockas it is currently not clear what it protects and what (nested) layers of the writeback code actually require it.One design goal of this proposed writeback interface is to hide the wb list lock entirely from the filesystem so core writeback can evolve more easily.
Yes, I realise that. Hence my point that the bleeding of it across layers of writeback infrstructure is sub-optimal.
Quoted 5 lines
This is not cast in stone, but it smells
like decent factoring. We can save some spinlocks by violating that factoring (as Hirofumi's original hack did) at the cost of holding a potentially busy wb lock longer, which does not seem like a good trade.
If we abstract at a higher layer, the wb lock protects just the work queuing and dispatch, and everything else can be done by the superblock callout. If the superblock callout is missing, then we simply fall down to the existing wb_writeback() code that retakes the lock and does it's work....
Let's get the layering and abstraction in the right place the first time, rather having to revist this in the not-to-distant future.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 13 lines · Christian Stroetmann
When I followed the advice of Dave Chinner:
"We're not going to merge that page forking stuff (like you were told at LSF 2013 more than a year ago:
http://lwn.net/Articles/548091/) without rigorous design review and a demonstration of the solutions to all the hard corner cases it has"
given in his e-mail related with the presentation of the latest version of the Tux3 file system (see [1]) and read the linked article, I found in the second comments:
"Parts of this almost sound like it either a.) overlaps with or b.) would benefit greatly from something similar to Featherstitch [[2]]."
Quoted 4 lines
Could it be that we have with Featherstitch a general solution already that is said to be even "file system agnostic"? Honestly, I thought that something like this would make its way into the Linux code base.
Here's what I said about the last proposal (a few months ago) for integrating featherstitch into the kernel:
http://www.spinics.net/lists/linux-fsdevel/msg72799.html
It's not a viable solution.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 21 lines · Dave Chinner
Quoted 13 lines · Christian Stroetmann
When I followed the advice of Dave Chinner:
"We're not going to merge that page forking stuff (like you were told at LSF 2013 more than a year ago:
http://lwn.net/Articles/548091/) without rigorous design review and a demonstration of the solutions to all the hard corner cases it has"
given in his e-mail related with the presentation of the latest version of the Tux3 file system (see [1]) and read the linked article, I found in the second comments:
"Parts of this almost sound like it either a.) overlaps with or b.) would benefit greatly from something similar to Featherstitch [[2]]."Quoted 4 lines
Could it be that we have with Featherstitch a general solution already that is said to be even "file system agnostic"? Honestly, I thought that something like this would make its way into the Linux code base.
Here's what I said about the last proposal (a few months ago) for integrating featherstitch into the kernel:
Quoted 7 lines
http://www.spinics.net/lists/linux-fsdevel/msg72799.html
It's not a viable solution.
Cheers,
Dave.
How annoying, I did not remember your e-mail of the referenced thread "[Lsf-pc] [LSF/MM TOPIC] atomic block device" despite I saved it on local disk. Thanks a lot for the reminder.
I also directly saw the problem with the research prototype Featherstitch, specifically the point "All the filesystem modules it has are built into the featherstitch kernel module, and called through a VFS shim layer". But it is just a prototype and its concept of abstraction has not to be copied 1:1 into the Linux code base.
In general, I do not believe that the complexity problems of soft updates, atomic writes, and related techniques can be solved by hand/manually. So my suggestion is to automatically handle the complexity problem of e.g. dependancies in a way that is comparable to a(n on-the-fly) file-system compiler so to say that works on a very large dependancy graph (having several billions of graph vertices actually). And at this point an abstraction like it is given with Featherstitch helps to feed and control this special FS compiler.
Actually, I have to follow the discussion further on the one hand and go deeper into the highly complex problem space on the other hand.
With all the best
Christian
Hi Dave,
Quoted 14 lines · Dave Chinner
Quoted 4 lines · Daniel Phillips
Redirty_tailnearly works, but "if (!list_empty(&wb->b_dirty))" is not correct because the inode needs to end up on the dirty list whether it was already there or not.
redirty_tail()always moves the inode to the end of the dirty list.Quoted 6 lines
This requirement is analogous to
__mark_inode_dirty()and must tolerate similar races. At the microoptimization level, callingredirty_tailfrominode_writeback_touchwould be less efficient and more bulky. Another small issue is,redirty_taildoes not always update the timestamp, which could trigger some bogus writeback events.
Quoted 6 lines
redirty_taildoes not update the timestamp when it doesn't need to change. If it needs to be changed because the current value would violate the time ordering requirements of the list, it rewrites it.So there is essentially no functional difference between the new function and
redirty_tail....
Hirofumi, would you care to comment?
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Hmmmm - this is using the wb dirty lists and locks, but you don't pass the wb structure to the writeback callout? That seem wrong to me - why would you bother manipulating these lists if you aren't using them to track dirty inodes in the first place?
Quoted 7 lines
Quoted 6 lines
From Tux3's point of view, the wb lists just allow fs-writeback to determine when to call ->writeback(). We agree that inode lists are a suboptimal mechanism, but that is how fs-writeback currently thinks. It would be better if our inodes never go onto wb lists in the first place, provided that fs-writeback can still drive writeback appropriately.
It can't, and definitely not with the callout you added.
Obviously, fs-writeback does not get to choose inodes or specific pages with our proposal (which is the whole point) but it still decides when to call Tux3 vs some other filesystem on the same device, and is still able to indicate how much data it thinks should be written out. Even though we ignore the latter suggestion and always flush everything, we appreciate the timing of those callbacks very much, because they give us exactly the pressure sensitive batching behavior we want.
Quoted 12 lines
However, we already avoid the VFS writeback lists for certain filesystems for pure metadata. e.g. XFS does not use the VFS dirty inode lists for inode metadata changes. They get tracked internally by the transaction subsystem which does it's own writeback according to the requirements of journal space availability.
This is done simply by not calling
mark_inode_dirty()on any metadata only change. If we want to do the same for data, then we'd simply not callmark_inode_dirty()in the data IO path. That requires a custom ->set_page_dirty method to be provided by the filesystem that didn't call
Quoted 8 lines
__mark_inode_dirty(mapping->host, I_DIRTY_PAGES);and instead did it's own thing.
So the per-superblock dirty tracking is something we can do right now, and some filesystems do it for metadata. The missing piece for data is writeback infrastructure capable of deferring to superblocks for writeback rather than BDIs....
We agree that fs-writeback inode lists are broken for anything more sophisticated than Ext2. An advantage of the patch under consideration is that it still lets fs-writeback mostly work the way it has worked for the last few years, except for not allowing it to pick specific inodes and data pages for writeout. As far as I can see, it still balances writeout between different filesystems on the same block device pretty well.
Quoted 9 lines
Quoted 5 lines
Perhaps fs-writeback should have an option to work without inode lists at all, and just maintain writeback scheduling statistics in the superblock or similar. That would be a big change, more on the experimental side. We would be happy to take it on after merge, hopefully for the benefit of other filesystems besides Tux3.
Well, I don't see it that way. If you have a requirement to be able to track dirty inodes internally, then lets move to that implement that infrastructure rather than hacking around with callouts that only have a limited shelf-life.
What you call shelf-life, I call iteration. Small changes are beautiful. Apologies for the rhetoric, content is below.
Quoted 16 lines
XFS already has everything it needs internally to track dirty inodes. In fact, we used to do data writeback from within XFS and we slowly removed it as the generic writeback code was improved made the XFS code redundant.
That said, parallelising writeback so we can support hundreds of GB/s of delayed allocation based writeback is something we eventually need to do, and that pretty much means we need to bring dirty data inode tracking back into XFS.
So what we really need is writeback infrastructure that is:
a) independent of the dirty inode lists
b) implements background, periodic and immediate writeback
c) can be driven by BDI or superblock
Quoted 7 lines
IOWs, the abstraction we need for this writeback callout is not at the
writeback_sb_inodes()layer, it's above the dirty inode list queuing layer. IOWs, the callout needs to be closer to thewb_do_writeback()layer which drives all the writeback work including periodic and background writeback, and the interactions between foreground and background work thatwb_writeback()uses needs to be turned into a bunch of helpers...
I agree, and that is what Tux3 really wants. I just wonder about the wisdom of embarking on a big change to fs-writeback when a small change will do. How will fs-writeback balancing between different filesystems on the same device work? What will those helpers look like? How long will it take to prove the new scheme stable? How can we prove that no existing fs-writeback clients will regress?
What about a sporting proposal: you post a patch that trumps ours in elegance, that you could in theory use for XFS. We verify that it works for Tux3 at least as well as the current patch. We also test it for you.
Quoted 5 lines
Quoted 5 lines
This is not cast in stone, but it smells
like decent factoring. We can save some spinlocks by violating that factoring (as Hirofumi's original hack did) at the cost of holding a potentially busy wb lock longer, which does not seem like a good trade.
Quoted 9 lines
If we abstract at a higher layer, the wb lock protects just the work queuing and dispatch, and everything else can be done by the superblock callout. If the superblock callout is missing, then we simply fall down to the existing
wb_writeback()code that retakes the lock and does it's work....Let's get the layering and abstraction in the right place the first time, rather having to revist this in the not-to-distant future.
That would be ideal, but incremental also has its attractions. In that vein, here is a rewrite along the lines you suggested yesterday. The only change to your code is, the writeback lock is dropped before calling ->writeback(). I tried doing it the other way (in the fs) but it was just too ugly. As a result, generic_writeback_sb_inodes is called under the wb list lock, but ->writeback drops it. An oddity that would obviously go away with a better abstraction along the lines you suggest.
Regards,
Daniel
---
Quoted 1 line
From
8672ba5915e94a833321435d979615ac7059c789Mon Sep 17 00:00:00 2001
From: Daniel Phillips <[email protected]>
Date: Mon, 2 Jun 2014 21:40:19 -0700
Subject: [PATCH] Add a super operation for writeback
Add a "writeback" super operation to be called in the form:
progress = s_op->writeback(sb, wb, work, wbc);
Where sb is (struct super_block *), wb is (struct bdi_writeback *), work is (struct wb_writeback_work *), and wbc is (struct writeback_control *).
The filesystem is expected to flush some inodes to disk and return progress of at least 1, or if no inodes are flushed, return progress of zero. The filesystem should try to flush at least the number of pages specified in work->nr_pages, or if that is not possible, return approximately the number of pages that were not flushed in work->nr_pages.
Within the ->writeback callback, the filesystem should call inode_writeback_done(inode) for each inode flushed (and therefore set clean) or inode_writeback_touch(inode) for any inode that will be retained dirty in cache.
--- fs/fs-writeback.c | 86 +++++++++++++++++------------------------------ include/linux/fs.h | 8 +++-- include/linux/writeback.h | 19 +++++++++++ 3 files changed, 56 insertions(+), 57 deletions(-)
fs/fs-writeback.c +31 -55
@@ -34,25 +34,6 @@*/ #define MIN_WRITEBACK_PAGES (4096UL >> (PAGE_CACHE_SHIFT - 10)) -/* -* Passed into wb_writeback(), essentially a subset of writeback_control -*/ -struct wb_writeback_work { -long nr_pages; -struct super_block *sb; -unsigned long *older_than_this; -enum writeback_sync_modes sync_mode; -unsigned int tagged_writepages:1 ; -unsigned int for_kupdate:1 ; -unsigned int range_cyclic:1 ; -unsigned int for_background:1 ; -unsigned int for_sync:1 ;/* sync(2) WB_SYNC_ALL writeback */ -enum wb_reason reason;/* why was writeback initiated? */ - -struct list_head list;/* pending work list */ -struct completion *done;/* set if the caller waits */ -}; -/** * writeback_in_progress - determine whether there is writeback in progress * @bdi: the device's backing_dev_info structure.
@@ -622,20 +603,12 @@ static long writeback_chunk_size(struct backing_dev_info *bdi,* * Return the number of pages and/or inodes written. */ -static long __writeback_sb_inodes (struct super_block *sb, -struct bdi_writeback *wb, -struct wb_writeback_work *work) +long generic_writeback_sb_inodes ( +struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work, +struct writeback_control *wbc) { -struct writeback_control wbc = { - .sync_mode = work->sync_mode, - .tagged_writepages = work->tagged_writepages, - .for_kupdate = work->for_kupdate, - .for_background = work->for_background, - .for_sync = work->for_sync, - .range_cyclic = work->range_cyclic, - .range_start =0 , - .range_end =LLONG_MAX , - };unsigned long start_time = jiffies;long write_chunk;long wrote =0 ;/* count both pages and inodes */
@@ -673,7 +646,7 @@ static long __writeback_sb_inodes(struct super_block *sb,redirty_tail (inode, wb);continue ; } -if ((inode->i_state &I_SYNC ) && wbc.sync_mode !=WB_SYNC_ALL ) { +if ((inode->i_state &I_SYNC ) && wbc->sync_mode !=WB_SYNC_ALL ) {/* * If this inode is locked for writeback and we are not * doing writeback-for-data-integrity, move it to
@@ -706,22 +679,22 @@ static long __writeback_sb_inodes(struct super_block *sb,spin_unlock (&inode->i_lock); write_chunk =writeback_chunk_size (wb->bdi, work); - wbc.nr_to_write = write_chunk; - wbc.pages_skipped =0 ; + wbc->nr_to_write = write_chunk; + wbc->pages_skipped =0 ;/* * We use I_SYNC to pin the inode in memory. While it is set * evict_inode() will wait so the inode cannot be freed. */ -__writeback_single_inode (inode, &wbc); +__writeback_single_inode (inode, wbc); - work->nr_pages -= write_chunk - wbc.nr_to_write; - wrote += write_chunk - wbc.nr_to_write; + work->nr_pages -= write_chunk - wbc->nr_to_write; + wrote += write_chunk - wbc->nr_to_write;spin_lock (&wb->list_lock);spin_lock (&inode->i_lock);if (!(inode->i_state &I_DIRTY )) wrote++; -requeue_inode (inode, wb, &wbc); +requeue_inode (inode, wb, wbc);inode_sync_complete (inode);spin_unlock (&inode->i_lock);cond_resched_lock (&wb->list_lock);
@@ -739,28 +712,31 @@ static long __writeback_sb_inodes(struct super_block *sb,return wrote; } -static long writeback_sb_inodes (struct super_block *sb, -struct bdi_writeback *wb, -struct wb_writeback_work *work) +static long writeback_sb_inodes ( +struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) { -if (sb->s_op->writeback) { -struct writeback_control wbc = { - .sync_mode = work->sync_mode, - .tagged_writepages = work->tagged_writepages, - .for_kupdate = work->for_kupdate, - .for_background = work->for_background, - .for_sync = work->for_sync, - .range_cyclic = work->range_cyclic, - }; -long ret; +struct writeback_control wbc = { + .sync_mode = work->sync_mode, + .tagged_writepages = work->tagged_writepages, + .for_kupdate = work->for_kupdate, + .for_background = work->for_background, + .for_sync = work->for_sync, + .range_cyclic = work->range_cyclic, + .range_start =0 , + .range_end =LLONG_MAX , + }; +if (sb->s_op->writeback) { +int err;spin_unlock (&wb->list_lock); - ret = sb->s_op->writeback (sb, &wbc, &work->nr_pages); + err = sb->s_op->writeback (sb, wb, work, &wbc);spin_lock (&wb->list_lock); -return ret; +return err; } -return __writeback_sb_inodes (sb, wb, work); +return generic_writeback_sb_inodes (sb, wb, work, &wbc); } static long __writeback_inodes_wb(struct bdi_writeback *wb,
include/linux/fs.h +6 -2
@@ -295,6 +295,8 @@ enum positive_aop_returns {struct page ;struct address_space ;struct writeback_control ; +struct wb_writeback_work ; +struct bdi_writeback ;/* * "descriptor" for what we're up to with a read.
@@ -1542,8 +1544,10 @@ struct super_operations {int (*statfs) (struct dentry *,struct kstatfs *);int (*remount_fs) (struct super_block *,int *,char *);void (*umount_begin) (struct super_block *); -long (*writeback)(struct super_block *super,struct writeback_control *wbc,long *nr_pages); - +long (*writeback)(struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work, +struct writeback_control *wbc);int (*show_options)(struct seq_file *,struct dentry *);int (*show_devname)(struct seq_file *,struct dentry *);int (*show_path)(struct seq_file *,struct dentry *);
include/linux/writeback.h +19 -0
@@ -87,6 +87,25 @@ struct writeback_control { };/* +* Passed into wb_writeback(), essentially a subset of writeback_control +*/ +struct wb_writeback_work { +long nr_pages; +struct super_block *sb; +unsigned long *older_than_this; +enum writeback_sync_modes sync_mode; +unsigned int tagged_writepages:1 ; +unsigned int for_kupdate:1 ; +unsigned int range_cyclic:1 ; +unsigned int for_background:1 ; +unsigned int for_sync:1 ;/* sync(2) WB_SYNC_ALL writeback */ +enum wb_reason reason;/* why was writeback initiated? */ + +struct list_head list;/* pending work list */ +struct completion *done;/* set if the caller waits */ +}; + +/* * fs/fs-writeback.c */ struct bdi_writeback ;
Signature · 1 line
1.9.1
Hi Dave,
Here is a non-incremental patch. This implements your suggestion from yesterday, except that the wb list lock is dropped before calling ->writeback().
Regards,
Daniel
Quoted 1 line
From
d030d328757b160b39b252e82811a94843513cfcMon Sep 17 00:00:00 2001
From: Daniel Phillips <[email protected]>
Date: Tue, 3 Jun 2014 00:19:11 -0700
Subject: [PATCH] Add a super operation for writeback
Add a "writeback" super operation to be called in the form:
progress = s_op->writeback(sb, wb, work, wbc);
Where sb is (struct super_block *), wb is (struct bdi_writeback *), work is (struct wb_writeback_work *), and wbc is (struct writeback_control *).
The filesystem is expected to flush some inodes to disk and return progress of at least 1, or if no inodes are flushed, return progress of zero. The filesystem should try to flush at least the number of pages specified in work->nr_pages, or if that is not possible, return approximately the number of pages that were not flushed in work->nr_pages.
Within the ->writeback callback, the filesystem should call inode_writeback_done(inode) for each inode flushed and therefore set clean) or inode_writeback_touch(inode) for any inode that will be retained dirty in cache.
--- fs/fs-writeback.c | 107 +++++++++++++++++++++++++++++----------------- include/linux/fs.h | 9 +++- include/linux/writeback.h | 19 ++++++++ 3 files changed, 95 insertions(+), 40 deletions(-)
fs/fs-writeback.c +68 -39
@@ -34,25 +34,6 @@*/ #define MIN_WRITEBACK_PAGES (4096UL >> (PAGE_CACHE_SHIFT - 10)) -/* -* Passed into wb_writeback(), essentially a subset of writeback_control -*/ -struct wb_writeback_work { -long nr_pages; -struct super_block *sb; -unsigned long *older_than_this; -enum writeback_sync_modes sync_mode; -unsigned int tagged_writepages:1 ; -unsigned int for_kupdate:1 ; -unsigned int range_cyclic:1 ; -unsigned int for_background:1 ; -unsigned int for_sync:1 ;/* sync(2) WB_SYNC_ALL writeback */ -enum wb_reason reason;/* why was writeback initiated? */ - -struct list_head list;/* pending work list */ -struct completion *done;/* set if the caller waits */ -}; -/** * writeback_in_progress - determine whether there is writeback in progress * @bdi: the device's backing_dev_info structure.
@@ -192,6 +173,35 @@ void inode_wb_list_del(struct inode *inode) }/* +* Remove inode from writeback list if clean. +*/ +void inode_writeback_done (struct inode *inode) +{ +struct backing_dev_info *bdi =inode_to_bdi (inode); + +spin_lock (&bdi->wb.list_lock); +spin_lock (&inode->i_lock); +if (!(inode->i_state &I_DIRTY )) +list_del_init (&inode->i_wb_list); +spin_unlock (&inode->i_lock); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_done); + +/* +* Add inode to writeback dirty list with current time. +*/ +void inode_writeback_touch (struct inode *inode) +{ +struct backing_dev_info *bdi = inode->i_sb->s_bdi; +spin_lock (&bdi->wb.list_lock); + inode->dirtied_when = jiffies; +list_move (&inode->i_wb_list, &bdi->wb.b_dirty); +spin_unlock (&bdi->wb.list_lock); +} +EXPORT_SYMBOL_GPL (inode_writeback_touch); + +/* * Redirty an inode: set its when-it-was dirtied timestamp and move it to the * furthest end of its superblock's dirty-inode list. *
@@ -593,20 +603,12 @@ static long writeback_chunk_size(struct backing_dev_info *bdi,* * Return the number of pages and/or inodes written. */ -static long writeback_sb_inodes (struct super_block *sb, -struct bdi_writeback *wb, -struct wb_writeback_work *work) +long generic_writeback_sb_inodes ( +struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work, +struct writeback_control *wbc) { -struct writeback_control wbc = { - .sync_mode = work->sync_mode, - .tagged_writepages = work->tagged_writepages, - .for_kupdate = work->for_kupdate, - .for_background = work->for_background, - .for_sync = work->for_sync, - .range_cyclic = work->range_cyclic, - .range_start =0 , - .range_end =LLONG_MAX , - };unsigned long start_time = jiffies;long write_chunk;long wrote =0 ;/* count both pages and inodes */
@@ -644,7 +646,7 @@ static long writeback_sb_inodes(struct super_block *sb,redirty_tail (inode, wb);continue ; } -if ((inode->i_state &I_SYNC ) && wbc.sync_mode !=WB_SYNC_ALL ) { +if ((inode->i_state &I_SYNC ) && wbc->sync_mode !=WB_SYNC_ALL ) {/* * If this inode is locked for writeback and we are not * doing writeback-for-data-integrity, move it to
@@ -677,22 +679,22 @@ static long writeback_sb_inodes(struct super_block *sb,spin_unlock (&inode->i_lock); write_chunk =writeback_chunk_size (wb->bdi, work); - wbc.nr_to_write = write_chunk; - wbc.pages_skipped =0 ; + wbc->nr_to_write = write_chunk; + wbc->pages_skipped =0 ;/* * We use I_SYNC to pin the inode in memory. While it is set * evict_inode() will wait so the inode cannot be freed. */ -__writeback_single_inode (inode, &wbc); +__writeback_single_inode (inode, wbc); - work->nr_pages -= write_chunk - wbc.nr_to_write; - wrote += write_chunk - wbc.nr_to_write; + work->nr_pages -= write_chunk - wbc->nr_to_write; + wrote += write_chunk - wbc->nr_to_write;spin_lock (&wb->list_lock);spin_lock (&inode->i_lock);if (!(inode->i_state &I_DIRTY )) wrote++; -requeue_inode (inode, wb, &wbc); +requeue_inode (inode, wb, wbc);inode_sync_complete (inode);spin_unlock (&inode->i_lock);cond_resched_lock (&wb->list_lock);
@@ -710,6 +712,33 @@ static long writeback_sb_inodes(struct super_block *sb,return wrote; } +static long writeback_sb_inodes ( +struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work) +{ +struct writeback_control wbc = { + .sync_mode = work->sync_mode, + .tagged_writepages = work->tagged_writepages, + .for_kupdate = work->for_kupdate, + .for_background = work->for_background, + .for_sync = work->for_sync, + .range_cyclic = work->range_cyclic, + .range_start =0 , + .range_end =LLONG_MAX , + }; + +if (sb->s_op->writeback) { +long ret; +spin_unlock (&wb->list_lock); + ret = sb->s_op->writeback (sb, wb, work, &wbc); +spin_lock (&wb->list_lock); +return ret; + } + +return generic_writeback_sb_inodes (sb, wb, work, &wbc); +} +static long __writeback_inodes_wb (struct bdi_writeback *wb,struct wb_writeback_work *work) {
include/linux/fs.h +8 -1
@@ -295,6 +295,8 @@ enum positive_aop_returns {struct page ;struct address_space ;struct writeback_control ; +struct wb_writeback_work ; +struct bdi_writeback ;/* * "descriptor" for what we're up to with a read.
@@ -1542,7 +1544,10 @@ struct super_operations {int (*statfs) (struct dentry *,struct kstatfs *);int (*remount_fs) (struct super_block *,int *,char *);void (*umount_begin) (struct super_block *); - +long (*writeback)(struct super_block *sb, +struct bdi_writeback *wb, +struct wb_writeback_work *work, +struct writeback_control *wbc);int (*show_options)(struct seq_file *,struct dentry *);int (*show_devname)(struct seq_file *,struct dentry *);int (*show_path)(struct seq_file *,struct dentry *);
@@ -1739,6 +1744,8 @@ static inline void file_accessed(struct file *file)int sync_inode (struct inode *inode,struct writeback_control *wbc);int sync_inode_metadata (struct inode *inode,int wait); +void inode_writeback_done (struct inode *inode); +void inode_writeback_touch (struct inode *inode);struct file_system_type {const char *name;
include/linux/writeback.h +19 -0
@@ -87,6 +87,25 @@ struct writeback_control { };/* +* Passed into wb_writeback(), essentially a subset of writeback_control +*/ +struct wb_writeback_work { +long nr_pages; +struct super_block *sb; +unsigned long *older_than_this; +enum writeback_sync_modes sync_mode; +unsigned int tagged_writepages:1 ; +unsigned int for_kupdate:1 ; +unsigned int range_cyclic:1 ; +unsigned int for_background:1 ; +unsigned int for_sync:1 ;/* sync(2) WB_SYNC_ALL writeback */ +enum wb_reason reason;/* why was writeback initiated? */ + +struct list_head list;/* pending work list */ +struct completion *done;/* set if the caller waits */ +}; + +/* * fs/fs-writeback.c */ struct bdi_writeback ;
Quoted 9 lines · Daniel Phillips
Hi Dave,
Quoted 7 lines · Dave Chinner
Quoted 4 lines · Daniel Phillips
Redirty_tailnearly works, but "if (!list_empty(&wb->b_dirty))" is not correct because the inode needs to end up on the dirty list whether it was already there or not.
redirty_tail()always moves the inode to the end of the dirty list.
It doesn't move inode to end of the dirty if wb.b_dirty is empty (I.e. it can move from wb.b_io to wb.b_dirty too).
BTW, this is called for like mark inode dirty process, not always writeback path.
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
This requirement is analogous to
__mark_inode_dirty()and must tolerate similar races. At the microoptimization level, callingredirty_tailfrominode_writeback_touchwould be less efficient and more bulky. Another small issue is,redirty_taildoes not always update the timestamp, which could trigger some bogus writeback events.
Quoted 8 lines
Quoted 6 lines
redirty_taildoes not update the timestamp when it doesn't need to change. If it needs to be changed because the current value would violate the time ordering requirements of the list, it rewrites it.So there is essentially no functional difference between the new function and
redirty_tail....Hirofumi, would you care to comment?
It has difference.
Say, tail->dirtied_when == 1, inode->dirtied_when == 2, and now == 30 (tail->dirtied_when is expired at 31 with default config). In this case, redirty_tail() doesn't update ->dirtied_when.
And if inode->dirtied_when is not updated to 30, expire time has difference. I.e. 32 vs 60.
Thanks.
Signature · 1 line
OGAWA Hirofumi <[email protected]>
Quoted 15 lines · Daniel Phillips
Hi Dave,
Quoted 5 lines · Dave Chinner
Quoted 4 lines · Daniel Phillips
Quoted 4 lines
Hmmmm - this is using the wb dirty lists and locks, but you don't pass the wb structure to the writeback callout? That seem wrong to me - why would you bother manipulating these lists if you aren't using them to track dirty inodes in the first place?
Quoted 7 lines
Quoted 6 lines
From Tux3's point of view, the wb lists just allow fs-writeback to determine when to call ->writeback(). We agree that inode lists are a suboptimal mechanism, but that is how fs-writeback currently thinks. It would be better if our inodes never go onto wb lists in the first place, provided that fs-writeback can still drive writeback appropriately.
It can't, and definitely not with the callout you added.
Quoted 7 lines
Obviously, fs-writeback does not get to choose inodes or specific pages with our proposal (which is the whole point) but it still decides when to call Tux3 vs some other filesystem on the same device, and is still able to indicate how much data it thinks should be written out. Even though we ignore the latter suggestion and always flush everything, we appreciate the timing of those callbacks very much, because they give us exactly the pressure sensitive batching behavior we want.
Which, again, points out that you want per-superblock flushing, not per-bdi flushing which is what the current writeback code does.
Quoted 16 lines
Quoted 16 lines
However, we already avoid the VFS writeback lists for certain filesystems for pure metadata. e.g. XFS does not use the VFS dirty inode lists for inode metadata changes. They get tracked internally by the transaction subsystem which does it's own writeback according to the requirements of journal space availability.
This is done simply by not calling
mark_inode_dirty()on any metadata only change. If we want to do the same for data, then we'd simply not callmark_inode_dirty()in the data IO path. That requires a custom ->set_page_dirty method to be provided by the filesystem that didn't call__mark_inode_dirty(mapping->host, I_DIRTY_PAGES);and instead did it's own thing.
Quoted 7 lines
Quoted 4 lines
So the per-superblock dirty tracking is something we can do right now, and some filesystems do it for metadata. The missing piece for data is writeback infrastructure capable of deferring to superblocks for writeback rather than BDIs....
We agree that fs-writeback inode lists are broken for anything more sophisticated than Ext2.
No, I did not say that.
I said XFS does something different for metadata changes because it has different flushing constraints and requirements than the generic code provides. That does not make the generic code broken.
Quoted 6 lines
An advantage of the patch under
consideration is that it still lets fs-writeback mostly work the way it has worked for the last few years, except for not allowing it to pick specific inodes and data pages for writeout. As far as I can see, it still balances writeout between different filesystems on the same block device pretty well.
Not really. If there are 3 superblocks on a BDI, and the dirty inode list iterates between 2 of them with lots of dirty inodes, it can starve writeback from the third until one of it's dirty inodes pops to the head of the b_io list. So it's inherently unfair from that perspective.
Changing the high level flushing to be per-superblock rather than per-BDI would enable us to control that behaviour and be much fairer to all the superblocks on a given BDI. That said, I don't really care that much about this case...
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Perhaps fs-writeback should have an option to work without inode lists at all, and just maintain writeback scheduling statistics in the superblock or similar. That would be a big change, more on the experimental side. We would be happy to take it on after merge, hopefully for the benefit of other filesystems besides Tux3.
Quoted 7 lines
Quoted 4 lines
Well, I don't see it that way. If you have a requirement to be able to track dirty inodes internally, then lets move to that implement that infrastructure rather than hacking around with callouts that only have a limited shelf-life.
What you call shelf-life, I call iteration. Small changes are beautiful. Apologies for the rhetoric, content is below.
Ok, let me use stronger language: implementing something at the wrong layer only to have to redo it at the correct layer a short while later is wasted effort. Do it right the first time.
I do beleive I've explained this to you at least once before, so lets not have to go over this again.
Quoted 24 lines
Quoted 16 lines
XFS already has everything it needs internally to track dirty inodes. In fact, we used to do data writeback from within XFS and we slowly removed it as the generic writeback code was improved made the XFS code redundant.
That said, parallelising writeback so we can support hundreds of GB/s of delayed allocation based writeback is something we eventually need to do, and that pretty much means we need to bring dirty data inode tracking back into XFS.
So what we really need is writeback infrastructure that is:
a) independent of the dirty inode lists
b) implements background, periodic and immediate writeback
c) can be driven by BDI or superblockQuoted 7 lines
IOWs, the abstraction we need for this writeback callout is not at the
writeback_sb_inodes()layer, it's above the dirty inode list queuing layer. IOWs, the callout needs to be closer to thewb_do_writeback()layer which drives all the writeback work including periodic and background writeback, and the interactions between foreground and background work thatwb_writeback()uses needs to be turned into a bunch of helpers...
Quoted 6 lines
I agree, and that is what Tux3 really wants. I just wonder about the wisdom of embarking on a big change to fs-writeback when a small change will do. How will fs-writeback balancing between different filesystems on the same device work? What will those helpers look like? How long will it take to prove the new scheme stable? How can we prove that no existing fs-writeback clients will regress?
If the filesystem doesn't provide a callout, then the existing writeback code runs. So for everything but tux3, there is no change in behaviour. For tux3, you don't have to care about such interactions while you are developing it. IOWs, we don't have to solve every problem immediately but we get the hooks in the right place and clean up and sanitise the locking and writeback infrastructure at the same time.
Quoted 4 lines
What about a sporting proposal: you post a patch that trumps ours in elegance, that you could in theory use for XFS. We verify that it works for Tux3 at least as well as the current patch. We also test it for you.
Sorry, the world doesn't work that way. I have other work to do, and the onus on getting new code into the tree is on the people who need it, not the people who are reviewing the code.
Quoted 11 lines
Quoted 11 lines
Quoted 5 lines
This is not cast in stone, but it smells
like decent factoring. We can save some spinlocks by violating that factoring (as Hirofumi's original hack did) at the cost of holding a potentially busy wb lock longer, which does not seem like a good trade.If we abstract at a higher layer, the wb lock protects just the work queuing and dispatch, and everything else can be done by the superblock callout. If the superblock callout is missing, then we simply fall down to the existing
wb_writeback()code that retakes the lock and does it's work....
Quoted 6 lines
Quoted 3 lines
Let's get the layering and abstraction in the right place the first time, rather having to revist this in the not-to-distant future.
That would be ideal, but incremental also has its attractions. In that vein, here is a rewrite along the lines you suggested yesterday.
Sure, but that assumes the hook is in the correct place, which with the information you gave today tells me it isn't. So polishing the original patch isn't the solution. Driving the hook up the stack seems to me like the better approach.
fs/fs-writeback.c line 43
Quoted 6 lines
- * Passed into
wb_writeback(), essentially a subset ofwriteback_control
- */fs/fs-writeback.c +0
-4-struct wb_writeback_work { -long nr_pages; -struct super_block *sb; -unsigned long *older_than_this;
Something is mangling tabs to 4 spaces in your patches.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 6 lines · OGAWA Hirofumi
Quoted 4 lines · Daniel Phillips
Hi Dave,
Quoted 2 lines · Dave Chinner
Quoted 1 line · Daniel Phillips
Quoted 8 lines
Quoted 5 lines
Quoted 5 lines
Quoted 3 lines
Redirty_tailnearly works, but "if (!list_empty(&wb->b_dirty))" is not correct because the inode needs to end up on the dirty list whether it was already there or not.
redirty_tail()always moves the inode to the end of the dirty list.It doesn't move inode to end of the dirty if wb.b_dirty is empty (I.e. it can move from wb.b_io to wb.b_dirty too).
Um, really? What code are you reading? From 3.15-rc8:
static void redirty_tail(struct inode *inode, struct bdi_writeback *wb)
{
assert_spin_locked(&wb->list_lock);
if (!list_empty(&wb->b_dirty)) {
struct inode *tail;
tail = wb_inode(wb->b_dirty.next);
if (time_before(inode->dirtied_when, tail->dirtied_when))
inode->dirtied_when = jiffies;
}
list_move(&inode->i_wb_list, &wb->b_dirty);
}The list_move() is not conditional at all and so the inode is *always* moved to the tail of wb->b_dirty....
Quoted 5 lines
It has difference.
Say,
tail->dirtied_when== 1,inode->dirtied_when== 2, and now == 30 (tail->dirtied_whenis expired at 31 with default config). In this case,redirty_tail()doesn't update ->dirtied_when.
OK, that's a different issue, and is actually handled by requeue_inode(), which is called to put inodes back on the correct dirty list when IO completes. I think that if you are going to use the wb dirty inode lists, you should probably use the existing functions to manage the inode lists appropriately rather than creating your own writeback list lifecycle.
If tux3 wants it's own dirty inode list lifecycles, then that's where avoiding the wb lists completely is an appropriate design point. I don't want to hack little bits and pieces all over the writeback code to support what tux3 is doing right now if it's going to do something else real soon. When tux3 moves to use it's own internal lists these new funcitons just have to be removed again, so let's skip the hack step we are at right now and go straight for supporting the "don't need the fs-writeback lists" infrstructure.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 18 lines · Dave Chinner
Quoted 2 lines
It doesn't move inode to end of the dirty if wb.b_dirty is empty (I.e. it can move from wb.b_io to wb.b_dirty too).
Um, really? What code are you reading? From 3.15-rc8:
static void
redirty_tail(struct inode *inode, struct bdi_writeback *wb){ assert_spin_locked(&wb->list_lock); if (!list_empty(&wb->b_dirty)) { struct inode *tail; tail = wb_inode(wb->b_dirty.next); if (time_before(inode->dirtied_when, tail->dirtied_when)) inode->dirtied_when = jiffies; } list_move(&inode->i_wb_list, &wb->b_dirty); }
Quoted 2 lines
The
list_move()is not conditional at all and so the inode is *always* moved to the tail ofwb->b_dirty....
Oh, you are right.
Quoted 6 lines
Quoted 5 lines
It has difference.
Say,
tail->dirtied_when== 1,inode->dirtied_when== 2, and now == 30 (tail->dirtied_whenis expired at 31 with default config). In this case,redirty_tail()doesn't update ->dirtied_when.
Quoted 6 lines
OK, that's a different issue, and is actually handled by
requeue_inode(), which is called to put inodes back on the correct dirty list when IO completes. I think that if you are going to use the wb dirty inode lists, you should probably use the existing functions to manage the inode lists appropriately rather than creating your own writeback list lifecycle.
See __mark_inode_dirty() what does. We can consolidate that with __mark_inode_dirty() if you want.
In our case, dirty while inode has I_DIRTY needs to handle new dirty sometime, because of data=journal behavior.
Quoted 8 lines
If tux3 wants it's own dirty inode list lifecycles, then that's where avoiding the wb lists completely is an appropriate design point. I don't want to hack little bits and pieces all over the writeback code to support what tux3 is doing right now if it's going to do something else real soon. When tux3 moves to use it's own internal lists these new funcitons just have to be removed again, so let's skip the hack step we are at right now and go straight for supporting the "don't need the fs-writeback lists" infrstructure.
Is it agreed with someone? There was bdflush, pdflush, and now bdi flush. Why changed to bdi flush? This respects the intent of change "pdflush to bdi flush" more or less.
Thanks.
Signature · 1 line
OGAWA Hirofumi <[email protected]>
Jan Kara
maintainerQuoted 44 lines · Dave Chinner
Quoted 16 lines · Daniel Phillips
Quoted 16 lines
However, we already avoid the VFS writeback lists for certain filesystems for pure metadata. e.g. XFS does not use the VFS dirty inode lists for inode metadata changes. They get tracked internally by the transaction subsystem which does it's own writeback according to the requirements of journal space availability.
This is done simply by not calling
mark_inode_dirty()on any metadata only change. If we want to do the same for data, then we'd simply not callmark_inode_dirty()in the data IO path. That requires a custom ->set_page_dirty method to be provided by the filesystem that didn't call__mark_inode_dirty(mapping->host, I_DIRTY_PAGES);and instead did it's own thing.
Quoted 7 lines
Quoted 4 lines
So the per-superblock dirty tracking is something we can do right now, and some filesystems do it for metadata. The missing piece for data is writeback infrastructure capable of deferring to superblocks for writeback rather than BDIs....
We agree that fs-writeback inode lists are broken for anything more sophisticated than Ext2.
No, I did not say that.
I said XFS does something different for metadata changes because it has different flushing constraints and requirements than the generic code provides. That does not make the generic code broken.
Quoted 6 lines
An advantage of the patch under
consideration is that it still lets fs-writeback mostly work the way it has worked for the last few years, except for not allowing it to pick specific inodes and data pages for writeout. As far as I can see, it still balances writeout between different filesystems on the same block device pretty well.Not really. If there are 3 superblocks on a BDI, and the dirty inode list iterates between 2 of them with lots of dirty inodes, it can starve writeback from the third until one of it's dirty inodes pops to the head of the
b_iolist. So it's inherently unfair from that perspective.
Quoted 4 lines
Changing the high level flushing to be per-superblock rather than per-BDI would enable us to control that behaviour and be much fairer to all the superblocks on a given BDI. That said, I don't really care that much about this case...
So we currently flush inodes in first dirtied first written back order when superblock is not specified in writeback work. That completely ignores the fact to which superblock inode belongs but I don't see per-sb fairness to actually make any sense when
1) flushing old data (to keep promise set in dirty_expire_centisecs)
2) flushing data to reduce number of dirty pages
And these are really the only two cases where we don't do per-sb flushing.
Now when filesystems want to do something more clever (and I can see reasons for that e.g. when journalling metadata, even more so when journalling data) I agree we need to somehow implement the above two types of writeback using per-sb flushing. Type 1) is actually pretty easy - just tell each sb to writeback dirty data upto time T. Type 2) is more difficult because that is more openended task - it seems similar to what shrinkers do but that would require us to track per sb amount of dirty pages / inodes and I'm not sure we want to add even more page counting statistics... Especially since often bdi == fs. Thoughts?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 8 lines · Jan Kara
So we currently flush inodes in first dirtied first written back order when superblock is not specified in writeback work. That completely ignores the fact to which superblock inode belongs but I don't see per-sb fairness to actually make any sense when
1) flushing old data (to keep promise set indirty_expire_centisecs)
2) flushing data to reduce number of dirty pages
And these are really the only two cases where we don't do per-sb flushing.
Quoted 9 lines
Now when filesystems want to do something more clever (and I can see reasons for that e.g. when journalling metadata, even more so when journalling data) I agree we need to somehow implement the above two types of writeback using per-sb flushing. Type 1) is actually pretty easy - just tell each sb to writeback dirty data upto time T. Type 2) is more difficult because that is more openended task - it seems similar to what shrinkers do but that would require us to track per sb amount of dirty pages / inodes and I'm not sure we want to add even more page counting statistics... Especially since often bdi == fs. Thoughts?
Honestly I think doing per-bdi writeback has been a major mistake. As you said it only even matters when we have filesystems on multiple partitions on a single device, and even then only in a simple setup, as soon as we use LVM or btrfs this sort of sharing stops to happen anyway. I don't even see much of a benefit except that we prevent two flushing daemons to congest a single device for that special case of multiple filesystems on partitions of the same device, and that could be solved in other ways.
The major benefit of the per-bdi writeback was that for the usual case of one filesystem per device we get exactly one flusher thread per filesystems intead of multiple competing ones, but per-sb writeback would solve that just as fine.
Quoted 8 lines · Christoph Hellwig
Honestly I think doing per-bdi writeback has been a major mistake. As you said it only even matters when we have filesystems on multiple partitions on a single device, and even then only in a simple setup, as soon as we use LVM or btrfs this sort of sharing stops to happen anyway. I don't even see much of a benefit except that we prevent two flushing daemons to congest a single device for that special case of multiple filesystems on partitions of the same device, and that could be solved in other ways.
To be fair, back when per-bdi writeback was introduced, having multiple partitions on a single disk was far more common, and the use of LVM was much less common. These days, many more systems using one big root filesystem, and or using flash where the parallel writes can actually be a good thing (since there isn't a single disk head which has to seek all over the HDD), the case for keeping per-bdi writeback is much weaker, if not non-existent.
Cheers,
- Ted
Quoted 8 lines · Christian Stroetmann
In general, I do not believe that the complexity problems of soft updates, atomic writes, and related techniques can be solved by hand/manually. So my suggestion is to automatically handle the complexity problem of e.g. dependancies in a way that is comparable to a(n on-the-fly) file-system compiler so to say that works on a very large dependancy graph (having several billions of graph vertices actually). And at this point an abstraction like it is given with Featherstitch helps to feed and control this special FS compiler.
Well, if you want to try to implement something like this, go for it!
I'd be very curious to see how well (a) how much CPU overhead it takes to crunch on a dependency graph with billions of vertices, and (b) how easily can it be to express these dependencies and maintainable such a dependency language would be. Sounds like a great research topic, and I'll note the Call For Papers for FAST 2015 is out, and if you can solve these problems, it would make a great FAST 2015 submission:
https://www.usenix.org/conference/fast15/call-for-papers
Cheers,
- Ted
Jan Kara
maintainerQuoted 19 lines · Christoph Hellwig
Quoted 8 lines · Jan Kara
So we currently flush inodes in first dirtied first written back order when superblock is not specified in writeback work. That completely ignores the fact to which superblock inode belongs but I don't see per-sb fairness to actually make any sense when
1) flushing old data (to keep promise set indirty_expire_centisecs)
2) flushing data to reduce number of dirty pages
And these are really the only two cases where we don't do per-sb flushing.Quoted 9 lines
Now when filesystems want to do something more clever (and I can see reasons for that e.g. when journalling metadata, even more so when journalling data) I agree we need to somehow implement the above two types of writeback using per-sb flushing. Type 1) is actually pretty easy - just tell each sb to writeback dirty data upto time T. Type 2) is more difficult because that is more openended task - it seems similar to what shrinkers do but that would require us to track per sb amount of dirty pages / inodes and I'm not sure we want to add even more page counting statistics... Especially since often bdi == fs. Thoughts?
Quoted 8 lines
Honestly I think doing per-bdi writeback has been a major mistake. As you said it only even matters when we have filesystems on multiple partitions on a single device, and even then only in a simple setup, as soon as we use LVM or btrfs this sort of sharing stops to happen anyway. I don't even see much of a benefit except that we prevent two flushing daemons to congest a single device for that special case of multiple filesystems on partitions of the same device, and that could be solved in other ways.
So I agree per-bdi / per-sb matters only in simple setups but machines
with single rotating disk with several partitions and without LVM aren't that rare AFAICT from my experience. And I agree we went for per-bdi flushing to avoid two threads congesting a single device leading to suboptimal IO patterns during background writeback.
So currently I'm convinced we want to go for per-sb dirty tracking. That also makes some speedups in that code noticeably simpler. I'm not convinced about the per-sb flushing thread - if we don't regress the multiple sb on bdi case when we just let the threads from different superblocks contend for IO, then that would be a natural thing to do. But once we have to introduce some synchronization between threads to avoid regressions, I think it might be easier to just stay with per-bdi thread which switches between superblocks.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 10 lines · Theodore Ts'o
Quoted 8 lines · Christian Stroetmann
In general, I do not believe that the complexity problems of soft updates, atomic writes, and related techniques can be solved by hand/manually. So my suggestion is to automatically handle the complexity problem of e.g. dependancies in a way that is comparable to a(n on-the-fly) file-system compiler so to say that works on a very large dependancy graph (having several billions of graph vertices actually). And at this point an abstraction like it is given with Featherstitch helps to feed and control this special FS compiler.
Well, if you want to try to implement something like this, go for it!
I am already active since some weeks.
Quoted 4 lines
I'd be very curious to see how well (a) how much CPU overhead it takes to crunch on a dependency graph with billions of vertices, and (b) how easily can it be to express these dependencies and maintainable such a dependency language would be. Sounds like a great research topic, and
To a) A run is expected to take some few hours on a single computing node.
Also, such a graph processing must not be done all the time, but only if a new application demands a specific handling of the data in respect to e.g. one of the ACID criterias. That is the reason why I put "on-the-fly" in paranthesis.
To b) I hoped that file system developers could make some suggestions or point to some no-gos. I am also thinking about Petri-Nets in this relation, though it is just an idea.
I would also like to mention that it could be used in conjunction with Non-Volatile Memory (NVM) as well.
Quoted 4 lines
I'll note the Call For Papers for FAST 2015 is out, and if you can solve these problems, it would make a great FAST 2015 submission:
Are you serious or have I missed the 1st of April once again?
Actually, I could only write a general overview about the approach comparable to a white paper, but nothing more.
Quoted 4 lines
Cheers,
- Ted
Best regards
Christian
Quoted 6 lines · Jan Kara
Quoted 1 line · Christoph Hellwig
On Tue, Jun 03, 2014 at 04:05:31PM +0200, Jan Kara wrote:
...
So I agree per-bdi / per-sb matters only in simple setups but machines
with single rotating disk with several partitions and without LVM aren't that rare AFAICT from my experience.
Retribution is sure to be swift, terrible and eternal for anyone who dares to break those.
Quoted 3 lines
And I agree we went for per-bdi
flushing to avoid two threads congesting a single device leading to suboptimal IO patterns during background writeback.
A proposal is on the table to implement s_ops->writeback() as a per-sb operation in such a way that nothing changes in the current per-inode path. Good or bad approach?
Quoted 2 lines
So currently I'm convinced we want to go for per-sb dirty tracking. That also makes some speedups in that code noticeably simpler. I'm not
convinced
Quoted 6 lines
about the per-sb flushing thread - if we don't regress the multiple sb on bdi case when we just let the threads from different superblocks contend for IO, then that would be a natural thing to do. But once we have to introduce some synchronization between threads to avoid regressions, I think it might be easier to just stay with per-bdi thread which switches between superblocks.
Could you elaborate on the means of switching between superblocks? Do you mean a new fs-writeback path just for data=journal class filesystems, or are you suggesting changing the way all filesystems are driven?
Regards,
Daniel
Jan Kara
maintainerQuoted 10 lines · Daniel Phillips
Quoted 5 lines · Jan Kara
Quoted 1 line · Christoph Hellwig
On Tue, Jun 03, 2014 at 04:05:31PM +0200, Jan Kara wrote:
And I agree we went for per-bdi
flushing to avoid two threads congesting a single device leading to suboptimal IO patterns during background writeback.A proposal is on the table to implement
s_ops->writeback()as a per-sb operation in such a way that nothing changes in the current per-inode path. Good or bad approach?
Having s_ops->writeback() is fine. But I just hate how you hook in that
callback into the writeback code (and Dave has expressed similar concerns). The way you hook it up, filesystem still has to have one inode in the dirty list so that __writeback_inodes_wb() can find that inode, get superblock from it and ask for writeback to be done via callback. That's really ugly and no-go for me.
Quoted 10 lines
Quoted 2 lines
So currently I'm convinced we want to go for per-sb dirty tracking. That also makes some speedups in that code noticeably simpler. I'm not
convinced
Quoted 6 lines
about the per-sb flushing thread - if we don't regress the multiple sb on bdi case when we just let the threads from different superblocks contend for IO, then that would be a natural thing to do. But once we have to introduce some synchronization between threads to avoid regressions, I think it might be easier to just stay with per-bdi thread which switches between superblocks.
Quoted 3 lines
Could you elaborate on the means of switching between superblocks? Do you mean a new fs-writeback path just for data=journal class filesystems, or are you suggesting changing the way all filesystems are driven?
So I suggest changing the way all filesystems are driven to a per-sb one.
That makes sense for other reasons as well and allows clean incorporation of your writeback callback (either a fs will use generic callback which would do what generic writeback code does now, only with per-sb dirty lists instead of current per-bdi ones, or the fs can do its own stuff). I can write something (the switch isn't really that complex) but it will need quite some testing to verify we don't regress somewhere...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR