| From 5acda9d12dcf1ad0d9a5a2a7c646de3472fa7555 Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Thu, 3 Apr 2014 14:46:23 -0700 |
| Subject: bdi: avoid oops on device removal |
| |
| From: Jan Kara <jack@suse.cz> |
| |
| commit 5acda9d12dcf1ad0d9a5a2a7c646de3472fa7555 upstream. |
| |
| After commit 839a8e8660b6 ("writeback: replace custom worker pool |
| implementation with unbound workqueue") when device is removed while we |
| are writing to it we crash in bdi_writeback_workfn() -> |
| set_worker_desc() because bdi->dev is NULL. |
| |
| This can happen because even though bdi_unregister() cancels all pending |
| flushing work, nothing really prevents new ones from being queued from |
| balance_dirty_pages() or other places. |
| |
| Fix the problem by clearing BDI_registered bit in bdi_unregister() and |
| checking it before scheduling of any flushing work. |
| |
| Fixes: 839a8e8660b6777e7fe4e80af1a048aebe2b5977 |
| |
| Reviewed-by: Tejun Heo <tj@kernel.org> |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Cc: Derek Basehore <dbasehore@chromium.org> |
| Cc: Jens Axboe <axboe@kernel.dk> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/fs-writeback.c | 23 ++++++++++++++++++----- |
| include/linux/backing-dev.h | 2 +- |
| mm/backing-dev.c | 13 +++++++++---- |
| 3 files changed, 28 insertions(+), 10 deletions(-) |
| |
| --- a/fs/fs-writeback.c |
| +++ b/fs/fs-writeback.c |
| @@ -89,16 +89,29 @@ static inline struct inode *wb_inode(str |
| #define CREATE_TRACE_POINTS |
| #include <trace/events/writeback.h> |
| |
| +static void bdi_wakeup_thread(struct backing_dev_info *bdi) |
| +{ |
| + spin_lock_bh(&bdi->wb_lock); |
| + if (test_bit(BDI_registered, &bdi->state)) |
| + mod_delayed_work(bdi_wq, &bdi->wb.dwork, 0); |
| + spin_unlock_bh(&bdi->wb_lock); |
| +} |
| + |
| static void bdi_queue_work(struct backing_dev_info *bdi, |
| struct wb_writeback_work *work) |
| { |
| trace_writeback_queue(bdi, work); |
| |
| spin_lock_bh(&bdi->wb_lock); |
| + if (!test_bit(BDI_registered, &bdi->state)) { |
| + if (work->done) |
| + complete(work->done); |
| + goto out_unlock; |
| + } |
| list_add_tail(&work->list, &bdi->work_list); |
| - spin_unlock_bh(&bdi->wb_lock); |
| - |
| mod_delayed_work(bdi_wq, &bdi->wb.dwork, 0); |
| +out_unlock: |
| + spin_unlock_bh(&bdi->wb_lock); |
| } |
| |
| static void |
| @@ -114,7 +127,7 @@ __bdi_start_writeback(struct backing_dev |
| work = kzalloc(sizeof(*work), GFP_ATOMIC); |
| if (!work) { |
| trace_writeback_nowork(bdi); |
| - mod_delayed_work(bdi_wq, &bdi->wb.dwork, 0); |
| + bdi_wakeup_thread(bdi); |
| return; |
| } |
| |
| @@ -161,7 +174,7 @@ void bdi_start_background_writeback(stru |
| * writeback as soon as there is no other work to do. |
| */ |
| trace_writeback_wake_background(bdi); |
| - mod_delayed_work(bdi_wq, &bdi->wb.dwork, 0); |
| + bdi_wakeup_thread(bdi); |
| } |
| |
| /* |
| @@ -1017,7 +1030,7 @@ void bdi_writeback_workfn(struct work_st |
| current->flags |= PF_SWAPWRITE; |
| |
| if (likely(!current_is_workqueue_rescuer() || |
| - list_empty(&bdi->bdi_list))) { |
| + !test_bit(BDI_registered, &bdi->state))) { |
| /* |
| * The normal path. Keep writing back @bdi until its |
| * work_list is empty. Note that this path is also taken |
| --- a/include/linux/backing-dev.h |
| +++ b/include/linux/backing-dev.h |
| @@ -95,7 +95,7 @@ struct backing_dev_info { |
| unsigned int max_ratio, max_prop_frac; |
| |
| struct bdi_writeback wb; /* default writeback info for this bdi */ |
| - spinlock_t wb_lock; /* protects work_list */ |
| + spinlock_t wb_lock; /* protects work_list & wb.dwork scheduling */ |
| |
| struct list_head work_list; |
| |
| --- a/mm/backing-dev.c |
| +++ b/mm/backing-dev.c |
| @@ -297,7 +297,10 @@ void bdi_wakeup_thread_delayed(struct ba |
| unsigned long timeout; |
| |
| timeout = msecs_to_jiffies(dirty_writeback_interval * 10); |
| - queue_delayed_work(bdi_wq, &bdi->wb.dwork, timeout); |
| + spin_lock_bh(&bdi->wb_lock); |
| + if (test_bit(BDI_registered, &bdi->state)) |
| + queue_delayed_work(bdi_wq, &bdi->wb.dwork, timeout); |
| + spin_unlock_bh(&bdi->wb_lock); |
| } |
| |
| /* |
| @@ -310,9 +313,6 @@ static void bdi_remove_from_list(struct |
| spin_unlock_bh(&bdi_lock); |
| |
| synchronize_rcu_expedited(); |
| - |
| - /* bdi_list is now unused, clear it to mark @bdi dying */ |
| - INIT_LIST_HEAD(&bdi->bdi_list); |
| } |
| |
| int bdi_register(struct backing_dev_info *bdi, struct device *parent, |
| @@ -363,6 +363,11 @@ static void bdi_wb_shutdown(struct backi |
| */ |
| bdi_remove_from_list(bdi); |
| |
| + /* Make sure nobody queues further work */ |
| + spin_lock_bh(&bdi->wb_lock); |
| + clear_bit(BDI_registered, &bdi->state); |
| + spin_unlock_bh(&bdi->wb_lock); |
| + |
| /* |
| * Drain work list and shutdown the delayed_work. At this point, |
| * @bdi->bdi_list is empty telling bdi_Writeback_workfn() that @bdi |