Skip to contentSkip to conversation

simplify writeback thread creation

RFC PATCH In review Christoph Hellwig 4 participants 15 messages Jul 7, 2010 – Jul 9, 2010

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 simplify writeback thread creation

Currently the per-bdi writeback thread is only created when there is dirty any dirty data on the BDI, and it lazy exists when it's been unused for some time.

This leads to some very complex code, and the need to keep a forker thread around.

This patch removes all this code and simply creates the thread as part of the bdi registration. The downside is that we use up ressoures for possible unused devices, although that overhead is rather low, with 8k kernel stack size on x86 and few other, even smaller ressources.

If the overhead is still considered too much I can look into starting the thread explicitly instead of as part of the bdi registration, but that will require a bit of code complexity, too.

---

 fs/fs-writeback.c                |   60 ---------
 include/linux/backing-dev.h      |   14 --
 include/linux/writeback.h        |    1 
 include/trace/events/writeback.h |    3 
 mm/backing-dev.c                 |  252 ++++-----------------------------------
 5 files changed, 33 insertions(+), 297 deletions(-)
fs/fs-writeback.c +6 -54
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -80,19 +80,7 @@ static void bdi_queue_work(struct backin
 	list_add_tail(&work->list, &bdi->work_list);
 	spin_unlock(&bdi->wb_lock);
 
-	/*
-	 * If the default thread isn't there, make sure we add it. When
-	 * it gets created and wakes up, we'll run this work.
-	 */
-	if (unlikely(!bdi->wb.task)) {
-		trace_writeback_nothread(bdi, work);
-		wake_up_process(default_backing_dev_info.wb.task);
-	} else {
-		struct bdi_writeback *wb = &bdi->wb;
-
-		if (wb->task)
-			wake_up_process(wb->task);
-	}
+	wake_up_process(bdi->wb.task);
 }
 
 static void
