bdi: reimplement bdev_inode_switch_bdi()
authorTejun Heo <tj@kernel.org>
Sun, 7 Sep 2014 23:04:01 +0000 (08:04 +0900)
committerJens Axboe <axboe@fb.com>
Mon, 8 Sep 2014 16:00:43 +0000 (10:00 -0600)
A block_device may be attached to different gendisks and thus
different bdis over time.  bdev_inode_switch_bdi() is used to switch
the associated bdi.  The function assumes that the inode could be
dirty and transfers it between bdis if so.  This is a bit nasty in
that it reaches into bdi internals.

This patch reimplements the function so that it writes out the inode
if dirty.  This is a lot simpler and can be implemented without
exposing bdi internals.

Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Jens Axboe <axboe@fb.com>
fs/block_dev.c
include/linux/backing-dev.h
mm/backing-dev.c

index d3251eca64292e3e1f441c55d1bde4dbe45e03ac..cc8d68ac29aa4b71d091eba55c5af1b9f94285fc 100644 (file)
@@ -50,32 +50,22 @@ inline struct block_device *I_BDEV(struct inode *inode)
 EXPORT_SYMBOL(I_BDEV);
 
 /*
- * Move the inode from its current bdi to a new bdi. If the inode is dirty we
- * need to move it onto the dirty list of @dst so that the inode is always on
- * the right list.
+ * Move the inode from its current bdi to a new bdi.  Make sure the inode
+ * is clean before moving so that it doesn't linger on the old bdi.
  */
 static void bdev_inode_switch_bdi(struct inode *inode,
                        struct backing_dev_info *dst)
 {
-       struct backing_dev_info *old = inode->i_data.backing_dev_info;
-       bool wakeup_bdi = false;
-
-       if (unlikely(dst == old))               /* deadlock avoidance */
-               return;
-       bdi_lock_two(&old->wb, &dst->wb);
-       spin_lock(&inode->i_lock);
-       inode->i_data.backing_dev_info = dst;
-       if (inode->i_state & I_DIRTY) {
-               if (bdi_cap_writeback_dirty(dst) && !wb_has_dirty_io(&dst->wb))
-                       wakeup_bdi = true;
-               list_move(&inode->i_wb_list, &dst->wb.b_dirty);
+       while (true) {
+               spin_lock(&inode->i_lock);
+               if (!(inode->i_state & I_DIRTY)) {
+                       inode->i_data.backing_dev_info = dst;
+                       spin_unlock(&inode->i_lock);
+                       return;
+               }
+               spin_unlock(&inode->i_lock);
+               WARN_ON_ONCE(write_inode_now(inode, true));
        }
-       spin_unlock(&inode->i_lock);
-       spin_unlock(&old->wb.list_lock);
-       spin_unlock(&dst->wb.list_lock);
-
-       if (wakeup_bdi)
-               bdi_wakeup_thread_delayed(dst);
 }
 
 /* Kill _all_ buffers and pagecache , dirty or not.. */
index 2103a7fa3fd8620aacab577fdd47c50f0650e94a..5da6012b7a141e4bf6c10ec62b1573756215176c 100644 (file)
@@ -121,7 +121,6 @@ void bdi_start_background_writeback(struct backing_dev_info *bdi);
 void bdi_writeback_workfn(struct work_struct *work);
 int bdi_has_dirty_io(struct backing_dev_info *bdi);
 void bdi_wakeup_thread_delayed(struct backing_dev_info *bdi);
-void bdi_lock_two(struct bdi_writeback *wb1, struct bdi_writeback *wb2);
 
 extern spinlock_t bdi_lock;
 extern struct list_head bdi_list;
index b65fe93ad612bbf9b30056c7b7fabc309126c02a..7d63d5e9d3de637f249bffbbc1fbfb35a4290d24 100644 (file)
@@ -40,7 +40,7 @@ LIST_HEAD(bdi_list);
 /* bdi_wq serves all asynchronous writeback tasks */
 struct workqueue_struct *bdi_wq;
 
-void bdi_lock_two(struct bdi_writeback *wb1, struct bdi_writeback *wb2)
+static void bdi_lock_two(struct bdi_writeback *wb1, struct bdi_writeback *wb2)
 {
        if (wb1 < wb2) {
                spin_lock(&wb1->list_lock);