We currently record an inode's time-of-first-dirtying in the address_space. This causes a few problems with dirty block special inodes: when the device node for /dev/hda1 needs to be written back we bogusly inspect the timestamp for the address_space, which belongs to a different inode. So move the inode's dirtying time up into the inode itself. This means that for block-special inodes, inode->dirtied_when represents the time at which the inode itself was dirtied. For resular files it represents the time at which the inode or its pages were dirtied. For blockdevs, the time-of-first-dirtying is recorded in the kernel-internal blockdev inode. Only I_DIRTY_PAGES makes sense on these inodes. The reason all this works is that when dirtying a page we always run __mark_inode_dirty(page->mapping->host); which refers to the kernel-internal blockdev inode. --- 25-akpm/fs/fs-writeback.c | 22 ++++++++++++++-------- 25-akpm/fs/inode.c | 2 +- 25-akpm/include/linux/fs.h | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff -puN fs/fs-writeback.c~inode-dirtying-timestamp-fix fs/fs-writeback.c --- 25/fs/fs-writeback.c~inode-dirtying-timestamp-fix 2004-03-20 23:19:16.015202216 -0800 +++ 25-akpm/fs/fs-writeback.c 2004-03-20 23:19:16.022201152 -0800 @@ -44,6 +44,13 @@ extern struct super_block *blockdev_supe * * This function *must* be atomic for the I_DIRTY_PAGES case - * set_page_dirty() is called under spinlock in several places. + * + * Note that for blockdevs, inode->dirtied_when represents the dirtying time of + * the block-special inode (/dev/hda1) itself. And the ->dirtied_when field of + * the kernel-internal blockdev inode represents the dirtying time of the + * blockdev's pages. This is why for I_DIRTY_PAGES we always use + * page->mapping->host, so the page-dirtying time is recorded in the internal + * blockdev inode. */ void __mark_inode_dirty(struct inode *inode, int flags) { @@ -71,7 +78,6 @@ void __mark_inode_dirty(struct inode *in spin_lock(&inode_lock); if ((inode->i_state & flags) != flags) { const int was_dirty = inode->i_state & I_DIRTY; - struct address_space *mapping = inode->i_mapping; inode->i_state |= flags; @@ -99,7 +105,7 @@ void __mark_inode_dirty(struct inode *in * reposition it (that would break s_dirty time-ordering). */ if (!was_dirty) { - mapping->dirtied_when = jiffies; + inode->dirtied_when = jiffies; list_move(&inode->i_list, &sb->s_dirty); } } @@ -180,11 +186,11 @@ __sync_single_inode(struct inode *inode, } else if (!list_empty(&mapping->dirty_pages)) { /* Redirtied */ inode->i_state |= I_DIRTY_PAGES; - mapping->dirtied_when = jiffies; + inode->dirtied_when = jiffies; list_move(&inode->i_list, &sb->s_dirty); } else if (inode->i_state & I_DIRTY) { /* Redirtied */ - mapping->dirtied_when = jiffies; + inode->dirtied_when = jiffies; list_move(&inode->i_list, &sb->s_dirty); } else if (atomic_read(&inode->i_count)) { list_move(&inode->i_list, &inode_in_use); @@ -225,7 +231,7 @@ __writeback_single_inode(struct inode *i * Write out a superblock's list of dirty inodes. A wait will be performed * upon no inodes, all inodes or the final one, depending upon sync_mode. * - * If older_than_this is non-NULL, then only write out mappings which + * If older_than_this is non-NULL, then only write out inodes which * had their first dirtying at a time earlier than *older_than_this. * * If we're a pdlfush thread, then implement pdflush collision avoidance @@ -297,11 +303,11 @@ sync_sb_inodes(struct super_block *sb, s } /* Was this inode dirtied after sync_sb_inodes was called? */ - if (time_after(mapping->dirtied_when, start)) + if (time_after(inode->dirtied_when, start)) break; /* Was this inode dirtied too recently? */ - if (wbc->older_than_this && time_after(mapping->dirtied_when, + if (wbc->older_than_this && time_after(inode->dirtied_when, *wbc->older_than_this)) break; @@ -313,7 +319,7 @@ sync_sb_inodes(struct super_block *sb, s __iget(inode); __writeback_single_inode(inode, wbc); if (wbc->sync_mode == WB_SYNC_HOLD) { - mapping->dirtied_when = jiffies; + inode->dirtied_when = jiffies; list_move(&inode->i_list, &sb->s_dirty); } if (current_is_pdflush()) diff -puN fs/inode.c~inode-dirtying-timestamp-fix fs/inode.c --- 25/fs/inode.c~inode-dirtying-timestamp-fix 2004-03-20 23:19:16.016202064 -0800 +++ 25-akpm/fs/inode.c 2004-03-20 23:19:16.024200848 -0800 @@ -132,6 +132,7 @@ static struct inode *alloc_inode(struct inode->i_cdev = NULL; inode->i_rdev = 0; inode->i_security = NULL; + inode->dirtied_when = 0; if (security_inode_alloc(inode)) { if (inode->i_sb->s_op->destroy_inode) inode->i_sb->s_op->destroy_inode(inode); @@ -144,7 +145,6 @@ static struct inode *alloc_inode(struct mapping->host = inode; mapping->flags = 0; mapping_set_gfp_mask(mapping, GFP_HIGHUSER); - mapping->dirtied_when = 0; mapping->assoc_mapping = NULL; mapping->backing_dev_info = &default_backing_dev_info; if (sb->s_bdev) diff -puN include/linux/fs.h~inode-dirtying-timestamp-fix include/linux/fs.h --- 25/include/linux/fs.h~inode-dirtying-timestamp-fix 2004-03-20 23:19:16.019201608 -0800 +++ 25-akpm/include/linux/fs.h 2004-03-20 23:19:16.026200544 -0800 @@ -336,7 +336,6 @@ struct address_space { struct list_head i_mmap_shared; /* list of shared mappings */ struct semaphore i_shared_sem; /* protect both above lists */ atomic_t truncate_count; /* Cover race condition with truncate */ - unsigned long dirtied_when; /* jiffies of first page dirtying */ unsigned long flags; /* error bits/gfp mask */ struct backing_dev_info *backing_dev_info; /* device readahead, etc */ spinlock_t private_lock; /* for use by the address_space */ @@ -421,6 +420,7 @@ struct inode { struct dnotify_struct *i_dnotify; /* for directory notifications */ unsigned long i_state; + unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned int i_flags; unsigned char i_sock; _