@@ -107,10 +95,8 @@ __bdi_start_writeback(struct backing_dev
 	 */
 	work = kzalloc(sizeof(*work), GFP_ATOMIC);
 	if (!work) {
-		if (bdi->wb.task) {
-			trace_writeback_nowork(bdi);
-			wake_up_process(bdi->wb.task);
-		}
+		trace_writeback_nowork(bdi);
+		wake_up_process(bdi->wb.task);
 		return;
 	}
 
@@ -756,7 +742,7 @@ static long wb_check_old_data_flush(stru
 /*
  * Retrieve work items and do the writeback they describe
  */
-long wb_do_writeback(struct bdi_writeback *wb, int force_wait)
+static long wb_do_writeback(struct bdi_writeback *wb, int force_wait)
 {
 	struct backing_dev_info *bdi = wb->bdi;
 	struct wb_writeback_work *work;
@@ -800,17 +786,8 @@ int bdi_writeback_thread(void *data)
 {
 	struct bdi_writeback *wb = data;
 	struct backing_dev_info *bdi = wb->bdi;
-	unsigned long last_active = jiffies;
-	unsigned long wait_jiffies = -1UL;
 	long pages_written;
 
-	/*
-	 * Add us to the active bdi_list
-	 */
-	spin_lock_bh(&bdi_lock);
-	list_add_rcu(&bdi->bdi_list, &bdi_list);
-	spin_unlock_bh(&bdi_lock);
-
 	current->flags |= PF_FLUSHER | PF_SWAPWRITE;
 	set_freezable();
 
@@ -819,38 +796,14 @@ int bdi_writeback_thread(void *data)
 	 */
 	set_user_nice(current, 0);
 
-	/*
-	 * Clear pending bit and wakeup anybody waiting to tear us down
-	 */
-	clear_bit(BDI_pending, &bdi->state);
-	smp_mb__after_clear_bit();
-	wake_up_bit(&bdi->state, BDI_pending);
-
-	trace_writeback_thread_start(bdi);
-
 	while (!kthread_should_stop()) {
 		pages_written = wb_do_writeback(wb, 0);
 
 		trace_writeback_pages_written(pages_written);
 
-		if (pages_written)
-			last_active = jiffies;
-		else if (wait_jiffies != -1UL) {
-			unsigned long max_idle;
-
-			/*
-			 * Longest period of inactivity that we tolerate. If we
-			 * see dirty data again later, the task will get
-			 * recreated automatically.
-			 */
-			max_idle = max(5UL * 60 * HZ, wait_jiffies);
-			if (time_after(jiffies, max_idle + last_active))
-				break;
-		}
-
 		if (dirty_writeback_interval) {
-			wait_jiffies = msecs_to_jiffies(dirty_writeback_interval * 10);
-			schedule_timeout_interruptible(wait_jiffies);
+			schedule_timeout_interruptible(msecs_to_jiffies(
+					dirty_writeback_interval * 10));
 		} else {
 			set_current_state(TASK_INTERRUPTIBLE);
 			if (list_empty_careful(&wb->bdi->work_list) &&
@@ -871,7 +824,6 @@ int bdi_writeback_thread(void *data)
 	if (!list_empty(&bdi->work_list))
 		wb_do_writeback(wb, 1);
 
-	trace_writeback_thread_stop(bdi);
 	return 0;
 }
 
include/linux/backing-dev.h +0 -14
Index: linux-2.6/include/linux/backing-dev.h
===================================================================
@@ -26,8 +26,6 @@ struct dentry;
  * Bits in backing_dev_info.state
  */
 enum bdi_state {
-	BDI_pending,		/* On its way to being activated */
-	BDI_wb_alloc,		/* Default embedded wb allocated */
 	BDI_async_congested,	/* The async (write) queue is getting full */
 	BDI_sync_congested,	/* The sync queue is getting full */
 	BDI_registered,		/* bdi_register() was done */
@@ -58,7 +56,6 @@ struct bdi_writeback {
 
 struct backing_dev_info {
 	struct list_head bdi_list;
-	struct rcu_head rcu_head;
 	unsigned long ra_pages;	/* max readahead in PAGE_CACHE_SIZE units */
 	unsigned long state;	/* Always use atomic bitops on this */
 	unsigned int capabilities; /* Device capabilities */
@@ -306,11 +303,6 @@ static inline bool bdi_cap_swap_backed(s
 	return bdi->capabilities & BDI_CAP_SWAP_BACKED;
 }
 
-static inline bool bdi_cap_flush_forker(struct backing_dev_info *bdi)
-{
-	return bdi == &default_backing_dev_info;
-}
-
 static inline bool mapping_cap_writeback_dirty(struct address_space *mapping)
 {
 	return bdi_cap_writeback_dirty(mapping->backing_dev_info);
@@ -326,12 +318,6 @@ static inline bool mapping_cap_swap_back
 	return bdi_cap_swap_backed(mapping->backing_dev_info);
 }
 
-static inline int bdi_sched_wait(void *word)
-{
-	schedule();
-	return 0;
-}
-
 static inline void blk_run_backing_dev(struct backing_dev_info *bdi,
 				       struct page *page)
 {
include/linux/writeback.h +0 -1
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -64,7 +64,6 @@ int writeback_inodes_sb_if_idle(struct s
 void sync_inodes_sb(struct super_block *);
 void writeback_inodes_wb(struct bdi_writeback *wb,
 		struct writeback_control *wbc);
-long wb_do_writeback(struct bdi_writeback *wb, int force_wait);
 void wakeup_flusher_threads(long nr_pages);
 
 /* writeback.h requires fs.h; it, too, is not included from here. */
mm/backing-dev.c +27 -225

329 lines not on this page · raw message

include/trace/events/writeback.h +0 -3
Index: linux-2.6/include/trace/events/writeback.h
===================================================================
@@ -45,7 +45,6 @@ DECLARE_EVENT_CLASS(writeback_work_class
 DEFINE_EVENT(writeback_work_class, name, \
 	TP_PROTO(struct backing_dev_info *bdi, struct wb_writeback_work *work), \
 	TP_ARGS(bdi, work))
-DEFINE_WRITEBACK_WORK_EVENT(writeback_nothread);
 DEFINE_WRITEBACK_WORK_EVENT(writeback_queue);
 DEFINE_WRITEBACK_WORK_EVENT(writeback_exec);
 
@@ -82,8 +81,6 @@ DEFINE_EVENT(writeback_class, name, \
 DEFINE_WRITEBACK_EVENT(writeback_nowork);
 DEFINE_WRITEBACK_EVENT(writeback_bdi_register);
 DEFINE_WRITEBACK_EVENT(writeback_bdi_unregister);
-DEFINE_WRITEBACK_EVENT(writeback_thread_start);
-DEFINE_WRITEBACK_EVENT(writeback_thread_stop);
 
 DECLARE_EVENT_CLASS(wbc_class,
 	TP_PROTO(struct writeback_control *wbc, struct backing_dev_info *bdi),
#1 lore raw

Thanks Christoph,

I'll try to test it in my environment.

Quoted 6 lines · Christoph Hellwig

Currently the per-bdi writeback thread is only created when there is dirty any dirty data on the BDI, and it lazy exists when it's been unused for some time.

This leads to some very complex code, and the need to keep a forker thread around.

I'd add here a comment that due to the complexity it is not bug-free - e.g., I pointed to the race between thread suicide and wake-up.

Also I'd add a comment that it leads to more thread types waking up periodically and consuming power. With just one thread type it is easier to fix this issue.

Quoted 4 lines

This patch removes all this code and simply creates the thread as part of the bdi registration. The downside is that we use up ressoures for possible unused devices, although that overhead is rather low, with 8k kernel stack size on x86 and few other, even smaller ressources.

s/ressources/resources/
:-)

On the other hand, there was a message that process IDs are precious resource on large NUMA systems. So, less suicides, more free IDs :-) But this is just a minor note.

Quoted 3 lines

If the overhead is still considered too much I can look into starting the thread explicitly instead of as part of the bdi registration, but that will require a bit of code complexity, too.

Well, the threads can exit, then we need:

1. when adding bdi works and the thread exited, forr it.
2. the same should be done in __mark_inode_dirty - if the bdi thread is dead, create it.

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#2 lore raw
In reply to Christoph Hellwig
Quoted 7 lines · Christoph Hellwig

Currently the per-bdi writeback thread is only created when there is dirty any dirty data on the BDI, and it lazy exists when it's been unused for some time.

This leads to some very complex code, and the need to keep a forker thread around.

Quoted 8 lines

This patch removes all this code and simply creates the thread as part of the bdi registration. The downside is that we use up ressoures for possible unused devices, although that overhead is rather low, with 8k kernel stack size on x86 and few other, even smaller ressources.

If the overhead is still considered too much I can look into starting the thread explicitly instead of as part of the bdi registration, but that will require a bit of code complexity, too.

I'm pretty sure this will come back to bite us in the ass... If we are going to change the lazy create/exit setup, I would greatly prefer doing it at fs mount time (or something to that effect).

Signature · 1 line
Jens Axboe
#3 lore raw
Quoted 13 lines · Jens Axboe
Quoted 12 lines · Christoph Hellwig

Currently the per-bdi writeback thread is only created when there is dirty any dirty data on the BDI, and it lazy exists when it's been unused for some time.

This leads to some very complex code, and the need to keep a forker thread around.

This patch removes all this code and simply creates the thread as part of the bdi registration. The downside is that we use up ressoures for possible unused devices, although that overhead is rather low, with 8k kernel stack size on x86 and few other, even smaller ressources.

Quoted 7 lines
Quoted 3 lines

If the overhead is still considered too much I can look into starting the thread explicitly instead of as part of the bdi registration, but that will require a bit of code complexity, too.

I'm pretty sure this will come back to bite us in the ass... If we are going to change the lazy create/exit setup, I would greatly prefer doing it at fs mount time (or something to that effect).

How about not starting any thread at all at the bdi registration time, and start a bdi thread only when something for this bdi becomes dirty (__mark_inode_dirty()) or a bdi work is queued (bdi_queue_work())? If we do this, then the tasks can also die by the 5min timeout, and will be forked again when dirt/bdi works arrives?

I guess it is a bit challenging to start a task in __mark_inode_dirty(), whis is supposed to be fast and non-sleeping, but we can just submit a work which will start the task.

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#4 lore raw
Quoted 28 lines · Artem Bityutskiy
Quoted 13 lines · Jens Axboe
Quoted 12 lines · Christoph Hellwig

Currently the per-bdi writeback thread is only created when there is dirty any dirty data on the BDI, and it lazy exists when it's been unused for some time.

This leads to some very complex code, and the need to keep a forker thread around.

This patch removes all this code and simply creates the thread as part of the bdi registration. The downside is that we use up ressoures for possible unused devices, although that overhead is rather low, with 8k kernel stack size on x86 and few other, even smaller ressources.

Quoted 7 lines
Quoted 3 lines

If the overhead is still considered too much I can look into starting the thread explicitly instead of as part of the bdi registration, but that will require a bit of code complexity, too.

I'm pretty sure this will come back to bite us in the ass... If we are going to change the lazy create/exit setup, I would greatly prefer doing it at fs mount time (or something to that effect).

How about not starting any thread at all at the bdi registration time, and start a bdi thread only when something for this bdi becomes dirty (__mark_inode_dirty()) or a bdi work is queued (bdi_queue_work())? If we do this, then the tasks can also die by the 5min timeout, and will be forked again when dirt/bdi works arrives?

Quoted 3 lines

I guess it is a bit challenging to start a task in __mark_inode_dirty(), whis is supposed to be fast and non-sleeping, but we can just submit a work which will start the task.

That work would have to reside on the stack, and __mark_inode_dirty() block on the thread startup. We can't always do that.

Signature · 1 line
Jens Axboe
#5 lore raw
Quoted 6 lines · Jens Axboe
Quoted 6 lines

How about not starting any thread at all at the bdi registration time, and start a bdi thread only when something for this bdi becomes dirty (__mark_inode_dirty()) or a bdi work is queued (bdi_queue_work())? If we do this, then the tasks can also die by the 5min timeout, and will be forked again when dirt/bdi works arrives?

Quoted 6 lines
Quoted 3 lines

I guess it is a bit challenging to start a task in __mark_inode_dirty(), whis is supposed to be fast and non-sleeping, but we can just submit a work which will start the task.

That work would have to reside on the stack, and __mark_inode_dirty() block on the thread startup. We can't always do that.

We can have a pre-defined bdi->wb->task_start_work or something like that.

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#6 lore raw
Quoted 11 lines · Artem Bityutskiy
Quoted 10 lines · Jens Axboe
Quoted 9 lines

How about not starting any thread at all at the bdi registration time, and start a bdi thread only when something for this bdi becomes dirty (__mark_inode_dirty()) or a bdi work is queued (bdi_queue_work())? If we do this, then the tasks can also die by the 5min timeout, and will be forked again when dirt/bdi works arrives?

I guess it is a bit challenging to start a task in __mark_inode_dirty(), whis is supposed to be fast and non-sleeping, but we can just submit a work which will start the task.

Quoted 5 lines
Quoted 2 lines

That work would have to reside on the stack, and __mark_inode_dirty() block on the thread startup. We can't always do that.

We can have a pre-defined bdi->wb->task_start_work or something like that.

Yeah, that would work.

Signature · 1 line
Jens Axboe
#7 lore raw
Quoted 12 lines · Jens Axboe
Quoted 11 lines · Artem Bityutskiy
Quoted 10 lines · Jens Axboe
Quoted 9 lines

How about not starting any thread at all at the bdi registration time, and start a bdi thread only when something for this bdi becomes dirty (__mark_inode_dirty()) or a bdi work is queued (bdi_queue_work())? If we do this, then the tasks can also die by the 5min timeout, and will be forked again when dirt/bdi works arrives?

I guess it is a bit challenging to start a task in __mark_inode_dirty(), whis is supposed to be fast and non-sleeping, but we can just submit a work which will start the task.

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

That work would have to reside on the stack, and __mark_inode_dirty() block on the thread startup. We can't always do that.

We can have a pre-defined bdi->wb->task_start_work or something like that.

Yeah, that would work.

Hmm, was thinking about this while driving home - the forker approach has a good resilience property - if it cannot fork - it'll do the stuff itself. I have a feeling that if something like this to be implemented with the approach I suggested, we'll end up with similar level of complexity that we wanted to get rid of...

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#8 lore raw
Quoted 5 lines · Artem Bityutskiy

Hmm, was thinking about this while driving home - the forker approach has a good resilience property - if it cannot fork - it'll do the stuff itself. I have a feeling that if something like this to be implemented with the approach I suggested, we'll end up with similar level of complexity that we wanted to get rid of...

Yes, the lazy starting is what adds the complexity. I think starting it once we have any filesystem mounted on the bdi and stop it once all filesystems are gone is a lot simpler and more elegant. It also solves the other issue wit hall lazy schemes, that is the race betwen dirtying data with no alive thread and the bdi going away. The current code tries to deal with that by splicing the remaining dirty inodes to the default BDI, but that'll just cause memory corruption in most cases, because the BDI is references all over the writeback code and it's gone by the point it'll actually go away. Which makes be believe this race is a rather theoretical one.

#9 lore raw
Quoted 10 lines · Christoph Hellwig
Quoted 5 lines · Artem Bityutskiy

Hmm, was thinking about this while driving home - the forker approach has a good resilience property - if it cannot fork - it'll do the stuff itself. I have a feeling that if something like this to be implemented with the approach I suggested, we'll end up with similar level of complexity that we wanted to get rid of...

Yes, the lazy starting is what adds the complexity. I think starting it once we have any filesystem mounted on the bdi and stop it once all filesystems are gone is a lot simpler and more elegant.

But what about cases like 'dd if=/dev/zero of=/dev/sda4'? They also involve dirty data write-back.

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#10 lore raw
Quoted 8 lines · Artem Bityutskiy
Quoted 7 lines · Christoph Hellwig
Quoted 5 lines · Artem Bityutskiy

Hmm, was thinking about this while driving home - the forker approach has a good resilience property - if it cannot fork - it'll do the stuff itself. I have a feeling that if something like this to be implemented with the approach I suggested, we'll end up with similar level of complexity that we wanted to get rid of...

Quoted 6 lines
Quoted 3 lines

Yes, the lazy starting is what adds the complexity. I think starting it once we have any filesystem mounted on the bdi and stop it once all filesystems are gone is a lot simpler and more elegant.

But what about cases like 'dd if=/dev/zero of=/dev/sda4'? They also involve dirty data write-back.

You would have to do it at device open time, if the thread does not already exist.

Not sure this is all worth it, I think the complexity of the lazy create/exit is a bit exaggerated.

Signature · 1 line
Jens Axboe
#11 lore raw

Chiming onto this thread late, I've definitely seen customer workloads which used a whole fleet-load of blades connected to a SAN network, the idea being that the blades were easily replaceable, and could take various different responsibilities due to shifting workload, battle damage,
etc. So this would be a case where the individual blades wouldn't have
huge amounts of memory, but there would be a huge number of block
devices available on the SAN network. So at least in this case, avoiding
creating every single BDI thread until the block devices is first used would be a major win for this sort of customer deployment.

-- Ted

#13 lore raw
Quoted 5 lines · Christoph Hellwig
Quoted 2 lines · Artem Bityutskiy

But what about cases like 'dd if=/dev/zero of=/dev/sda4'? They also involve dirty data write-back.

But only on the bdev fs inode.

OK, I do not know details that code and how it works, need to investigate, but I just thought it is a different case comparing to the "create bdi task only when an FS is mounter on top". And just wanted to point that things may be not so elegant.

Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#14 lore raw
In reply to Christoph Hellwig
Quoted 5 lines · Christoph Hellwig
(quoted diff) +0 -0
@@ -503,18 +359,10 @@ int bdi_register(struct backing_dev_info
         * and add other bdi's to the list. They will get a thread

created
* on-demand when they need it.
*/

Just noticed that this comment should be killed as well.

Quoted 16 lines
mm/backing-dev.c +3 -10
-       if (bdi_cap_flush_forker(bdi)) {
-               struct bdi_writeback *wb = &bdi->wb;
-
-               wb->task = kthread_run(bdi_forker_task, wb, "bdi-%s",
-                                               dev_name(dev));
-               if (IS_ERR(wb->task)) {
-                       wb->task = NULL;
-                       ret = -ENOMEM;
-
-                       bdi_remove_from_list(bdi);
+       if (bdi_cap_writeback_dirty(bdi)) {
+               ret = bdi_writeback_enable(bdi, dev);
+               if (ret)
                        goto exit;
Signature · 7 lines
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html