]> err.no Git - linux-2.6/blobdiff - fs/fs-writeback.c
writeback: fix time ordering of the per superblock dirty inode lists 7
[linux-2.6] / fs / fs-writeback.c
index c403b66ec83c6525532c25decbe923c32356649b..39fadfad86f7cf0f2991a0d7f8f612ec84eb453a 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/spinlock.h>
 #include <linux/sched.h>
 #include <linux/fs.h>
@@ -139,6 +140,52 @@ static int write_inode(struct inode *inode, int sync)
        return 0;
 }
 
+/*
+ * Redirty an inode: set its when-it-was dirtied timestamp and move it to the
+ * furthest end of its superblock's dirty-inode list.
+ *
+ * Before stamping the inode's ->dirtied_when, we check to see whether it is
+ * already the most-recently-dirtied inode on the s_dirty list.  If that is
+ * the case then the inode must have been redirtied while it was being written
+ * out and we don't reset its dirtied_when.
+ */
+static void redirty_tail(struct inode *inode)
+{
+       struct super_block *sb = inode->i_sb;
+
+       if (!list_empty(&sb->s_dirty)) {
+               struct inode *tail_inode;
+
+               tail_inode = list_entry(sb->s_dirty.next, struct inode, i_list);
+               if (!time_after_eq(inode->dirtied_when,
+                               tail_inode->dirtied_when))
+                       inode->dirtied_when = jiffies;
+       }
+       list_move(&inode->i_list, &sb->s_dirty);
+}
+
+/*
+ * Redirty an inode, but mark it as the very next-to-be-written inode on its
+ * superblock's dirty-inode list.
+ * We need to preserve s_dirty's reverse-time-orderedness, so we cheat by
+ * setting this inode's dirtied_when to the same value as that of the inode
+ * which is presently head-of-list, if present head-of-list is newer than this
+ * inode. (head-of-list is the least-recently-dirtied inode: the oldest one).
+ */
+static void redirty_head(struct inode *inode)
+{
+       struct super_block *sb = inode->i_sb;
+
+       if (!list_empty(&sb->s_dirty)) {
+               struct inode *head_inode;
+
+               head_inode = list_entry(sb->s_dirty.prev, struct inode, i_list);
+               if (time_after(inode->dirtied_when, head_inode->dirtied_when))
+                       inode->dirtied_when = head_inode->dirtied_when;
+       }
+       list_move_tail(&inode->i_list, &sb->s_dirty);
+}
+
 /*
  * Write a single inode's dirty pages and inode data out to disk.
  * If `wait' is set, wait on the writeout.
@@ -154,7 +201,6 @@ __sync_single_inode(struct inode *inode, struct writeback_control *wbc)
 {
        unsigned dirty;
        struct address_space *mapping = inode->i_mapping;
-       struct super_block *sb = inode->i_sb;
        int wait = wbc->sync_mode == WB_SYNC_ALL;
        int ret;
 
@@ -190,7 +236,16 @@ __sync_single_inode(struct inode *inode, struct writeback_control *wbc)
                        /*
                         * We didn't write back all the pages.  nfs_writepages()
                         * sometimes bales out without doing anything. Redirty
-                        * the inode.  It is still on sb->s_io.
+                        * the inode.  It is moved from s_io onto s_dirty.
+                        */
+                       /*
+                        * akpm: if the caller was the kupdate function we put
+                        * this inode at the head of s_dirty so it gets first
+                        * consideration.  Otherwise, move it to the tail, for
+                        * the reasons described there.  I'm not really sure
+                        * how much sense this makes.  Presumably I had a good
+                        * reasons for doing it this way, and I'd rather not
+                        * muck with it at present.
                         */
                        if (wbc->for_kupdate) {
                                /*
@@ -200,7 +255,7 @@ __sync_single_inode(struct inode *inode, struct writeback_control *wbc)
                                 * uncongested.
                                 */
                                inode->i_state |= I_DIRTY_PAGES;
-                               list_move_tail(&inode->i_list, &sb->s_dirty);
+                               redirty_head(inode);
                        } else {
                                /*
                                 * Otherwise fully redirty the inode so that
@@ -210,15 +265,14 @@ __sync_single_inode(struct inode *inode, struct writeback_control *wbc)
                                 * all the other files.
                                 */
                                inode->i_state |= I_DIRTY_PAGES;
-                               inode->dirtied_when = jiffies;
-                               list_move(&inode->i_list, &sb->s_dirty);
+                               redirty_tail(inode);
                        }
                } else if (inode->i_state & I_DIRTY) {
                        /*
                         * Someone redirtied the inode while were writing back
                         * the pages.
                         */
-                       list_move(&inode->i_list, &sb->s_dirty);
+                       redirty_tail(inode);
                } else if (atomic_read(&inode->i_count)) {
                        /*
                         * The inode is clean, inuse
@@ -251,8 +305,26 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
                WARN_ON(inode->i_state & I_WILL_FREE);
 
        if ((wbc->sync_mode != WB_SYNC_ALL) && (inode->i_state & I_LOCK)) {
-               list_move(&inode->i_list, &inode->i_sb->s_dirty);
-               return 0;
+               struct address_space *mapping = inode->i_mapping;
+               int ret;
+
+               /*
+                * We're skipping this inode because it's locked, and we're not
+                * doing writeback-for-data-integrity.  Move it to the head of
+                * s_dirty so that writeback can proceed with the other inodes
+                * on s_io.  We'll have another go at writing back this inode
+                * when the s_dirty iodes get moved back onto s_io.
+                */
+               redirty_head(inode);
+
+               /*
+                * Even if we don't actually write the inode itself here,
+                * we can at least start some of the data writeout..
+                */
+               spin_unlock(&inode_lock);
+               ret = do_writepages(mapping, wbc);
+               spin_lock(&inode_lock);
+               return ret;
        }
 
        /*
@@ -318,7 +390,7 @@ sync_sb_inodes(struct super_block *sb, struct writeback_control *wbc)
                long pages_skipped;
 
                if (!bdi_cap_writeback_dirty(bdi)) {
-                       list_move(&inode->i_list, &sb->s_dirty);
+                       redirty_tail(inode);
                        if (sb_is_blkdev_sb(sb)) {
                                /*
                                 * Dirty memory-backed blockdev: the ramdisk
@@ -338,14 +410,14 @@ sync_sb_inodes(struct super_block *sb, struct writeback_control *wbc)
                        wbc->encountered_congestion = 1;
                        if (!sb_is_blkdev_sb(sb))
                                break;          /* Skip a congested fs */
-                       list_move(&inode->i_list, &sb->s_dirty);
+                       redirty_head(inode);
                        continue;               /* Skip a congested blockdev */
                }
 
                if (wbc->bdi && bdi != wbc->bdi) {
                        if (!sb_is_blkdev_sb(sb))
                                break;          /* fs has the wrong queue */
-                       list_move(&inode->i_list, &sb->s_dirty);
+                       redirty_head(inode);
                        continue;               /* blockdev has wrong queue */
                }
 
@@ -377,7 +449,7 @@ sync_sb_inodes(struct super_block *sb, struct writeback_control *wbc)
                         * writeback is not making progress due to locked
                         * buffers.  Skip this inode for now.
                         */
-                       list_move(&inode->i_list, &sb->s_dirty);
+                       redirty_tail(inode);
                }
                spin_unlock(&inode_lock);
                iput(inode);