Skip to contentSkip to conversation

vfs scalability patches updated

PATCH In review [email protected] 15 participants 153 messages Jun 24, 2010 – Jul 7, 2010 Page 2 of 2

Subsystems (70)
lore
Share & tools
lore
maintainer
#86 lore raw

Re: [patch 30/52] fs: icache lock lru/writeback lists

In reply to Nick Piggin
Quoted 9 lines · Nick Piggin
Quoted 4 lines · Peter Zijlstra
Quoted 1 line · On Thu, 2010-06-24 at 13:02 +1000, [email protected] wrote:

+ assert_spin_locked(&wb_inode_list_lock);

There's also lockdep_assert_held() which also validates we're the owner.

These locks should have such miniscule contention now that they effectively mean the same thing :) But no that's a good suggestion thanks. I guess _most_ assert_spin_locked could be changed over.

Probably, I just haven't felt like actually visiting all sites to check ;-)

#87 lore raw

PATCH 31/52 fs: icache atomic inodes_stat

In reply to [email protected]

Protect inodes_stat statistics with atomic ops rather than inode_lock.

---
 fs/fs-writeback.c  |    6 ++++--
 fs/inode.c         |   28 +++++++++++++++-------------
 include/linux/fs.h |    5 +++--
 3 files changed, 22 insertions(+), 17 deletions(-)
fs/fs-writeback.c +4 -2
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -924,7 +924,8 @@ static long wb_check_old_data_flush(stru
 	wb->last_old_flush = jiffies;
 	nr_pages = global_page_state(NR_FILE_DIRTY) +
 			global_page_state(NR_UNSTABLE_NFS) +
-			(inodes_stat.nr_inodes - inodes_stat.nr_unused);
+			(atomic_read(&inodes_stat.nr_inodes) -
+			atomic_read(&inodes_stat.nr_unused));
 
 	if (nr_pages) {
 		struct wb_writeback_args args = {
@@ -1285,7 +1286,8 @@ void writeback_inodes_sb(struct super_bl
 	long nr_to_write;
 
 	nr_to_write = nr_dirty + nr_unstable +
-			(inodes_stat.nr_inodes - inodes_stat.nr_unused);
+			(atomic_read(&inodes_stat.nr_inodes) -
+			atomic_read(&inodes_stat.nr_unused));
 
 	bdi_start_writeback(sb->s_bdi, sb, nr_to_write);
 }
fs/inode.c +15 -13
Index: linux-2.6/fs/inode.c
===================================================================
@@ -123,7 +123,10 @@ static DECLARE_RWSEM(iprune_sem);
 /*
  * Statistics gathering..
  */
-struct inodes_stat_t inodes_stat;
+struct inodes_stat_t inodes_stat = {
+	.nr_inodes = ATOMIC_INIT(0),
+	.nr_unused = ATOMIC_INIT(0),
+};
 
 static struct kmem_cache *inode_cachep __read_mostly;
 
@@ -318,7 +321,7 @@ void __iget(struct inode *inode)
 		list_move(&inode->i_list, &inode_in_use);
 		spin_unlock(&wb_inode_list_lock);
 	}
-	inodes_stat.nr_unused--;
+	atomic_dec(&inodes_stat.nr_unused);
 }
 
 /**
@@ -382,9 +385,7 @@ static void dispose_list(struct list_hea
 		destroy_inode(inode);
 		nr_disposed++;
 	}
-	spin_lock(&inode_lock);
-	inodes_stat.nr_inodes -= nr_disposed;
-	spin_unlock(&inode_lock);
+	atomic_sub(nr_disposed, &inodes_stat.nr_inodes);
 }
 
 /*
@@ -433,7 +434,7 @@ static int invalidate_list(struct list_h
 		busy = 1;
 	}
 	/* only unused inodes may be cached with i_count zero */
-	inodes_stat.nr_unused -= count;
+	atomic_sub(count, &inodes_stat.nr_unused);
 	return busy;
 }
 
@@ -551,7 +552,7 @@ again2:
 		spin_unlock(&inode->i_lock);
 		nr_pruned++;
 	}
-	inodes_stat.nr_unused -= nr_pruned;
+	atomic_sub(nr_pruned, &inodes_stat.nr_unused);
 	if (current_is_kswapd())
 		__count_vm_events(KSWAPD_INODESTEAL, reap);
 	else
@@ -584,7 +585,8 @@ static int shrink_icache_memory(int nr,
 			return -1;
 		prune_icache(nr);
 	}
-	return (inodes_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
+	return (atomic_read(&inodes_stat.nr_unused) / 100) *
+					sysctl_vfs_cache_pressure;
 }
 
 static struct shrinker icache_shrinker = {
@@ -677,7 +679,7 @@ static inline void
 __inode_add_to_lists(struct super_block *sb, struct hlist_head *head,
 			struct inode *inode)
 {
-	inodes_stat.nr_inodes++;
+	atomic_inc(&inodes_stat.nr_inodes);
 	spin_lock(&sb_inode_list_lock);
 	list_add(&inode->i_sb_list, &sb->s_inodes);
 	spin_unlock(&sb_inode_list_lock);
@@ -1321,8 +1323,8 @@ void generic_delete_inode(struct inode *
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
 	spin_unlock(&inode->i_lock);
-	inodes_stat.nr_inodes--;
 	spin_unlock(&inode_lock);
+	atomic_dec(&inodes_stat.nr_inodes);
 
 	if (op->delete_inode) {
 		void (*delete)(struct inode *) = op->delete_inode;
@@ -1365,7 +1367,7 @@ int generic_detach_inode(struct inode *i
 			list_move(&inode->i_list, &inode_unused);
 			spin_unlock(&wb_inode_list_lock);
 		}
-		inodes_stat.nr_unused++;
+		atomic_inc(&inodes_stat.nr_unused);
 		if (sb->s_flags & MS_ACTIVE) {
 			spin_unlock(&inode->i_lock);
 			spin_unlock(&sb_inode_list_lock);
@@ -1383,7 +1385,7 @@ int generic_detach_inode(struct inode *i
 		spin_lock(&inode->i_lock);
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state &= ~I_WILL_FREE;
-		inodes_stat.nr_unused--;
+		atomic_dec(&inodes_stat.nr_unused);
 		spin_lock(&inode_hash_lock);
 		hlist_del_init(&inode->i_hash);
 		spin_unlock(&inode_hash_lock);
@@ -1395,9 +1397,9 @@ int generic_detach_inode(struct inode *i
 	spin_unlock(&sb_inode_list_lock);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
-	inodes_stat.nr_inodes--;
 	spin_unlock(&inode->i_lock);
 	spin_unlock(&inode_lock);
+	atomic_dec(&inodes_stat.nr_inodes);
 	return 1;
 }
 EXPORT_SYMBOL_GPL(generic_detach_inode);
include/linux/fs.h +6 -7
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -38,13 +38,6 @@ struct files_stat_struct {
 	int max_files;		/* tunable */
 };
 
-struct inodes_stat_t {
-	int nr_inodes;
-	int nr_unused;
-	int dummy[5];		/* padding for sysctl ABI compatibility */
-};
-
-
 #define NR_FILE  8192	/* this can well be larger on a larger system */
 
 #define MAY_EXEC 1
@@ -418,6 +411,12 @@ typedef int (get_block_t)(struct inode *
 typedef void (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 			ssize_t bytes, void *private);
 
+struct inodes_stat_t {
+	atomic_t nr_inodes;
+	atomic_t nr_unused;
+	int dummy[5];		/* padding for sysctl ABI compatibility */
+};
+
 /*
  * Attribute flags.  These should be or-ed together to figure out what
  * has been changed!
drivers/staging/pohmelfs/inode.c +5 -5
Index: linux-2.6/drivers/staging/pohmelfs/inode.c
===================================================================
@@ -1283,11 +1283,11 @@ static void pohmelfs_put_super(struct su
 		dprintk("%s: ino: %llu, pi: %p, inode: %p, count: %u.\n",
 				__func__, pi->ino, pi, inode, count);
 
-		if (atomic_read(&inode->i_count) != count) {
+		if (inode->i_count != count) {
 			printk("%s: ino: %llu, pi: %p, inode: %p, count: %u, i_count: %d.\n",
 					__func__, pi->ino, pi, inode, count,
-					atomic_read(&inode->i_count));
-			count = atomic_read(&inode->i_count);
+					inode->i_count);
+			count = inode->i_count;
 			in_drop_list++;
 		}
 
@@ -1299,7 +1299,7 @@ static void pohmelfs_put_super(struct su
 		pi = POHMELFS_I(inode);
 
 		dprintk("%s: ino: %llu, pi: %p, inode: %p, i_count: %u.\n",
-				__func__, pi->ino, pi, inode, atomic_read(&inode->i_count));
+				__func__, pi->ino, pi, inode, inode->i_count);
 
 		/*
 		 * These are special inodes, they were created during
@@ -1307,7 +1307,7 @@ static void pohmelfs_put_super(struct su
 		 * so they live here with reference counter being 1 and prevent
 		 * umount from succeed since it believes that they are busy.
 		 */
-		count = atomic_read(&inode->i_count);
+		count = inode->i_count;
 		if (count) {
 			list_del_init(&inode->i_sb_list);
 			while (count--)
fs/btrfs/inode.c +9 -4
Index: linux-2.6/fs/btrfs/inode.c
===================================================================
@@ -1964,8 +1964,13 @@ void btrfs_add_delayed_iput(struct inode
 	struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info;
 	struct delayed_iput *delayed;
 
-	if (atomic_add_unless(&inode->i_count, -1, 1))
+	spin_lock(&inode->i_lock);
+	if (inode->i_count > 1) {
+		inode->i_count--;
+		spin_unlock(&inode->i_lock);
 		return;
+	}
+	spin_unlock(&inode->i_lock);
 
 	delayed = kmalloc(sizeof(*delayed), GFP_NOFS | __GFP_NOFAIL);
 	delayed->inode = inode;
@@ -2718,10 +2723,10 @@ static struct btrfs_trans_handle *__unli
 		return ERR_PTR(-ENOSPC);
 
 	/* check if there is someone else holds reference */
-	if (S_ISDIR(inode->i_mode) && atomic_read(&inode->i_count) > 1)
+	if (S_ISDIR(inode->i_mode) && inode->i_count > 1)
 		return ERR_PTR(-ENOSPC);
 
-	if (atomic_read(&inode->i_count) > 2)
+	if (inode->i_count > 2)
 		return ERR_PTR(-ENOSPC);
 
 	if (xchg(&root->fs_info->enospc_unlink, 1))
@@ -3934,7 +3939,7 @@ again:
 		inode = igrab(&entry->vfs_inode);
 		if (inode) {
 			spin_unlock(&root->inode_lock);
-			if (atomic_read(&inode->i_count) > 1)
+			if (inode->i_count > 1)
 				d_prune_aliases(inode);
 			/*
 			 * btrfs_drop_inode will remove it from
fs/ceph/mds_client.c +1 -1
Index: linux-2.6/fs/ceph/mds_client.c
===================================================================
@@ -1028,7 +1028,7 @@ static int trim_caps_cb(struct inode *in
 		spin_unlock(&inode->i_lock);
 		d_prune_aliases(inode);
 		dout("trim_caps_cb %p cap %p  pruned, count now %d\n",
-		     inode, cap, atomic_read(&inode->i_count));
+		     inode, cap, inode->i_count);
 		return 0;
 	}
 
fs/logfs/dir.c +3 -1
Index: linux-2.6/fs/logfs/dir.c
===================================================================
@@ -566,7 +566,9 @@ static int logfs_link(struct dentry *old
 		return -EMLINK;
 
 	inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
-	atomic_inc(&inode->i_count);
+	spin_lock(&inode->i_lock);
+	inode->i_count++;
+	spin_unlock(&inode->i_lock);
 	inode->i_nlink++;
 	mark_inode_dirty_sync(inode);
 
fs/logfs/readwrite.c +1 -1
Index: linux-2.6/fs/logfs/readwrite.c
===================================================================
@@ -1002,7 +1002,7 @@ static int __logfs_is_valid_block(struct
 {
 	struct logfs_inode *li = logfs_inode(inode);
 
-	if ((inode->i_nlink == 0) && atomic_read(&inode->i_count) == 1)
+	if (inode->i_nlink == 0 && inode->i_count == 1)
 		return 0;
 
 	if (bix < I0_BLOCKS)
#88 lore raw

PATCH 32/52 fs: icache protect inode state

In reply to [email protected]

Protect i_hash, i_sb_list etc members with i_lock.

---
 fs/hugetlbfs/inode.c |    1 +
 fs/inode.c           |   29 ++++++++++++++++++++++++++---
 2 files changed, 27 insertions(+), 3 deletions(-)
fs/inode.c +29 -4
Index: linux-2.6/fs/inode.c
===================================================================
@@ -35,7 +35,11 @@
  * wb_inode_list_lock protects:
  *   inode_in_use, inode_unused, b_io, b_more_io, b_dirty, i_list
  * inode->i_lock protects:
- *   i_state, i_count
+ *   i_state
+ *   i_count
+ *   i_hash
+ *   i_list
+ *   i_sb_list
  *
  * Ordering:
  * inode_lock
@@ -373,12 +377,14 @@ static void dispose_list(struct list_hea
 		clear_inode(inode);
 
 		spin_lock(&inode_lock);
+		spin_lock(&sb_inode_list_lock);
+		spin_lock(&inode->i_lock);
 		spin_lock(&inode_hash_lock);
 		hlist_del_init(&inode->i_hash);
 		spin_unlock(&inode_hash_lock);
-		spin_lock(&sb_inode_list_lock);
 		list_del_init(&inode->i_sb_list);
 		spin_unlock(&sb_inode_list_lock);
+		spin_unlock(&inode->i_lock);
 		spin_unlock(&inode_lock);
 
 		wake_up_inode(inode);
@@ -680,7 +686,6 @@ __inode_add_to_lists(struct super_block
 			struct inode *inode)
 {
 	atomic_inc(&inodes_stat.nr_inodes);
-	spin_lock(&sb_inode_list_lock);
 	list_add(&inode->i_sb_list, &sb->s_inodes);
 	spin_unlock(&sb_inode_list_lock);
 	spin_lock(&wb_inode_list_lock);
@@ -710,7 +715,10 @@ void inode_add_to_lists(struct super_blo
 	struct hlist_head *head = inode_hashtable + hash(sb, inode->i_ino);
 
 	spin_lock(&inode_lock);
+	spin_lock(&sb_inode_list_lock);
+	spin_lock(&inode->i_lock);
 	__inode_add_to_lists(sb, head, inode);
+	spin_unlock(&inode->i_lock);
 	spin_unlock(&inode_lock);
 }
 EXPORT_SYMBOL_GPL(inode_add_to_lists);
@@ -742,9 +750,12 @@ struct inode *new_inode(struct super_blo
 	inode = alloc_inode(sb);
 	if (inode) {
 		spin_lock(&inode_lock);
+		spin_lock(&sb_inode_list_lock);
+		spin_lock(&inode->i_lock);
 		inode->i_ino = ++last_ino;
 		inode->i_state = 0;
 		__inode_add_to_lists(sb, NULL, inode);
+		spin_unlock(&inode->i_lock);
 		spin_unlock(&inode_lock);
 	}
 	return inode;
@@ -808,11 +819,14 @@ static struct inode *get_new_inode(struc
 		/* We released the lock, so.. */
 		old = find_inode(sb, head, test, data);
 		if (!old) {
+			spin_lock(&sb_inode_list_lock);
+			spin_lock(&inode->i_lock);
 			if (set(inode, data))
 				goto set_failed;
 
 			inode->i_state = I_NEW;
 			__inode_add_to_lists(sb, head, inode);
+			spin_unlock(&inode->i_lock);
 			spin_unlock(&inode_lock);
 
 			/* Return the locked inode with I_NEW set, the
@@ -837,6 +851,7 @@ static struct inode *get_new_inode(struc
 
 set_failed:
 	spin_unlock(&inode->i_lock);
+	spin_unlock(&sb_inode_list_lock);
 	spin_unlock(&inode_lock);
 	destroy_inode(inode);
 	return NULL;
@@ -859,9 +874,12 @@ static struct inode *get_new_inode_fast(
 		/* We released the lock, so.. */
 		old = find_inode_fast(sb, head, ino);
 		if (!old) {
+			spin_lock(&sb_inode_list_lock);
+			spin_lock(&inode->i_lock);
 			inode->i_ino = ino;
 			inode->i_state = I_NEW;
 			__inode_add_to_lists(sb, head, inode);
+			spin_unlock(&inode->i_lock);
 			spin_unlock(&inode_lock);
 
 			/* Return the locked inode with I_NEW set, the
@@ -1275,10 +1293,13 @@ EXPORT_SYMBOL(insert_inode_locked4);
 void __insert_inode_hash(struct inode *inode, unsigned long hashval)
 {
 	struct hlist_head *head = inode_hashtable + hash(inode->i_sb, hashval);
+
 	spin_lock(&inode_lock);
+	spin_lock(&inode->i_lock);
 	spin_lock(&inode_hash_lock);
 	hlist_add_head(&inode->i_hash, head);
 	spin_unlock(&inode_hash_lock);
+	spin_unlock(&inode->i_lock);
 	spin_unlock(&inode_lock);
 }
 EXPORT_SYMBOL(__insert_inode_hash);
@@ -1292,9 +1313,11 @@ EXPORT_SYMBOL(__insert_inode_hash);
 void remove_inode_hash(struct inode *inode)
 {
 	spin_lock(&inode_lock);
+	spin_lock(&inode->i_lock);
 	spin_lock(&inode_hash_lock);
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_hash_lock);
+	spin_unlock(&inode->i_lock);
 	spin_unlock(&inode_lock);
 }
 EXPORT_SYMBOL(remove_inode_hash);
@@ -1338,9 +1361,11 @@ void generic_delete_inode(struct inode *
 		clear_inode(inode);
 	}
 	spin_lock(&inode_lock);
+	spin_lock(&inode->i_lock);
 	spin_lock(&inode_hash_lock);
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_hash_lock);
+	spin_unlock(&inode->i_lock);
 	spin_unlock(&inode_lock);
 	wake_up_inode(inode);
 	BUG_ON(inode->i_state != I_CLEAR);
@@ -1385,10 +1410,10 @@ int generic_detach_inode(struct inode *i
 		spin_lock(&inode->i_lock);
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state &= ~I_WILL_FREE;
-		atomic_dec(&inodes_stat.nr_unused);
 		spin_lock(&inode_hash_lock);
 		hlist_del_init(&inode->i_hash);
 		spin_unlock(&inode_hash_lock);
+		atomic_dec(&inodes_stat.nr_unused);
 	}
 	spin_lock(&wb_inode_list_lock);
 	list_del_init(&inode->i_list);
fs/hugetlbfs/inode.c +1 -0
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
@@ -382,6 +382,7 @@ static void hugetlbfs_forget_inode(struc
 	if (generic_detach_inode(inode)) {
 		truncate_hugepages(inode, 0);
 		clear_inode(inode);
+		/* XXX: why no wake_up_inode? */
 		destroy_inode(inode);
 	}
 }
#89 lore raw

PATCH 33/52 fs: icache atomic last_ino, iunique lock

In reply to [email protected]

Make last_ino atomic in preperation for removing inode_lock. Make a new lock for iunique counter, for removing inode_lock.

---
 fs/inode.c |   28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)
fs/inode.c +22 -6
Index: linux-2.6/fs/inode.c
===================================================================
@@ -742,7 +742,7 @@ struct inode *new_inode(struct super_blo
 	 * error if st_ino won't fit in target struct field. Use 32bit counter
 	 * here to attempt to avoid that.
 	 */
-	static unsigned int last_ino;
+	static atomic_t last_ino = ATOMIC_INIT(0);
 	struct inode *inode;
 
 	spin_lock_prefetch(&inode_lock);
@@ -752,7 +752,7 @@ struct inode *new_inode(struct super_blo
 		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 		spin_lock(&inode->i_lock);
-		inode->i_ino = ++last_ino;
+		inode->i_ino = atomic_inc_return(&last_ino);
 		inode->i_state = 0;
 		__inode_add_to_lists(sb, NULL, inode);
 		spin_unlock(&inode->i_lock);
@@ -903,6 +903,22 @@ static struct inode *get_new_inode_fast(
 	return inode;
 }
 
+static int test_inode_iunique(struct super_block * sb, struct hlist_head *head, unsigned long ino)
+{
+	struct hlist_node *node;
+	struct inode * inode = NULL;
+
+	spin_lock(&inode_hash_lock);
+	hlist_for_each_entry(inode, node, head, i_hash) {
+		if (inode->i_ino == ino && inode->i_sb == sb) {
+			spin_unlock(&inode_hash_lock);
+			return 0;
+		}
+	}
+	spin_unlock(&inode_hash_lock);
+	return 1;
+}
+
 /**
  *	iunique - get a unique inode number
  *	@sb: superblock
@@ -924,20 +940,20 @@ ino_t iunique(struct super_block *sb, in
 	 * error if st_ino won't fit in target struct field. Use 32bit counter
 	 * here to attempt to avoid that.
 	 */
+	static DEFINE_SPINLOCK(unique_lock);
 	static unsigned int counter;
-	struct inode *inode;
 	struct hlist_head *head;
 	ino_t res;
 
 	spin_lock(&inode_lock);
+	spin_lock(&unique_lock);
 	do {
 		if (counter <= max_reserved)
 			counter = max_reserved + 1;
 		res = counter++;
 		head = inode_hashtable + hash(sb, res);
-		inode = find_inode_fast(sb, head, res);
-		spin_unlock(&inode->i_lock);
-	} while (inode != NULL);
+	} while (!test_inode_iunique(sb, head, res));
+	spin_unlock(&unique_lock);
 	spin_unlock(&inode_lock);
 
 	return res;
#90 lore raw

PATCH 34/52 fs: icache remove inode_lock

In reply to [email protected]

Remove the global inode_lock, as it doesn't protect anything.

---
 fs/buffer.c                 |    2 -
 fs/drop_caches.c            |    4 --
 fs/fs-writeback.c           |   35 ++++-------------------
 fs/hugetlbfs/inode.c        |    2 -
 fs/inode.c                  |   65 ++------------------------------------------
 fs/notify/inode_mark.c      |   14 +++++----
 fs/notify/inotify/inotify.c |   16 ++++++----
 fs/quota/dquot.c            |    6 ----
 include/linux/writeback.h   |    1 
 mm/backing-dev.c            |    4 --
 10 files changed, 30 insertions(+), 119 deletions(-)
fs/buffer.c +1 -1
Index: linux-2.6/fs/buffer.c
===================================================================
@@ -1144,7 +1144,7 @@ __getblk_slow(struct block_device *bdev,
  * inode list.
  *
  * mark_buffer_dirty() is atomic.  It takes bh->b_page->mapping->private_lock,
- * mapping->tree_lock and the global inode_lock.
+ * and mapping->tree_lock.
  */
 void mark_buffer_dirty(struct buffer_head *bh)
 {
fs/drop_caches.c +0 -4
Index: linux-2.6/fs/drop_caches.c
===================================================================
@@ -16,7 +16,6 @@ static void drop_pagecache_sb(struct sup
 {
 	struct inode *inode, *toput_inode = NULL;
 
-	spin_lock(&inode_lock);
 	spin_lock(&sb_inode_list_lock);
 	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
 		spin_lock(&inode->i_lock);
@@ -28,15 +27,12 @@ static void drop_pagecache_sb(struct sup
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
 		spin_unlock(&sb_inode_list_lock);
-		spin_unlock(&inode_lock);
 		invalidate_mapping_pages(inode->i_mapping, 0, -1);
 		iput(toput_inode);
 		toput_inode = inode;
-		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 	}
 	spin_unlock(&sb_inode_list_lock);
-	spin_unlock(&inode_lock);
 	iput(toput_inode);
 }
 
fs/fs-writeback.c +6 -30
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -312,7 +312,7 @@ static void requeue_io(struct inode *ino
 static void inode_sync_complete(struct inode *inode)
 {
 	/*
-	 * Prevent speculative execution through spin_unlock(&inode_lock);
+	 * Prevent speculative execution through spin_unlock(&inode->i_lock);
 	 */
 	smp_mb();
 	wake_up_bit(&inode->i_state, __I_SYNC);
@@ -404,9 +404,7 @@ static void inode_wait_for_writeback(str
 	while (inode->i_state & I_SYNC) {
 		spin_unlock(&wb_inode_list_lock);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&inode_lock);
 		__wait_on_bit(wqh, &wq, inode_wait, TASK_UNINTERRUPTIBLE);
-		spin_lock(&inode_lock);
 		spin_lock(&inode->i_lock);
 		spin_lock(&wb_inode_list_lock);
 	}
@@ -464,7 +462,6 @@ writeback_single_inode(struct inode *ino
 	inode->i_state &= ~I_DIRTY_PAGES;
 	spin_unlock(&wb_inode_list_lock);
 	spin_unlock(&inode->i_lock);
-	spin_unlock(&inode_lock);
 
 	ret = do_writepages(mapping, wbc);
 
@@ -484,12 +481,10 @@ writeback_single_inode(struct inode *ino
 	 * due to delalloc, clear dirty metadata flags right before
 	 * write_inode()
 	 */
-	spin_lock(&inode_lock);
 	spin_lock(&inode->i_lock);
 	dirty = inode->i_state & I_DIRTY;
 	inode->i_state &= ~(I_DIRTY_SYNC | I_DIRTY_DATASYNC);
 	spin_unlock(&inode->i_lock);
-	spin_unlock(&inode_lock);
 	/* Don't write the inode if only I_DIRTY_PAGES was set */
 	if (dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) {
 		int err = write_inode(inode, wbc);
@@ -497,7 +492,6 @@ writeback_single_inode(struct inode *ino
 			ret = err;
 	}
 
-	spin_lock(&inode_lock);
 	spin_lock(&inode->i_lock);
 	spin_lock(&wb_inode_list_lock);
 	inode->i_state &= ~I_SYNC;
@@ -679,10 +673,8 @@ again:
 		}
 		spin_unlock(&wb_inode_list_lock);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&inode_lock);
 		iput(inode);
 		cond_resched();
-		spin_lock(&inode_lock);
 		spin_lock(&wb_inode_list_lock);
 		if (wbc->nr_to_write <= 0) {
 			wbc->more_io = 1;
@@ -701,7 +693,6 @@ static void writeback_inodes_wb(struct b
 	int ret = 0;
 
 	wbc->wb_start = jiffies; /* livelock avoidance */
-	spin_lock(&inode_lock);
 again:
 	spin_lock(&wb_inode_list_lock);
 
@@ -744,7 +735,6 @@ again:
 			break;
 	}
 	spin_unlock(&wb_inode_list_lock);
-	spin_unlock(&inode_lock);
 	/* Leave any unwritten inodes on b_io */
 }
 
@@ -857,21 +847,18 @@ static long wb_writeback(struct bdi_writ
 		 * we'll just busyloop.
 		 */
 retry:
-		spin_lock(&inode_lock);
 		spin_lock(&wb_inode_list_lock);
 		if (!list_empty(&wb->b_more_io))  {
 			inode = list_entry(wb->b_more_io.prev,
 						struct inode, i_list);
 			if (!spin_trylock(&inode->i_lock)) {
 				spin_unlock(&wb_inode_list_lock);
-				spin_unlock(&inode_lock);
 				goto retry;
 			}
 			inode_wait_for_writeback(inode);
 			spin_unlock(&inode->i_lock);
 		}
 		spin_unlock(&wb_inode_list_lock);
-		spin_unlock(&inode_lock);
 	}
 
 	return wrote;
@@ -1141,7 +1128,6 @@ void __mark_inode_dirty(struct inode *in
 	if (unlikely(block_dump))
 		block_dump___mark_inode_dirty(inode);
 
-	spin_lock(&inode_lock);
 	spin_lock(&inode->i_lock);
 	if ((inode->i_state & flags) != flags) {
 		const int was_dirty = inode->i_state & I_DIRTY;
@@ -1190,7 +1176,6 @@ void __mark_inode_dirty(struct inode *in
 	}
 out:
 	spin_unlock(&inode->i_lock);
-	spin_unlock(&inode_lock);
 }
 EXPORT_SYMBOL(__mark_inode_dirty);
 
@@ -1221,7 +1206,6 @@ static void wait_sb_inodes(struct super_
 	 */
 	WARN_ON(!rwsem_is_locked(&sb->s_umount));
 
-	spin_lock(&inode_lock);
 	spin_lock(&sb_inode_list_lock);
 
 	/*
@@ -1246,14 +1230,12 @@ static void wait_sb_inodes(struct super_
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
 		spin_unlock(&sb_inode_list_lock);
-		spin_unlock(&inode_lock);
 		/*
-		 * We hold a reference to 'inode' so it couldn't have
-		 * been removed from s_inodes list while we dropped the
-		 * inode_lock.  We cannot iput the inode now as we can
-		 * be holding the last reference and we cannot iput it
-		 * under inode_lock. So we keep the reference and iput
-		 * it later.
+		 * We hold a reference to 'inode' so it couldn't have been
+		 * removed from s_inodes list while we dropped the
+		 * sb_inode_list_lock.  We cannot iput the inode now as we can
+		 * be holding the last reference and we cannot iput it under
+		 * spinlock. So we keep the reference and iput it later.
 		 */
 		iput(old_inode);
 		old_inode = inode;
@@ -1262,11 +1244,9 @@ static void wait_sb_inodes(struct super_
 
 		cond_resched();
 
-		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 	}
 	spin_unlock(&sb_inode_list_lock);
-	spin_unlock(&inode_lock);
 	iput(old_inode);
 }
 
@@ -1348,13 +1328,11 @@ int write_inode_now(struct inode *inode,
 		wbc.nr_to_write = 0;
 
 	might_sleep();
-	spin_lock(&inode_lock);
 	spin_lock(&inode->i_lock);
 	spin_lock(&wb_inode_list_lock);
 	ret = writeback_single_inode(inode, &wbc);
 	spin_unlock(&wb_inode_list_lock);
 	spin_unlock(&inode->i_lock);
-	spin_unlock(&inode_lock);
 	if (sync)
 		inode_sync_wait(inode);
 	return ret;
@@ -1376,13 +1354,11 @@ int sync_inode(struct inode *inode, stru
 {
 	int ret;
 
-	spin_lock(&inode_lock);
 	spin_lock(&inode->i_lock);
 	spin_lock(&wb_inode_list_lock);
 	ret = writeback_single_inode(inode, wbc);
 	spin_unlock(&wb_inode_list_lock);
 	spin_unlock(&inode->i_lock);
-	spin_unlock(&inode_lock);
 	return ret;
 }
 EXPORT_SYMBOL(sync_inode);
fs/hugetlbfs/inode.c +1 -1
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
@@ -377,7 +377,7 @@ static void hugetlbfs_delete_inode(struc
 	clear_inode(inode);
 }
 
-static void hugetlbfs_forget_inode(struct inode *inode) __releases(inode_lock)
+static void hugetlbfs_forget_inode(struct inode *inode)
 {
 	if (generic_detach_inode(inode)) {
 		truncate_hugepages(inode, 0);
fs/inode.c +4 -61

349 lines not on this page · raw message

fs/notify/inotify/inotify.c +10 -6
Index: linux-2.6/fs/notify/inotify/inotify.c
===================================================================
@@ -390,13 +390,16 @@ void inotify_unmount_inodes(struct list_
 		struct inode *need_iput_tmp;
 		struct list_head *watches;
 
+		spin_lock(&inode->i_lock);
 		/*
 		 * We cannot __iget() an inode in state I_CLEAR, I_FREEING,
 		 * I_WILL_FREE, or I_NEW which is fine because by that point
 		 * the inode cannot have any associated watches.
 		 */
-		if (inode->i_state & (I_CLEAR|I_FREEING|I_WILL_FREE|I_NEW))
+		if (inode->i_state & (I_CLEAR|I_FREEING|I_WILL_FREE|I_NEW)) {
+			spin_unlock(&inode->i_lock);
 			continue;
+		}
 
 		/*
 		 * If i_count is zero, the inode cannot have any watches and
@@ -404,18 +407,21 @@ void inotify_unmount_inodes(struct list_
 		 * evict all inodes with zero i_count from icache which is
 		 * unnecessarily violent and may in fact be illegal to do.
 		 */
-		if (!inode->i_count)
+		if (!inode->i_count) {
+			spin_unlock(&inode->i_lock);
 			continue;
+		}
 
 		need_iput_tmp = need_iput;
 		need_iput = NULL;
 		/* In case inotify_remove_watch_locked() drops a reference. */
 		if (inode != need_iput_tmp) {
-			spin_lock(&inode->i_lock);
 			__iget(inode);
-			spin_unlock(&inode->i_lock);
 		} else
 			need_iput_tmp = NULL;
+
+		spin_unlock(&inode->i_lock);
+
 		/* In case the dropping of a reference would nuke next_i. */
 		if (&next_i->i_sb_list != list) {
 			spin_lock(&next_i->i_lock);
@@ -435,7 +441,6 @@ void inotify_unmount_inodes(struct list_
 		 * iprune_mutex keeps shrink_icache_memory() away.
 		 */
 		spin_unlock(&sb_inode_list_lock);
-		spin_unlock(&inode_lock);
 
 		if (need_iput_tmp)
 			iput(need_iput_tmp);
@@ -455,7 +460,6 @@ void inotify_unmount_inodes(struct list_
 		mutex_unlock(&inode->inotify_mutex);
 		iput(inode);		
 
-		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 	}
 }
include/linux/writeback.h +0 -1
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -9,7 +9,6 @@
 
 struct backing_dev_info;
 
-extern spinlock_t inode_lock;
 extern spinlock_t sb_inode_list_lock;
 extern spinlock_t wb_inode_list_lock;
 extern spinlock_t inode_hash_lock;
fs/quota/dquot.c +0 -6
Index: linux-2.6/fs/quota/dquot.c
===================================================================
@@ -883,7 +883,6 @@ static void add_dquot_ref(struct super_b
 	int reserved = 0;
 #endif
 
-	spin_lock(&inode_lock);
 	spin_lock(&sb_inode_list_lock);
 	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
 		spin_lock(&inode->i_lock);
@@ -907,7 +906,6 @@ static void add_dquot_ref(struct super_b
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
 		spin_unlock(&sb_inode_list_lock);
-		spin_unlock(&inode_lock);
 
 		iput(old_inode);
 		__dquot_initialize(inode, type);
@@ -917,11 +915,9 @@ static void add_dquot_ref(struct super_b
 		 * reference and we cannot iput it under inode_lock. So we
 		 * keep the reference and iput it later. */
 		old_inode = inode;
-		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 	}
 	spin_unlock(&sb_inode_list_lock);
-	spin_unlock(&inode_lock);
 	iput(old_inode);
 
 #ifdef CONFIG_QUOTA_DEBUG
@@ -999,7 +995,6 @@ static void remove_dquot_ref(struct supe
 {
 	struct inode *inode;
 
-	spin_lock(&inode_lock);
 	spin_lock(&sb_inode_list_lock);
 	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
 		/*
@@ -1012,7 +1007,6 @@ static void remove_dquot_ref(struct supe
 			remove_inode_dquot_ref(inode, type, tofree_head);
 	}
 	spin_unlock(&sb_inode_list_lock);
-	spin_unlock(&inode_lock);
 }
 
 /* Gather all references from inodes and drop them */
fs/notify/inode_mark.c +8 -6
Index: linux-2.6/fs/notify/inode_mark.c
===================================================================
@@ -368,13 +368,16 @@ void fsnotify_unmount_inodes(struct list
 	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
 		struct inode *need_iput_tmp;
 
+		spin_lock(&inode->i_lock);
 		/*
 		 * We cannot __iget() an inode in state I_CLEAR, I_FREEING,
 		 * I_WILL_FREE, or I_NEW which is fine because by that point
 		 * the inode cannot have any associated watches.
 		 */
-		if (inode->i_state & (I_CLEAR|I_FREEING|I_WILL_FREE|I_NEW))
+		if (inode->i_state & (I_CLEAR|I_FREEING|I_WILL_FREE|I_NEW)) {
+			spin_unlock(&inode->i_lock);
 			continue;
+		}
 
 		/*
 		 * If i_count is zero, the inode cannot have any watches and
@@ -382,19 +385,20 @@ void fsnotify_unmount_inodes(struct list
 		 * evict all inodes with zero i_count from icache which is
 		 * unnecessarily violent and may in fact be illegal to do.
 		 */
-		if (!inode->i_count)
+		if (!inode->i_count) {
+			spin_unlock(&inode->i_lock);
 			continue;
+		}
 
 		need_iput_tmp = need_iput;
 		need_iput = NULL;
 
 		/* In case fsnotify_inode_delete() drops a reference. */
 		if (inode != need_iput_tmp) {
-			spin_lock(&inode->i_lock);
 			__iget(inode);
-			spin_unlock(&inode->i_lock);
 		} else
 			need_iput_tmp = NULL;
+		spin_unlock(&inode->i_lock);
 
 		/* In case the dropping of a reference would nuke next_i. */
 		if (&next_i->i_sb_list != list) {
@@ -415,7 +419,6 @@ void fsnotify_unmount_inodes(struct list
 		 * iprune_mutex keeps shrink_icache_memory() away.
 		 */
 		spin_unlock(&sb_inode_list_lock);
-		spin_unlock(&inode_lock);
 
 		if (need_iput_tmp)
 			iput(need_iput_tmp);
@@ -427,7 +430,6 @@ void fsnotify_unmount_inodes(struct list
 
 		iput(inode);
 
-		spin_lock(&inode_lock);
 		spin_lock(&sb_inode_list_lock);
 	}
 }
mm/backing-dev.c +0 -4
Index: linux-2.6/mm/backing-dev.c
===================================================================
@@ -77,7 +77,6 @@ static int bdi_debug_stats_show(struct s
 	 * RCU on the reader side
 	 */
 	nr_wb = nr_dirty = nr_io = nr_more_io = 0;
-	spin_lock(&inode_lock);
 	spin_lock(&wb_inode_list_lock);
 	list_for_each_entry(wb, &bdi->wb_list, list) {
 		nr_wb++;
@@ -89,7 +88,6 @@ static int bdi_debug_stats_show(struct s
 			nr_more_io++;
 	}
 	spin_unlock(&wb_inode_list_lock);
-	spin_unlock(&inode_lock);
 
 	get_dirty_limits(&background_thresh, &dirty_thresh, &bdi_thresh, bdi);
 
@@ -713,13 +711,11 @@ void bdi_destroy(struct backing_dev_info
 	if (bdi_has_dirty_io(bdi)) {
 		struct bdi_writeback *dst = &default_backing_dev_info.wb;
 
-		spin_lock(&inode_lock);
 		spin_lock(&wb_inode_list_lock);
 		list_splice(&bdi->wb.b_dirty, &dst->b_dirty);
 		list_splice(&bdi->wb.b_io, &dst->b_io);
 		list_splice(&bdi->wb.b_more_io, &dst->b_more_io);
 		spin_unlock(&wb_inode_list_lock);
-		spin_unlock(&inode_lock);
 	}
 
 	bdi_unregister(bdi);
#91 lore raw

PATCH 35/52 fs: icache factor hash lock into functions

In reply to [email protected]

Make inode_hash_lock private by adding a function __remove_inode_hash that can be used by filesystems defining their own drop_inode functions.

---
 fs/inode.c                |   42 +++++++++++++++++++++++++++---------------
 include/linux/fs.h        |    1 +
 include/linux/writeback.h |    1 -
 3 files changed, 28 insertions(+), 16 deletions(-)
fs/inode.c +27 -15
Index: linux-2.6/fs/inode.c
===================================================================
@@ -107,7 +107,7 @@ static struct hlist_head *inode_hashtabl
  */
 DEFINE_SPINLOCK(sb_inode_list_lock);
 DEFINE_SPINLOCK(wb_inode_list_lock);
-DEFINE_SPINLOCK(inode_hash_lock);
+static DEFINE_SPINLOCK(inode_hash_lock);
 
 /*
  * iprune_sem provides exclusion between the kswapd or try_to_free_pages
@@ -377,9 +377,7 @@ static void dispose_list(struct list_hea
 
 		spin_lock(&sb_inode_list_lock);
 		spin_lock(&inode->i_lock);
-		spin_lock(&inode_hash_lock);
-		hlist_del_init(&inode->i_hash);
-		spin_unlock(&inode_hash_lock);
+		__remove_inode_hash(inode);
 		list_del_init(&inode->i_sb_list);
 		spin_unlock(&inode->i_lock);
 		spin_unlock(&sb_inode_list_lock);
@@ -1280,6 +1278,20 @@ void __insert_inode_hash(struct inode *i
 EXPORT_SYMBOL(__insert_inode_hash);
 
 /**
+ *	__remove_inode_hash - remove an inode from the hash
+ *	@inode: inode to unhash
+ *
+ *	Remove an inode from the superblock. inode->i_lock must be
+ *	held.
+ */
+void __remove_inode_hash(struct inode *inode)
+{
+	spin_lock(&inode_hash_lock);
+	hlist_del_init(&inode->i_hash);
+	spin_unlock(&inode_hash_lock);
+}
+
+/**
  *	remove_inode_hash - remove an inode from the hash
  *	@inode: inode to unhash
  *
@@ -1288,9 +1300,7 @@ EXPORT_SYMBOL(__insert_inode_hash);
 void remove_inode_hash(struct inode *inode)
 {
 	spin_lock(&inode->i_lock);
-	spin_lock(&inode_hash_lock);
-	hlist_del_init(&inode->i_hash);
-	spin_unlock(&inode_hash_lock);
+	__remove_inode_hash(inode);
 	spin_unlock(&inode->i_lock);
 }
 EXPORT_SYMBOL(remove_inode_hash);
@@ -1332,11 +1342,15 @@ void generic_delete_inode(struct inode *
 		truncate_inode_pages(&inode->i_data, 0);
 		clear_inode(inode);
 	}
-	spin_lock(&inode->i_lock);
-	spin_lock(&inode_hash_lock);
-	hlist_del_init(&inode->i_hash);
-	spin_unlock(&inode_hash_lock);
-	spin_unlock(&inode->i_lock);
+	/*
+	 * i_lock not required to delete from hash. If there was a
+	 * concurrency window, then it would be possible for the other
+	 * thread to touch the inode after it has been freed, with
+	 * destroy_inode.
+	 * XXX: yes it is because find_inode_fast checks it. Maybe we
+	 * can avoid it though...
+	 */
+	remove_inode_hash(inode);
 	wake_up_inode(inode);
 	BUG_ON(inode->i_state != I_CLEAR);
 	destroy_inode(inode);
@@ -1377,9 +1391,7 @@ int generic_detach_inode(struct inode *i
 		spin_lock(&inode->i_lock);
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state &= ~I_WILL_FREE;
-		spin_lock(&inode_hash_lock);
-		hlist_del_init(&inode->i_hash);
-		spin_unlock(&inode_hash_lock);
+		__remove_inode_hash(inode);
 		atomic_dec(&inodes_stat.nr_unused);
 	}
 	spin_lock(&wb_inode_list_lock);
include/linux/fs.h +1 -0
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -2181,6 +2181,7 @@ extern int should_remove_suid(struct den
 extern int file_remove_suid(struct file *);
 
 extern void __insert_inode_hash(struct inode *, unsigned long hashval);
+extern void __remove_inode_hash(struct inode *);
 extern void remove_inode_hash(struct inode *);
 static inline void insert_inode_hash(struct inode *inode) {
 	__insert_inode_hash(inode, inode->i_ino);
include/linux/writeback.h +0 -1
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -11,7 +11,6 @@ struct backing_dev_info;
 
 extern spinlock_t sb_inode_list_lock;
 extern spinlock_t wb_inode_list_lock;
-extern spinlock_t inode_hash_lock;
 extern struct list_head inode_in_use;
 extern struct list_head inode_unused;
 
#92 lore raw

PATCH 36/52 fs: icache per-bucket inode hash locks

In reply to [email protected]

Remove the global inode_hash_lock and replace it with per-hash-bucket locks.

---
 fs/inode.c |  167 ++++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 90 insertions(+), 77 deletions(-)
fs/inode.c +110 -88
Index: linux-2.6/fs/inode.c
===================================================================
@@ -25,12 +25,13 @@
 #include <linux/mount.h>
 #include <linux/async.h>
 #include <linux/posix_acl.h>
+#include <linux/bit_spinlock.h>
 
 /*
  * Usage:
  * sb_inode_list_lock protects:
  *   s_inodes, i_sb_list
- * inode_hash_lock protects:
+ * inode_hash_bucket lock protects:
  *   inode hash table, i_hash
  * wb_inode_list_lock protects:
  *   inode_in_use, inode_unused, b_io, b_more_io, b_dirty, i_list
@@ -46,7 +47,7 @@
  *   sb_inode_list_lock
  *     inode->i_lock
  *       wb_inode_list_lock
- *       inode_hash_lock
+ *       inode_hash_bucket lock
  */
 /*
  * This is needed for the following functions:
@@ -97,7 +98,22 @@ static unsigned int i_hash_shift __read_
 
 LIST_HEAD(inode_in_use);
 LIST_HEAD(inode_unused);
-static struct hlist_head *inode_hashtable __read_mostly;
+
+struct inode_hash_bucket {
+	struct hlist_bl_head head;
+};
+
+static inline void spin_lock_bucket(struct inode_hash_bucket *b)
+{
+	bit_spin_lock(0, (unsigned long *)b);
+}
+
+static inline void spin_unlock_bucket(struct inode_hash_bucket *b)
+{
+	__bit_spin_unlock(0, (unsigned long *)b);
+}
+
+static struct inode_hash_bucket *inode_hashtable __read_mostly;
 
 /*
  * A simple spinlock to protect the list manipulations.
@@ -107,7 +123,6 @@ static struct hlist_head *inode_hashtabl
  */
 DEFINE_SPINLOCK(sb_inode_list_lock);
 DEFINE_SPINLOCK(wb_inode_list_lock);
-static DEFINE_SPINLOCK(inode_hash_lock);
 
 /*
  * iprune_sem provides exclusion between the kswapd or try_to_free_pages
@@ -280,7 +295,7 @@ void destroy_inode(struct inode *inode)
 void inode_init_once(struct inode *inode)
 {
 	memset(inode, 0, sizeof(*inode));
-	INIT_HLIST_NODE(&inode->i_hash);
+	INIT_HLIST_BL_NODE(&inode->i_hash);
 	INIT_LIST_HEAD(&inode->i_dentry);
 	INIT_LIST_HEAD(&inode->i_devices);
 	INIT_RADIX_TREE(&inode->i_data.page_tree, GFP_ATOMIC);
@@ -596,20 +611,21 @@ static void __wait_on_freeing_inode(stru
  * add any additional branch in the common code.
  */
 static struct inode *find_inode(struct super_block *sb,
-				struct hlist_head *head,
+				struct inode_hash_bucket *b,
 				int (*test)(struct inode *, void *),
 				void *data)
 {
-	struct hlist_node *node;
+	struct hlist_bl_node *node;
 	struct inode *inode = NULL;
 
 repeat:
-	spin_lock(&inode_hash_lock);
-	hlist_for_each_entry(inode, node, head, i_hash) {
+	spin_lock_bucket(b);
+	hlist_bl_for_each_entry(inode, node, &b->head, i_hash) {
 		if (inode->i_sb != sb)
 			continue;
 		if (!spin_trylock(&inode->i_lock)) {
-			spin_unlock(&inode_hash_lock);
+			spin_unlock_bucket(b);
+			cpu_relax();
 			goto repeat;
 		}
 		if (!test(inode, data)) {
@@ -617,13 +633,13 @@ repeat:
 			continue;
 		}
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)) {
-			spin_unlock(&inode_hash_lock);
+			spin_unlock_bucket(b);
 			__wait_on_freeing_inode(inode);
 			goto repeat;
 		}
 		break;
 	}
-	spin_unlock(&inode_hash_lock);
+	spin_unlock_bucket(b);
 	return node ? inode : NULL;
 }
 
@@ -632,30 +648,32 @@ repeat:
  * iget_locked for details.
  */
 static struct inode *find_inode_fast(struct super_block *sb,
-				struct hlist_head *head, unsigned long ino)
+				struct inode_hash_bucket *b,
+				unsigned long ino)
 {
-	struct hlist_node *node;
+	struct hlist_bl_node *node;
 	struct inode *inode = NULL;
 
 repeat:
-	spin_lock(&inode_hash_lock);
-	hlist_for_each_entry(inode, node, head, i_hash) {
+	spin_lock_bucket(b);
+	hlist_bl_for_each_entry(inode, node, &b->head, i_hash) {
 		if (inode->i_ino != ino)
 			continue;
 		if (inode->i_sb != sb)
 			continue;
 		if (!spin_trylock(&inode->i_lock)) {
-			spin_unlock(&inode_hash_lock);
+			spin_unlock_bucket(b);
+			cpu_relax();
 			goto repeat;
 		}
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)) {
-			spin_unlock(&inode_hash_lock);
+			spin_unlock_bucket(b);
 			__wait_on_freeing_inode(inode);
 			goto repeat;
 		}
 		break;
 	}
-	spin_unlock(&inode_hash_lock);
+	spin_unlock_bucket(b);
 	return node ? inode : NULL;
 }
 
@@ -670,7 +688,7 @@ static unsigned long hash(struct super_b
 }
 
 static inline void
-__inode_add_to_lists(struct super_block *sb, struct hlist_head *head,
+__inode_add_to_lists(struct super_block *sb, struct inode_hash_bucket *b,
 			struct inode *inode)
 {
 	atomic_inc(&inodes_stat.nr_inodes);
@@ -679,10 +697,10 @@ __inode_add_to_lists(struct super_block
 	spin_lock(&wb_inode_list_lock);
 	list_add(&inode->i_list, &inode_in_use);
 	spin_unlock(&wb_inode_list_lock);
-	if (head) {
-		spin_lock(&inode_hash_lock);
-		hlist_add_head(&inode->i_hash, head);
-		spin_unlock(&inode_hash_lock);
+	if (b) {
+		spin_lock_bucket(b);
+		hlist_bl_add_head(&inode->i_hash, &b->head);
+		spin_unlock_bucket(b);
 	}
 }
 
@@ -700,11 +718,11 @@ __inode_add_to_lists(struct super_block
  */
 void inode_add_to_lists(struct super_block *sb, struct inode *inode)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, inode->i_ino);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, inode->i_ino);
 
 	spin_lock(&sb_inode_list_lock);
 	spin_lock(&inode->i_lock);
-	__inode_add_to_lists(sb, head, inode);
+	__inode_add_to_lists(sb, b, inode);
 	spin_unlock(&inode->i_lock);
 }
 EXPORT_SYMBOL_GPL(inode_add_to_lists);
@@ -786,7 +804,7 @@ EXPORT_SYMBOL(unlock_new_inode);
  *	-- [email protected]
  */
 static struct inode *get_new_inode(struct super_block *sb,
-				struct hlist_head *head,
+				struct inode_hash_bucket *b,
 				int (*test)(struct inode *, void *),
 				int (*set)(struct inode *, void *),
 				void *data)
@@ -798,7 +816,7 @@ static struct inode *get_new_inode(struc
 		struct inode *old;
 
 		/* We released the lock, so.. */
-		old = find_inode(sb, head, test, data);
+		old = find_inode(sb, b, test, data);
 		if (!old) {
 			spin_lock(&sb_inode_list_lock);
 			spin_lock(&inode->i_lock);
@@ -806,7 +824,7 @@ static struct inode *get_new_inode(struc
 				goto set_failed;
 
 			inode->i_state = I_NEW;
-			__inode_add_to_lists(sb, head, inode);
+			__inode_add_to_lists(sb, b, inode);
 			spin_unlock(&inode->i_lock);
 
 			/* Return the locked inode with I_NEW set, the
@@ -840,7 +858,7 @@ set_failed:
  * comment at iget_locked for details.
  */
 static struct inode *get_new_inode_fast(struct super_block *sb,
-				struct hlist_head *head, unsigned long ino)
+				struct inode_hash_bucket *b, unsigned long ino)
 {
 	struct inode *inode;
 
@@ -849,13 +867,13 @@ static struct inode *get_new_inode_fast(
 		struct inode *old;
 
 		/* We released the lock, so.. */
-		old = find_inode_fast(sb, head, ino);
+		old = find_inode_fast(sb, b, ino);
 		if (!old) {
 			spin_lock(&sb_inode_list_lock);
 			spin_lock(&inode->i_lock);
 			inode->i_ino = ino;
 			inode->i_state = I_NEW;
-			__inode_add_to_lists(sb, head, inode);
+			__inode_add_to_lists(sb, b, inode);
 			spin_unlock(&inode->i_lock);
 
 			/* Return the locked inode with I_NEW set, the
@@ -878,19 +896,20 @@ static struct inode *get_new_inode_fast(
 	return inode;
 }
 
-static int test_inode_iunique(struct super_block * sb, struct hlist_head *head, unsigned long ino)
+static int test_inode_iunique(struct super_block *sb,
+				struct inode_hash_bucket *b, unsigned long ino)
 {
-	struct hlist_node *node;
-	struct inode * inode = NULL;
+	struct hlist_bl_node *node;
+	struct inode *inode = NULL;
 
-	spin_lock(&inode_hash_lock);
-	hlist_for_each_entry(inode, node, head, i_hash) {
+	spin_lock_bucket(b);
+	hlist_bl_for_each_entry(inode, node, &b->head, i_hash) {
 		if (inode->i_ino == ino && inode->i_sb == sb) {
-			spin_unlock(&inode_hash_lock);
+			spin_unlock_bucket(b);
 			return 0;
 		}
 	}
-	spin_unlock(&inode_hash_lock);
+	spin_unlock_bucket(b);
 	return 1;
 }
 
@@ -917,7 +936,7 @@ ino_t iunique(struct super_block *sb, in
 	 */
 	static DEFINE_SPINLOCK(unique_lock);
 	static unsigned int counter;
-	struct hlist_head *head;
+	struct inode_hash_bucket *b;
 	ino_t res;
 
 	spin_lock(&unique_lock);
@@ -925,8 +944,8 @@ ino_t iunique(struct super_block *sb, in
 		if (counter <= max_reserved)
 			counter = max_reserved + 1;
 		res = counter++;
-		head = inode_hashtable + hash(sb, res);
-	} while (!test_inode_iunique(sb, head, res));
+		b = inode_hashtable + hash(sb, res);
+	} while (!test_inode_iunique(sb, b, res));
 	spin_unlock(&unique_lock);
 
 	return res;
@@ -973,12 +992,13 @@ EXPORT_SYMBOL(igrab);
  * Note, @test is called with the inode_lock held, so can't sleep.
  */
 static struct inode *ifind(struct super_block *sb,
-		struct hlist_head *head, int (*test)(struct inode *, void *),
+		struct inode_hash_bucket *b,
+		int (*test)(struct inode *, void *),
 		void *data, const int wait)
 {
 	struct inode *inode;
 
-	inode = find_inode(sb, head, test, data);
+	inode = find_inode(sb, b, test, data);
 	if (inode) {
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
@@ -1005,11 +1025,12 @@ static struct inode *ifind(struct super_
  * Otherwise NULL is returned.
  */
 static struct inode *ifind_fast(struct super_block *sb,
-		struct hlist_head *head, unsigned long ino)
+		struct inode_hash_bucket *b,
+		unsigned long ino)
 {
 	struct inode *inode;
 
-	inode = find_inode_fast(sb, head, ino);
+	inode = find_inode_fast(sb, b, ino);
 	if (inode) {
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
@@ -1043,9 +1064,9 @@ static struct inode *ifind_fast(struct s
 struct inode *ilookup5_nowait(struct super_block *sb, unsigned long hashval,
 		int (*test)(struct inode *, void *), void *data)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, hashval);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, hashval);
 
-	return ifind(sb, head, test, data, 0);
+	return ifind(sb, b, test, data, 0);
 }
 EXPORT_SYMBOL(ilookup5_nowait);
 
@@ -1071,9 +1092,9 @@ EXPORT_SYMBOL(ilookup5_nowait);
 struct inode *ilookup5(struct super_block *sb, unsigned long hashval,
 		int (*test)(struct inode *, void *), void *data)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, hashval);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, hashval);
 
-	return ifind(sb, head, test, data, 1);
+	return ifind(sb, b, test, data, 1);
 }
 EXPORT_SYMBOL(ilookup5);
 
@@ -1093,9 +1114,9 @@ EXPORT_SYMBOL(ilookup5);
  */
 struct inode *ilookup(struct super_block *sb, unsigned long ino)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, ino);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, ino);
 
-	return ifind_fast(sb, head, ino);
+	return ifind_fast(sb, b, ino);
 }
 EXPORT_SYMBOL(ilookup);
 
@@ -1123,17 +1144,17 @@ struct inode *iget5_locked(struct super_
 		int (*test)(struct inode *, void *),
 		int (*set)(struct inode *, void *), void *data)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, hashval);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, hashval);
 	struct inode *inode;
 
-	inode = ifind(sb, head, test, data, 1);
+	inode = ifind(sb, b, test, data, 1);
 	if (inode)
 		return inode;
 	/*
 	 * get_new_inode() will do the right thing, re-trying the search
 	 * in case it had to block at any point.
 	 */
-	return get_new_inode(sb, head, test, set, data);
+	return get_new_inode(sb, b, test, set, data);
 }
 EXPORT_SYMBOL(iget5_locked);
 
@@ -1154,17 +1175,17 @@ EXPORT_SYMBOL(iget5_locked);
  */
 struct inode *iget_locked(struct super_block *sb, unsigned long ino)
 {
-	struct hlist_head *head = inode_hashtable + hash(sb, ino);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, ino);
 	struct inode *inode;
 
-	inode = ifind_fast(sb, head, ino);
+	inode = ifind_fast(sb, b, ino);
 	if (inode)
 		return inode;
 	/*
 	 * get_new_inode_fast() will do the right thing, re-trying the search
 	 * in case it had to block at any point.
 	 */
-	return get_new_inode_fast(sb, head, ino);
+	return get_new_inode_fast(sb, b, ino);
 }
 EXPORT_SYMBOL(iget_locked);
 
@@ -1172,16 +1193,16 @@ int insert_inode_locked(struct inode *in
 {
 	struct super_block *sb = inode->i_sb;
 	ino_t ino = inode->i_ino;
-	struct hlist_head *head = inode_hashtable + hash(sb, ino);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, ino);
 
 	inode->i_state |= I_NEW;
 	while (1) {
-		struct hlist_node *node;
+		struct hlist_bl_node *node;
 		struct inode *old = NULL;
 
 repeat:
-		spin_lock(&inode_hash_lock);
-		hlist_for_each_entry(old, node, head, i_hash) {
+		spin_lock_bucket(b);
+		hlist_bl_for_each_entry(old, node, &b->head, i_hash) {
 			if (old->i_ino != ino)
 				continue;
 			if (old->i_sb != sb)
@@ -1189,21 +1210,21 @@ repeat:
 			if (old->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE))
 				continue;
 			if (!spin_trylock(&old->i_lock)) {
-				spin_unlock(&inode_hash_lock);
+				spin_unlock_bucket(b);
 				goto repeat;
 			}
 			break;
 		}
 		if (likely(!node)) {
-			hlist_add_head(&inode->i_hash, head);
-			spin_unlock(&inode_hash_lock);
+			hlist_bl_add_head(&inode->i_hash, &b->head);
+			spin_unlock_bucket(b);
 			return 0;
 		}
-		spin_unlock(&inode_hash_lock);
+		spin_unlock_bucket(b);
 		__iget(old);
 		spin_unlock(&old->i_lock);
 		wait_on_inode(old);
-		if (unlikely(!hlist_unhashed(&old->i_hash))) {
+		if (unlikely(!hlist_bl_unhashed(&old->i_hash))) {
 			iput(old);
 			return -EBUSY;
 		}
@@ -1216,17 +1237,17 @@ int insert_inode_locked4(struct inode *i
 		int (*test)(struct inode *, void *), void *data)
 {
 	struct super_block *sb = inode->i_sb;
-	struct hlist_head *head = inode_hashtable + hash(sb, hashval);
+	struct inode_hash_bucket *b = inode_hashtable + hash(sb, hashval);
 
 	inode->i_state |= I_NEW;
 
 	while (1) {
-		struct hlist_node *node;
+		struct hlist_bl_node *node;
 		struct inode *old = NULL;
 
 repeat:
-		spin_lock(&inode_hash_lock);
-		hlist_for_each_entry(old, node, head, i_hash) {
+		spin_lock_bucket(b);
+		hlist_bl_for_each_entry(old, node, &b->head, i_hash) {
 			if (old->i_sb != sb)
 				continue;
 			if (!test(old, data))
@@ -1234,21 +1255,21 @@ repeat:
 			if (old->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE))
 				continue;
 			if (!spin_trylock(&old->i_lock)) {
-				spin_unlock(&inode_hash_lock);
+				spin_unlock_bucket(b);
 				goto repeat;
 			}
 			break;
 		}
 		if (likely(!node)) {
-			hlist_add_head(&inode->i_hash, head);
-			spin_unlock(&inode_hash_lock);
+			hlist_bl_add_head(&inode->i_hash, &b->head);
+			spin_unlock_bucket(b);
 			return 0;
 		}
-		spin_unlock(&inode_hash_lock);
+		spin_unlock_bucket(b);
 		__iget(old);
 		spin_unlock(&old->i_lock);
 		wait_on_inode(old);
-		if (unlikely(!hlist_unhashed(&old->i_hash))) {
+		if (unlikely(!hlist_bl_unhashed(&old->i_hash))) {
 			iput(old);
 			return -EBUSY;
 		}
@@ -1267,12 +1288,12 @@ EXPORT_SYMBOL(insert_inode_locked4);
  */
 void __insert_inode_hash(struct inode *inode, unsigned long hashval)
 {
-	struct hlist_head *head = inode_hashtable + hash(inode->i_sb, hashval);
+	struct inode_hash_bucket *b = inode_hashtable + hash(inode->i_sb, hashval);
 
 	spin_lock(&inode->i_lock);
-	spin_lock(&inode_hash_lock);
-	hlist_add_head(&inode->i_hash, head);
-	spin_unlock(&inode_hash_lock);
+	spin_lock_bucket(b);
+	hlist_bl_add_head(&inode->i_hash, &b->head);
+	spin_unlock_bucket(b);
 	spin_unlock(&inode->i_lock);
 }
 EXPORT_SYMBOL(__insert_inode_hash);
@@ -1286,9 +1307,10 @@ EXPORT_SYMBOL(__insert_inode_hash);
  */
 void __remove_inode_hash(struct inode *inode)
 {
-	spin_lock(&inode_hash_lock);
-	hlist_del_init(&inode->i_hash);
-	spin_unlock(&inode_hash_lock);
+	struct inode_hash_bucket *b = inode_hashtable + hash(inode->i_sb, inode->i_ino);
+	spin_lock_bucket(b);
+	hlist_bl_del_init(&inode->i_hash);
+	spin_unlock_bucket(b);
 }
 
 /**
@@ -1370,7 +1392,7 @@ int generic_detach_inode(struct inode *i
 {
 	struct super_block *sb = inode->i_sb;
 
-	if (!hlist_unhashed(&inode->i_hash)) {
+	if (!hlist_bl_unhashed(&inode->i_hash)) {
 		if (!(inode->i_state & (I_DIRTY|I_SYNC))) {
 			spin_lock(&wb_inode_list_lock);
 			list_move(&inode->i_list, &inode_unused);
@@ -1699,7 +1721,7 @@ void __init inode_init_early(void)
 
 	inode_hashtable =
 		alloc_large_system_hash("Inode-cache",
-					sizeof(struct hlist_head),
+					sizeof(struct inode_hash_bucket),
 					ihash_entries,
 					14,
 					HASH_EARLY,
@@ -1708,7 +1730,7 @@ void __init inode_init_early(void)
 					0);
 
 	for (loop = 0; loop < (1 << i_hash_shift); loop++)
-		INIT_HLIST_HEAD(&inode_hashtable[loop]);
+		INIT_HLIST_BL_HEAD(&inode_hashtable[loop].head);
 }
 
 void __init inode_init(void)
@@ -1730,7 +1752,7 @@ void __init inode_init(void)
 
 	inode_hashtable =
 		alloc_large_system_hash("Inode-cache",
-					sizeof(struct hlist_head),
+					sizeof(struct inode_hash_bucket),
 					ihash_entries,
 					14,
 					0,
@@ -1739,7 +1761,7 @@ void __init inode_init(void)
 					0);
 
 	for (loop = 0; loop < (1 << i_hash_shift); loop++)
-		INIT_HLIST_HEAD(&inode_hashtable[loop]);
+		INIT_HLIST_BL_HEAD(&inode_hashtable[loop].head);
 }
 
 void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
fs/fs-writeback.c +1 -1
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -1147,7 +1147,7 @@ void __mark_inode_dirty(struct inode *in
 		 * dirty list.  Add blockdev inodes as well.
 		 */
 		if (!S_ISBLK(inode->i_mode)) {
-			if (hlist_unhashed(&inode->i_hash))
+			if (hlist_bl_unhashed(&inode->i_hash))
 				goto out;
 		}
 		if (inode->i_state & (I_FREEING|I_CLEAR))
include/linux/fs.h +1 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -723,7 +723,7 @@ struct posix_acl;
 #define ACL_NOT_CACHED ((void *)(-1))
 
 struct inode {
-	struct hlist_node	i_hash;
+	struct hlist_bl_node	i_hash;
 	struct list_head	i_list;		/* backing dev IO list */
 	struct list_head	i_sb_list;
 	struct list_head	i_dentry;
mm/shmem.c +2 -2
Index: linux-2.6/mm/shmem.c
===================================================================
@@ -2122,7 +2122,7 @@ static int shmem_encode_fh(struct dentry
 	if (*len < 3)
 		return 255;
 
-	if (hlist_unhashed(&inode->i_hash)) {
+	if (hlist_bl_unhashed(&inode->i_hash)) {
 		/* Unfortunately insert_inode_hash is not idempotent,
 		 * so as we hash inodes here rather than at creation
 		 * time, we need a lock to ensure we only try
@@ -2130,7 +2130,7 @@ static int shmem_encode_fh(struct dentry
 		 */
 		static DEFINE_SPINLOCK(lock);
 		spin_lock(&lock);
-		if (hlist_unhashed(&inode->i_hash))
+		if (hlist_bl_unhashed(&inode->i_hash))
 			__insert_inode_hash(inode,
 					    inode->i_ino + inode->i_generation);
 		spin_unlock(&lock);
fs/btrfs/inode.c +1 -1
Index: linux-2.6/fs/btrfs/inode.c
===================================================================
@@ -3849,7 +3849,7 @@ again:
 	p = &root->inode_tree.rb_node;
 	parent = NULL;
 
-	if (hlist_unhashed(&inode->i_hash))
+	if (hlist_bl_unhashed(&inode->i_hash))
 		return;
 
 	spin_lock(&root->inode_lock);
fs/reiserfs/xattr.c +1 -1
Index: linux-2.6/fs/reiserfs/xattr.c
===================================================================
@@ -424,7 +424,7 @@ int reiserfs_prepare_write(struct file *
 static void update_ctime(struct inode *inode)
 {
 	struct timespec now = current_fs_time(inode->i_sb);
-	if (hlist_unhashed(&inode->i_hash) || !inode->i_nlink ||
+	if (hlist_bl_unhashed(&inode->i_hash) || !inode->i_nlink ||
 	    timespec_equal(&inode->i_ctime, &now))
 		return;
 
fs/hfs/hfs_fs.h +1 -1
Index: linux-2.6/fs/hfs/hfs_fs.h
===================================================================
@@ -148,7 +148,7 @@ struct hfs_sb_info {
 
 	int fs_div;
 
-	struct hlist_head rsrc_inodes;
+	struct hlist_bl_head rsrc_inodes;
 };
 
 #define HFS_FLG_BITMAP_DIRTY	0
fs/hfs/inode.c +1 -1
Index: linux-2.6/fs/hfs/inode.c
===================================================================
@@ -500,7 +500,7 @@ static struct dentry *hfs_file_lookup(st
 	HFS_I(inode)->rsrc_inode = dir;
 	HFS_I(dir)->rsrc_inode = inode;
 	igrab(dir);
-	hlist_add_head(&inode->i_hash, &HFS_SB(dir->i_sb)->rsrc_inodes);
+	hlist_bl_add_head(&inode->i_hash, &HFS_SB(dir->i_sb)->rsrc_inodes);
 	mark_inode_dirty(inode);
 out:
 	d_add(dentry, inode);
fs/hfsplus/hfsplus_fs.h +1 -1
Index: linux-2.6/fs/hfsplus/hfsplus_fs.h
===================================================================
@@ -144,7 +144,7 @@ struct hfsplus_sb_info {
 
 	unsigned long flags;
 
-	struct hlist_head rsrc_inodes;
+	struct hlist_bl_head rsrc_inodes;
 };
 
 #define HFSPLUS_SB_WRITEBACKUP	0x0001
fs/hfsplus/inode.c +1 -1
Index: linux-2.6/fs/hfsplus/inode.c
===================================================================
@@ -178,7 +178,7 @@ static struct dentry *hfsplus_file_looku
 	HFSPLUS_I(inode).rsrc_inode = dir;
 	HFSPLUS_I(dir).rsrc_inode = inode;
 	igrab(dir);
-	hlist_add_head(&inode->i_hash, &HFSPLUS_SB(sb).rsrc_inodes);
+	hlist_bl_add_head(&inode->i_hash, &HFSPLUS_SB(sb).rsrc_inodes);
 	mark_inode_dirty(inode);
 out:
 	d_add(dentry, inode);
fs/nilfs2/gcinode.c +10 -10
Index: linux-2.6/fs/nilfs2/gcinode.c
===================================================================
@@ -187,13 +187,13 @@ int nilfs_init_gccache(struct the_nilfs
 	INIT_LIST_HEAD(&nilfs->ns_gc_inodes);
 
 	nilfs->ns_gc_inodes_h =
-		kmalloc(sizeof(struct hlist_head) * NILFS_GCINODE_HASH_SIZE,
+		kmalloc(sizeof(struct hlist_bl_head) * NILFS_GCINODE_HASH_SIZE,
 			GFP_NOFS);
 	if (nilfs->ns_gc_inodes_h == NULL)
 		return -ENOMEM;
 
 	for (loop = 0; loop < NILFS_GCINODE_HASH_SIZE; loop++)
-		INIT_HLIST_HEAD(&nilfs->ns_gc_inodes_h[loop]);
+		INIT_HLIST_BL_HEAD(&nilfs->ns_gc_inodes_h[loop]);
 	return 0;
 }
 
@@ -245,18 +245,18 @@ static unsigned long ihash(ino_t ino, __
  */
 struct inode *nilfs_gc_iget(struct the_nilfs *nilfs, ino_t ino, __u64 cno)
 {
-	struct hlist_head *head = nilfs->ns_gc_inodes_h + ihash(ino, cno);
-	struct hlist_node *node;
+	struct hlist_bl_head *head = nilfs->ns_gc_inodes_h + ihash(ino, cno);
+	struct hlist_bl_node *node;
 	struct inode *inode;
 
-	hlist_for_each_entry(inode, node, head, i_hash) {
+	hlist_bl_for_each_entry(inode, node, head, i_hash) {
 		if (inode->i_ino == ino && NILFS_I(inode)->i_cno == cno)
 			return inode;
 	}
 
 	inode = alloc_gcinode(nilfs, ino, cno);
 	if (likely(inode)) {
-		hlist_add_head(&inode->i_hash, head);
+		hlist_bl_add_head(&inode->i_hash, head);
 		list_add(&NILFS_I(inode)->i_dirty, &nilfs->ns_gc_inodes);
 	}
 	return inode;
@@ -275,14 +275,14 @@ void nilfs_clear_gcinode(struct inode *i
  */
 void nilfs_remove_all_gcinode(struct the_nilfs *nilfs)
 {
-	struct hlist_head *head = nilfs->ns_gc_inodes_h;
-	struct hlist_node *node, *n;
+	struct hlist_bl_head *head = nilfs->ns_gc_inodes_h;
+	struct hlist_bl_node *node, *n;
 	struct inode *inode;
 	int loop;
 
 	for (loop = 0; loop < NILFS_GCINODE_HASH_SIZE; loop++, head++) {
-		hlist_for_each_entry_safe(inode, node, n, head, i_hash) {
-			hlist_del_init(&inode->i_hash);
+		hlist_bl_for_each_entry_safe(inode, node, n, head, i_hash) {
+			hlist_bl_del_init(&inode->i_hash);
 			list_del_init(&NILFS_I(inode)->i_dirty);
 			nilfs_clear_gcinode(inode); /* might sleep */
 		}
fs/nilfs2/segment.c +1 -1
Index: linux-2.6/fs/nilfs2/segment.c
===================================================================
@@ -2447,7 +2447,7 @@ nilfs_remove_written_gcinodes(struct the
 	list_for_each_entry_safe(ii, n, head, i_dirty) {
 		if (!test_bit(NILFS_I_UPDATED, &ii->i_state))
 			continue;
-		hlist_del_init(&ii->vfs_inode.i_hash);
+		hlist_bl_del_init(&ii->vfs_inode.i_hash);
 		list_del_init(&ii->i_dirty);
 		nilfs_clear_gcinode(&ii->vfs_inode);
 	}
fs/nilfs2/the_nilfs.h +1 -1
Index: linux-2.6/fs/nilfs2/the_nilfs.h
===================================================================
@@ -164,7 +164,7 @@ struct the_nilfs {
 
 	/* GC inode list and hash table head */
 	struct list_head	ns_gc_inodes;
-	struct hlist_head      *ns_gc_inodes_h;
+	struct hlist_bl_head      *ns_gc_inodes_h;
 
 	/* Disk layout information (static) */
 	unsigned int		ns_blocksize_bits;
#93 lore raw

PATCH 37/52 fs: icache lazy lru

In reply to [email protected]

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

---
 fs/fs-writeback.c         |   21 +++++++--------
 fs/inode.c                |   61 +++++++++++++++++++---------------------------
 include/linux/fs.h        |    7 ++++-
 include/linux/writeback.h |    1 
 4 files changed, 42 insertions(+), 48 deletions(-)
fs/inode.c +38 -67
Index: linux-2.6/fs/inode.c
===================================================================
@@ -96,7 +96,6 @@ static unsigned int i_hash_shift __read_
  * allowing for low-overhead inode sync() operations.
  */
 
-LIST_HEAD(inode_in_use);
 LIST_HEAD(inode_unused);
 
 struct inode_hash_bucket {
@@ -298,6 +297,7 @@ void inode_init_once(struct inode *inode
 	INIT_HLIST_BL_NODE(&inode->i_hash);
 	INIT_LIST_HEAD(&inode->i_dentry);
 	INIT_LIST_HEAD(&inode->i_devices);
+	INIT_LIST_HEAD(&inode->i_list);
 	INIT_RADIX_TREE(&inode->i_data.page_tree, GFP_ATOMIC);
 	spin_lock_init(&inode->i_data.tree_lock);
 	spin_lock_init(&inode->i_data.i_mmap_lock);
@@ -323,25 +323,6 @@ static void init_once(void *foo)
 	inode_init_once(inode);
 }
 
-/*
- * inode_lock must be held
- */
-void __iget(struct inode *inode)
-{
-	assert_spin_locked(&inode->i_lock);
-
-	inode->i_count++;
-	if (inode->i_count > 1)
-		return;
-
-	if (!(inode->i_state & (I_DIRTY|I_SYNC))) {
-		spin_lock(&wb_inode_list_lock);
-		list_move(&inode->i_list, &inode_in_use);
-		spin_unlock(&wb_inode_list_lock);
-	}
-	atomic_dec(&inodes_stat.nr_unused);
-}
-
 /**
  * clear_inode - clear an inode
  * @inode: inode to clear
@@ -384,7 +365,7 @@ static void dispose_list(struct list_hea
 		struct inode *inode;
 
 		inode = list_first_entry(head, struct inode, i_list);
-		list_del(&inode->i_list);
+		list_del_init(&inode->i_list);
 
 		if (inode->i_data.nrpages)
 			truncate_inode_pages(&inode->i_data, 0);
@@ -437,11 +418,12 @@ static int invalidate_list(struct list_h
 		invalidate_inode_buffers(inode);
 		if (!inode->i_count) {
 			spin_lock(&wb_inode_list_lock);
-			list_move(&inode->i_list, dispose);
+			list_del(&inode->i_list);
 			spin_unlock(&wb_inode_list_lock);
 			WARN_ON(inode->i_state & I_NEW);
 			inode->i_state |= I_FREEING;
 			spin_unlock(&inode->i_lock);
+			list_add(&inode->i_list, dispose);
 			count++;
 			continue;
 		}
@@ -480,19 +462,6 @@ int invalidate_inodes(struct super_block
 }
 EXPORT_SYMBOL(invalidate_inodes);
 
-static int can_unuse(struct inode *inode)
-{
-	if (inode->i_state)
-		return 0;
-	if (inode_has_buffers(inode))
-		return 0;
-	if (inode->i_count)
-		return 0;
-	if (inode->i_data.nrpages)
-		return 0;
-	return 1;
-}
-
 /*
  * Scan `goal' inodes on the unused list for freeable ones. They are moved to
  * a temporary list and then are freed outside inode_lock by dispose_list().
@@ -510,13 +479,12 @@ static void prune_icache(int nr_to_scan)
 {
 	LIST_HEAD(freeable);
 	int nr_pruned = 0;
-	int nr_scanned;
 	unsigned long reap = 0;
 
 	down_read(&iprune_sem);
 again:
 	spin_lock(&wb_inode_list_lock);
-	for (nr_scanned = 0; nr_scanned < nr_to_scan; nr_scanned++) {
+	for (; nr_to_scan; nr_to_scan--) {
 		struct inode *inode;
 
 		if (list_empty(&inode_unused))
@@ -528,33 +496,30 @@ again:
 			spin_unlock(&wb_inode_list_lock);
 			goto again;
 		}
-		if (inode->i_state || inode->i_count) {
+		if (inode->i_count || (inode->i_state & ~I_REFERENCED)) {
+			list_del_init(&inode->i_list);
+			spin_unlock(&inode->i_lock);
+			atomic_dec(&inodes_stat.nr_unused);
+			continue;
+		}
+		if (inode->i_state) {
 			list_move(&inode->i_list, &inode_unused);
+			inode->i_state &= ~I_REFERENCED;
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
 		if (inode_has_buffers(inode) || inode->i_data.nrpages) {
+			list_move(&inode->i_list, &inode_unused);
 			spin_unlock(&wb_inode_list_lock);
 			__iget(inode);
 			spin_unlock(&inode->i_lock);
+
 			if (remove_inode_buffers(inode))
 				reap += invalidate_mapping_pages(&inode->i_data,
 								0, -1);
 			iput(inode);
-again2:
 			spin_lock(&wb_inode_list_lock);
-
-			if (inode != list_entry(inode_unused.next,
-						struct inode, i_list))
-				continue;	/* wrong inode or list_empty */
-			if (!spin_trylock(&inode->i_lock)) {
-				spin_unlock(&wb_inode_list_lock);
-				goto again2;
-			}
-			if (!can_unuse(inode)) {
-				spin_unlock(&inode->i_lock);
-				continue;
-			}
+			continue;
 		}
 		list_move(&inode->i_list, &freeable);
 		WARN_ON(inode->i_state & I_NEW);
@@ -694,9 +659,6 @@ __inode_add_to_lists(struct super_block
 	atomic_inc(&inodes_stat.nr_inodes);
 	list_add(&inode->i_sb_list, &sb->s_inodes);
 	spin_unlock(&sb_inode_list_lock);
-	spin_lock(&wb_inode_list_lock);
-	list_add(&inode->i_list, &inode_in_use);
-	spin_unlock(&wb_inode_list_lock);
 	if (b) {
 		spin_lock_bucket(b);
 		hlist_bl_add_head(&inode->i_hash, &b->head);
@@ -1343,9 +1305,13 @@ void generic_delete_inode(struct inode *
 {
 	const struct super_operations *op = inode->i_sb->s_op;
 
-	spin_lock(&wb_inode_list_lock);
-	list_del_init(&inode->i_list);
-	spin_unlock(&wb_inode_list_lock);
+	if (!list_empty(&inode->i_list)) {
+		spin_lock(&wb_inode_list_lock);
+		list_del_init(&inode->i_list);
+		spin_unlock(&wb_inode_list_lock);
+		if (!inode->i_state)
+			atomic_dec(&inodes_stat.nr_unused);
+	}
 	list_del_init(&inode->i_sb_list);
 	spin_unlock(&sb_inode_list_lock);
 	WARN_ON(inode->i_state & I_NEW);
@@ -1393,13 +1359,15 @@ int generic_detach_inode(struct inode *i
 	struct super_block *sb = inode->i_sb;
 
 	if (!hlist_bl_unhashed(&inode->i_hash)) {
-		if (!(inode->i_state & (I_DIRTY|I_SYNC))) {
-			spin_lock(&wb_inode_list_lock);
-			list_move(&inode->i_list, &inode_unused);
-			spin_unlock(&wb_inode_list_lock);
-		}
-		atomic_inc(&inodes_stat.nr_unused);
 		if (sb->s_flags & MS_ACTIVE) {
+			inode->i_state |= I_REFERENCED;
+			if (!(inode->i_state & (I_DIRTY|I_SYNC)) &&
+					list_empty(&inode->i_list)) {
+				spin_lock(&wb_inode_list_lock);
+				list_add(&inode->i_list, &inode_unused);
+				spin_unlock(&wb_inode_list_lock);
+				atomic_inc(&inodes_stat.nr_unused);
+			}
 			spin_unlock(&inode->i_lock);
 			spin_unlock(&sb_inode_list_lock);
 			return 0;
@@ -1414,11 +1382,14 @@ int generic_detach_inode(struct inode *i
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state &= ~I_WILL_FREE;
 		__remove_inode_hash(inode);
-		atomic_dec(&inodes_stat.nr_unused);
 	}
-	spin_lock(&wb_inode_list_lock);
-	list_del_init(&inode->i_list);
-	spin_unlock(&wb_inode_list_lock);
+	if (!list_empty(&inode->i_list)) {
+		spin_lock(&wb_inode_list_lock);
+		list_del_init(&inode->i_list);
+		spin_unlock(&wb_inode_list_lock);
+		if (!inode->i_state)
+			atomic_dec(&inodes_stat.nr_unused);
+	}
 	list_del_init(&inode->i_sb_list);
 	spin_unlock(&sb_inode_list_lock);
 	WARN_ON(inode->i_state & I_NEW);
include/linux/fs.h +13 -7
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -1634,16 +1634,17 @@ struct super_operations {
  *
  * Q: What is the difference between I_WILL_FREE and I_FREEING?
  */
-#define I_DIRTY_SYNC		1
-#define I_DIRTY_DATASYNC	2
-#define I_DIRTY_PAGES		4
+#define I_DIRTY_SYNC		0x01
+#define I_DIRTY_DATASYNC	0x02
+#define I_DIRTY_PAGES		0x04
 #define __I_NEW			3
 #define I_NEW			(1 << __I_NEW)
-#define I_WILL_FREE		16
-#define I_FREEING		32
-#define I_CLEAR			64
+#define I_WILL_FREE		0x10
+#define I_FREEING		0x20
+#define I_CLEAR			0x40
 #define __I_SYNC		7
 #define I_SYNC			(1 << __I_SYNC)
+#define I_REFERENCED		0x100
 
 #define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
 
@@ -2171,7 +2172,6 @@ extern int insert_inode_locked4(struct i
 extern int insert_inode_locked(struct inode *);
 extern void unlock_new_inode(struct inode *);
 
-extern void __iget(struct inode * inode);
 extern void iget_failed(struct inode *);
 extern void clear_inode(struct inode *);
 extern void destroy_inode(struct inode *);
@@ -2422,6 +2422,12 @@ extern int generic_show_options(struct s
 extern void save_mount_options(struct super_block *sb, char *options);
 extern void replace_mount_options(struct super_block *sb, char *options);
 
+static inline void __iget(struct inode *inode)
+{
+	assert_spin_locked(&inode->i_lock);
+	inode->i_count++;
+}
+
 static inline ino_t parent_ino(struct dentry *dentry)
 {
 	ino_t res;
fs/fs-writeback.c +10 -18
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -552,15 +552,8 @@ select_queue:
 				inode->i_state |= I_DIRTY_PAGES;
 				redirty_tail(inode);
 			}
-		} else if (inode->i_count) {
-			/*
-			 * The inode is clean, inuse
-			 */
-			list_move(&inode->i_list, &inode_in_use);
 		} else {
-			/*
-			 * The inode is clean, unused
-			 */
+			/* The inode is clean */
 			list_move(&inode->i_list, &inode_unused);
 		}
 	}
@@ -1206,8 +1199,6 @@ static void wait_sb_inodes(struct super_
 	 */
 	WARN_ON(!rwsem_is_locked(&sb->s_umount));
 
-	spin_lock(&sb_inode_list_lock);
-
 	/*
 	 * Data integrity sync. Must wait for all pages under writeback,
 	 * because there may have been pages dirtied before our sync
@@ -1215,7 +1206,8 @@ static void wait_sb_inodes(struct super_
 	 * In which case, the inode may not be on the dirty list, but
 	 * we still have to wait for that writeout.
 	 */
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
 		struct address_space *mapping;
 
 		mapping = inode->i_mapping;
@@ -1229,13 +1221,13 @@ static void wait_sb_inodes(struct super_
 		}
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&sb_inode_list_lock);
+		rcu_read_unlock();
 		/*
 		 * We hold a reference to 'inode' so it couldn't have been
-		 * removed from s_inodes list while we dropped the
-		 * sb_inode_list_lock.  We cannot iput the inode now as we can
-		 * be holding the last reference and we cannot iput it under
-		 * spinlock. So we keep the reference and iput it later.
+		 * removed from s_inodes list while we dropped the i_lock.  We
+		 * cannot iput the inode now as we can be holding the last
+		 * reference and we cannot iput it under spinlock. So we keep
+		 * the reference and iput it later.
 		 */
 		iput(old_inode);
 		old_inode = inode;
@@ -1244,9 +1236,9 @@ static void wait_sb_inodes(struct super_
 
 		cond_resched();
 
-		spin_lock(&sb_inode_list_lock);
+		rcu_read_lock();
 	}
-	spin_unlock(&sb_inode_list_lock);
+	rcu_read_unlock();
 	iput(old_inode);
 }
 
include/linux/writeback.h +0 -1
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -11,7 +11,6 @@ struct backing_dev_info;
 
 extern spinlock_t sb_inode_list_lock;
 extern spinlock_t wb_inode_list_lock;
-extern struct list_head inode_in_use;
 extern struct list_head inode_unused;
 
 /*
#94 lore raw

Re: [patch 37/52] fs: icache lazy lru

Quoted 2 lines · [email protected] writes:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

Or just drop inode LRU completely and only rely on the dcache for that?

-Andi

Signature · 1 line
[email protected] -- Speaking for myself only.
#95 lore raw

Re: [patch 37/52] fs: icache lazy lru

Quoted 6 lines · Andi Kleen
Quoted 2 lines · [email protected] writes:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

Or just drop inode LRU completely and only rely on the dcache for that?

Possible, yes. There was some talking about it. I prefer not to do anything too controversial yet if possible :)

#96 lore raw

Re: [patch 37/52] fs: icache lazy lru

In reply to [email protected]
Quoted 2 lines · On Thu, Jun 24, 2010 at 01:02:49PM +1000, [email protected] wrote:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

I'm not sure we want the I_REFERENCED reclaim free pass for a clean inode that has been put on the LRU directly. I can see exactly how it is benficial to delay reclaim of dirty inodes (XFS uses that trick), but in terms of aging the cache we've already done this free pass trick at the dentry level. Hence I think the frequent separate access patterns tend to be filtered out at the dcache level and hence we don't need to handle that in the inode cache.

Perhaps we only need the I_REFERENCED flag to give dirty inodes a chance to be flushed by other means before forcing reclaim to do inode writeback?

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#97 lore raw

Re: [patch 37/52] fs: icache lazy lru

Quoted 12 lines · Dave Chinner
Quoted 2 lines · On Thu, Jun 24, 2010 at 01:02:49PM +1000, [email protected] wrote:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

I'm not sure we want the I_REFERENCED reclaim free pass for a clean inode that has been put on the LRU directly. I can see exactly how it is benficial to delay reclaim of dirty inodes (XFS uses that trick), but in terms of aging the cache we've already done this free pass trick at the dentry level. Hence I think the frequent separate access patterns tend to be filtered out at the dcache level and hence we don't need to handle that in the inode cache.

Quoted 3 lines

Perhaps we only need the I_REFERENCED flag to give dirty inodes a chance to be flushed by other means before forcing reclaim to do inode writeback?

It doesn't force flush, but it force invalidates pagecache.

#98 lore raw

Re: [patch 37/52] fs: icache lazy lru

Quoted 13 lines · Nick Piggin
Quoted 12 lines · Dave Chinner
Quoted 2 lines · On Thu, Jun 24, 2010 at 01:02:49PM +1000, [email protected] wrote:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

I'm not sure we want the I_REFERENCED reclaim free pass for a clean inode that has been put on the LRU directly. I can see exactly how it is benficial to delay reclaim of dirty inodes (XFS uses that trick), but in terms of aging the cache we've already done this free pass trick at the dentry level. Hence I think the frequent separate access patterns tend to be filtered out at the dcache level and hence we don't need to handle that in the inode cache.

Quoted 5 lines
Quoted 3 lines

Perhaps we only need the I_REFERENCED flag to give dirty inodes a chance to be flushed by other means before forcing reclaim to do inode writeback?

It doesn't force flush, but it force invalidates pagecache.

Sorry, bad choice of words - I should have said "forcing reclaim to wait on inode writeback". That is what will happen in the case of delayed allocation - the page cache will be clean, but the inode can be dirty, so reclaiming such an inode would cause the shrinker to block in clear_inode() waiting for inode writeback to complete.

However, that is mostly irrelevant - you haven't comented at all on whether the I_REFERENCED flag is too broad or even needed which is what really needs to be discussed about this patch....

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#99 lore raw

Re: [patch 37/52] fs: icache lazy lru

Quoted 18 lines · Dave Chinner
Quoted 17 lines · Nick Piggin
Quoted 15 lines · Dave Chinner
Quoted 2 lines · On Thu, Jun 24, 2010 at 01:02:49PM +1000, [email protected] wrote:

Impelemnt lazy inode lru similarly to dcache. This will reduce lock acquisition and will help to improve lock ordering subsequently.

I'm not sure we want the I_REFERENCED reclaim free pass for a clean inode that has been put on the LRU directly. I can see exactly how it is benficial to delay reclaim of dirty inodes (XFS uses that trick), but in terms of aging the cache we've already done this free pass trick at the dentry level. Hence I think the frequent separate access patterns tend to be filtered out at the dcache level and hence we don't need to handle that in the inode cache.

Perhaps we only need the I_REFERENCED flag to give dirty inodes a chance to be flushed by other means before forcing reclaim to do inode writeback?

Quoted 8 lines
Quoted 1 line

It doesn't force flush, but it force invalidates pagecache.

Sorry, bad choice of words - I should have said "forcing reclaim to wait on inode writeback". That is what will happen in the case of delayed allocation - the page cache will be clean, but the inode can be dirty, so reclaiming such an inode would cause the shrinker to block in clear_inode() waiting for inode writeback to complete.

OK.

Quoted 3 lines

However, that is mostly irrelevant - you haven't comented at all on whether the I_REFERENCED flag is too broad or even needed which is what really needs to be discussed about this patch....

Well when moving from non-lazy to lazy, I think I_REFERENCED makes sense, and matched what dcache did when it went to lazy LRU. Otherwise inodes on the LRU list stay basically in the order that they were added to the list, and have no relation to when they were last used, I don't think that's a good idea.

#100 lore raw

PATCH 38/52 fs: icache RCU free inodes

In reply to [email protected]

RCU free the struct inode. This will allow:

- sb_inode_list_lock to be moved inside i_lock because sb list walkers who want to take i_lock no longer need to take sb_inode_list_lock to walk the list in the first place. This will simplify and optimize locking.
- eventually, completely write-free RCU path walking. The inode must be consulted for permissions when walking, so a write-free reference (ie. RCU is helpful).
- can potentially simplify things a bit in VM land. May not need to take the page lock to get back to the page->mapping.
- can remove some nested trylock loops in dcache code
- Could remove the 'wq' allocation from socket now that the entire thing is rcu freed.

todo: convert all filesystems

---
 fs/block_dev.c        |    9 ++++++++-
 fs/ext2/super.c       |    9 ++++++++-
 fs/ext3/super.c       |    9 ++++++++-
 fs/fat/inode.c        |    9 ++++++++-
 fs/hugetlbfs/inode.c  |    9 ++++++++-
 fs/inode.c            |    9 ++++++++-
 fs/nfs/inode.c        |    9 ++++++++-
 fs/proc/inode.c       |    9 ++++++++-
 include/linux/fs.h    |    5 ++++-
 ipc/mqueue.c          |    9 ++++++++-
 mm/shmem.c            |    9 ++++++++-
 net/socket.c          |    9 ++++++++-
 net/sunrpc/rpc_pipe.c |   10 +++++++++-
 13 files changed, 101 insertions(+), 13 deletions(-)
fs/ext2/super.c +8 -1
Index: linux-2.6/fs/ext2/super.c
===================================================================
@@ -161,11 +161,18 @@ static struct inode *ext2_alloc_inode(st
 	return &ei->vfs_inode;
 }
 
-static void ext2_destroy_inode(struct inode *inode)
+static void ext2_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(ext2_inode_cachep, EXT2_I(inode));
 }
 
+static void ext2_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, ext2_i_callback);
+}
+
 static void init_once(void *foo)
 {
 	struct ext2_inode_info *ei = (struct ext2_inode_info *) foo;
fs/inode.c +71 -61

208 lines not on this page · raw message

include/linux/fs.h +4 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -726,7 +726,10 @@ struct inode {
 	struct hlist_bl_node	i_hash;
 	struct list_head	i_list;		/* backing dev IO list */
 	struct list_head	i_sb_list;
-	struct list_head	i_dentry;
+	union {
+		struct list_head	i_dentry;
+		struct rcu_head		i_rcu;
+	};
 	unsigned long		i_ino;
 	unsigned int		i_count;
 	unsigned int		i_nlink;
fs/block_dev.c +8 -1
Index: linux-2.6/fs/block_dev.c
===================================================================
@@ -397,13 +397,20 @@ static struct inode *bdev_alloc_inode(st
 	return &ei->vfs_inode;
 }
 
-static void bdev_destroy_inode(struct inode *inode)
+static void bdev_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
 	struct bdev_inode *bdi = BDEV_I(inode);
 
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(bdev_cachep, bdi);
 }
 
+static void bdev_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, bdev_i_callback);
+}
+
 static void init_once(void *foo)
 {
 	struct bdev_inode *ei = (struct bdev_inode *) foo;
fs/ext3/super.c +8 -1
Index: linux-2.6/fs/ext3/super.c
===================================================================
@@ -485,6 +485,13 @@ static struct inode *ext3_alloc_inode(st
 	return &ei->vfs_inode;
 }
 
+static void ext3_i_callback(struct rcu_head *head)
+{
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
+	kmem_cache_free(ext3_inode_cachep, EXT3_I(inode));
+}
+
 static void ext3_destroy_inode(struct inode *inode)
 {
 	if (!list_empty(&(EXT3_I(inode)->i_orphan))) {
@@ -495,7 +502,7 @@ static void ext3_destroy_inode(struct in
 				false);
 		dump_stack();
 	}
-	kmem_cache_free(ext3_inode_cachep, EXT3_I(inode));
+	call_rcu(&inode->i_rcu, ext3_i_callback);
 }
 
 static void init_once(void *foo)
fs/hugetlbfs/inode.c +8 -1
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
@@ -665,11 +665,18 @@ static struct inode *hugetlbfs_alloc_ino
 	return &p->vfs_inode;
 }
 
+static void hugetlbfs_i_callback(struct rcu_head *head)
+{
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
+	kmem_cache_free(hugetlbfs_inode_cachep, HUGETLBFS_I(inode));
+}
+
 static void hugetlbfs_destroy_inode(struct inode *inode)
 {
 	hugetlbfs_inc_free_inodes(HUGETLBFS_SB(inode->i_sb));
 	mpol_free_shared_policy(&HUGETLBFS_I(inode)->policy);
-	kmem_cache_free(hugetlbfs_inode_cachep, HUGETLBFS_I(inode));
+	call_rcu(&inode->i_rcu, hugetlbfs_i_callback);
 }
 
 static const struct address_space_operations hugetlbfs_aops = {
fs/proc/inode.c +8 -1
Index: linux-2.6/fs/proc/inode.c
===================================================================
@@ -66,11 +66,18 @@ static struct inode *proc_alloc_inode(st
 	return inode;
 }
 
-static void proc_destroy_inode(struct inode *inode)
+static void proc_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(proc_inode_cachep, PROC_I(inode));
 }
 
+static void proc_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, proc_i_callback);
+}
+
 static void init_once(void *foo)
 {
 	struct proc_inode *ei = (struct proc_inode *) foo;
ipc/mqueue.c +8 -1
Index: linux-2.6/ipc/mqueue.c
===================================================================
@@ -236,11 +236,18 @@ static struct inode *mqueue_alloc_inode(
 	return &ei->vfs_inode;
 }
 
-static void mqueue_destroy_inode(struct inode *inode)
+static void mqueue_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(mqueue_inode_cachep, MQUEUE_I(inode));
 }
 
+static void mqueue_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, mqueue_i_callback);
+}
+
 static void mqueue_delete_inode(struct inode *inode)
 {
 	struct mqueue_inode_info *info;
net/socket.c +8 -8
Index: linux-2.6/net/socket.c
===================================================================
@@ -271,20 +271,20 @@ static struct inode *sock_alloc_inode(st
 }
 
 
-static void wq_free_rcu(struct rcu_head *head)
+static void sock_free_rcu(struct rcu_head *head)
 {
-	struct socket_wq *wq = container_of(head, struct socket_wq, rcu);
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	struct socket_alloc *ei = container_of(inode, struct socket_alloc,
+								vfs_inode);
 
-	kfree(wq);
+	kfree(ei->socket.wq);
+	INIT_LIST_HEAD(&inode->i_dentry);
+	kmem_cache_free(sock_inode_cachep, ei);
 }
 
 static void sock_destroy_inode(struct inode *inode)
 {
-	struct socket_alloc *ei;
-
-	ei = container_of(inode, struct socket_alloc, vfs_inode);
-	call_rcu(&ei->socket.wq->rcu, wq_free_rcu);
-	kmem_cache_free(sock_inode_cachep, ei);
+	call_rcu(&inode->i_rcu, sock_free_rcu);
 }
 
 static void init_once(void *foo)
fs/fat/inode.c +8 -1
Index: linux-2.6/fs/fat/inode.c
===================================================================
@@ -520,11 +520,18 @@ static struct inode *fat_alloc_inode(str
 	return &ei->vfs_inode;
 }
 
-static void fat_destroy_inode(struct inode *inode)
+static void fat_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(fat_inode_cachep, MSDOS_I(inode));
 }
 
+static void fat_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, fat_i_callback);
+}
+
 static void init_once(void *foo)
 {
 	struct msdos_inode_info *ei = (struct msdos_inode_info *)foo;
fs/nfs/inode.c +8 -1
Index: linux-2.6/fs/nfs/inode.c
===================================================================
@@ -1365,11 +1365,18 @@ struct inode *nfs_alloc_inode(struct sup
 	return &nfsi->vfs_inode;
 }
 
-void nfs_destroy_inode(struct inode *inode)
+static void nfs_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(nfs_inode_cachep, NFS_I(inode));
 }
 
+void nfs_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, nfs_i_callback);
+}
+
 static inline void nfs4_init_once(struct nfs_inode *nfsi)
 {
 #ifdef CONFIG_NFS_V4
mm/shmem.c +8 -1
Index: linux-2.6/mm/shmem.c
===================================================================
@@ -2389,13 +2389,20 @@ static struct inode *shmem_alloc_inode(s
 	return &p->vfs_inode;
 }
 
+static void shmem_i_callback(struct rcu_head *head)
+{
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
+	kmem_cache_free(shmem_inode_cachep, SHMEM_I(inode));
+}
+
 static void shmem_destroy_inode(struct inode *inode)
 {
 	if ((inode->i_mode & S_IFMT) == S_IFREG) {
 		/* only struct inode is valid if it's an inline symlink */
 		mpol_free_shared_policy(&SHMEM_I(inode)->policy);
 	}
-	kmem_cache_free(shmem_inode_cachep, SHMEM_I(inode));
+	call_rcu(&inode->i_rcu, shmem_i_callback);
 }
 
 static void init_once(void *foo)
net/sunrpc/rpc_pipe.c +9 -1
Index: linux-2.6/net/sunrpc/rpc_pipe.c
===================================================================
@@ -163,11 +163,19 @@ rpc_alloc_inode(struct super_block *sb)
 }
 
 static void
-rpc_destroy_inode(struct inode *inode)
+rpc_i_callback(struct rcu_head *head)
 {
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	INIT_LIST_HEAD(&inode->i_dentry);
 	kmem_cache_free(rpc_inode_cachep, RPC_I(inode));
 }
 
+static void
+rpc_destroy_inode(struct inode *inode)
+{
+	call_rcu(&inode->i_rcu, rpc_i_callback);
+}
+
 static int
 rpc_pipe_open(struct inode *inode, struct file *filp)
 {
include/linux/net.h +0 -1
Index: linux-2.6/include/linux/net.h
===================================================================
@@ -120,7 +120,6 @@ enum sock_shutdown_cmd {
 struct socket_wq {
 	wait_queue_head_t	wait;
 	struct fasync_struct	*fasync_list;
-	struct rcu_head		rcu;
 } ____cacheline_aligned_in_smp;
 
 /**
#101 lore raw

Re: [patch 38/52] fs: icache RCU free inodes

Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:50PM +1000, [email protected] wrote:

RCU free the struct inode. This will allow:

Rather than what it will allow, what are the constraints this imposes on allocating and freeing a struct inode? e.g. XFS embeds the struct inode in a larger inode structure and does it's own allocation, caching and freeing of the larger structure outside of the VFS functionality.

Does this need to be converted to RCU? Do we need to do more initialisation of the struct inode than we currently do? What functions/call chains now implicitly require RCU freeing semantics on the struct inode for safe operation? What else do we need to be aware of?

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#102 lore raw

Re: [patch 38/52] fs: icache RCU free inodes

Quoted 9 lines · Dave Chinner
Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:50PM +1000, [email protected] wrote:

RCU free the struct inode. This will allow:

Rather than what it will allow, what are the constraints this imposes on allocating and freeing a struct inode? e.g. XFS embeds the struct inode in a larger inode structure and does it's own allocation, caching and freeing of the larger structure outside of the VFS functionality.

Quoted 5 lines

Does this need to be converted to RCU? Do we need to do more initialisation of the struct inode than we currently do? What functions/call chains now implicitly require RCU freeing semantics on the struct inode for safe operation? What else do we need to be aware of?

Yeah, filesystems with their own freeing functions will need to do a call_rcu to free it (many are not converted). Otherwise, there is nothing else to know. They could take advantage of RCU if they would like though.

#103 lore raw

PATCH 39/52 fs: icache rcu walk for i_sb_list

In reply to [email protected]

This enables locking to be reduced and ordering simplified.

---
 fs/drop_caches.c            |   10 ++++----
 fs/inode.c                  |   53 +++++++++++++++-----------------------------
 fs/notify/inode_mark.c      |   10 --------
 fs/notify/inotify/inotify.c |   10 --------
 fs/quota/dquot.c            |   16 ++++++-------
 5 files changed, 32 insertions(+), 67 deletions(-)
fs/drop_caches.c +5 -5
Index: linux-2.6/fs/drop_caches.c
===================================================================
@@ -16,8 +16,8 @@ static void drop_pagecache_sb(struct sup
 {
 	struct inode *inode, *toput_inode = NULL;
 
-	spin_lock(&sb_inode_list_lock);
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
 		spin_lock(&inode->i_lock);
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE|I_NEW)
 				|| inode->i_mapping->nrpages == 0) {
@@ -26,13 +26,13 @@ static void drop_pagecache_sb(struct sup
 		}
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&sb_inode_list_lock);
+		rcu_read_unlock();
 		invalidate_mapping_pages(inode->i_mapping, 0, -1);
 		iput(toput_inode);
 		toput_inode = inode;
-		spin_lock(&sb_inode_list_lock);
+		rcu_read_lock();
 	}
-	spin_unlock(&sb_inode_list_lock);
+	rcu_read_unlock();
 	iput(toput_inode);
 }
 
fs/inode.c +23 -38

161 lines not on this page · raw message

fs/notify/inode_mark.c +0 -10
Index: linux-2.6/fs/notify/inode_mark.c
===================================================================
@@ -412,14 +412,6 @@ void fsnotify_unmount_inodes(struct list
 			spin_unlock(&next_i->i_lock);
 		}
 
-		/*
-		 * We can safely drop inode_lock here because we hold
-		 * references on both inode and next_i.  Also no new inodes
-		 * will be added since the umount has begun.  Finally,
-		 * iprune_mutex keeps shrink_icache_memory() away.
-		 */
-		spin_unlock(&sb_inode_list_lock);
-
 		if (need_iput_tmp)
 			iput(need_iput_tmp);
 
@@ -429,7 +421,5 @@ void fsnotify_unmount_inodes(struct list
 		fsnotify_inode_delete(inode);
 
 		iput(inode);
-
-		spin_lock(&sb_inode_list_lock);
 	}
 }
fs/notify/inotify/inotify.c +0 -10
Index: linux-2.6/fs/notify/inotify/inotify.c
===================================================================
@@ -434,14 +434,6 @@ void inotify_unmount_inodes(struct list_
 			spin_unlock(&next_i->i_lock);
 		}
 
-		/*
-		 * We can safely drop inode_lock here because we hold
-		 * references on both inode and next_i.  Also no new inodes
-		 * will be added since the umount has begun.  Finally,
-		 * iprune_mutex keeps shrink_icache_memory() away.
-		 */
-		spin_unlock(&sb_inode_list_lock);
-
 		if (need_iput_tmp)
 			iput(need_iput_tmp);
 
@@ -459,8 +451,6 @@ void inotify_unmount_inodes(struct list_
 		}
 		mutex_unlock(&inode->inotify_mutex);
 		iput(inode);		
-
-		spin_lock(&sb_inode_list_lock);
 	}
 }
 EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
fs/quota/dquot.c +8 -8
Index: linux-2.6/fs/quota/dquot.c
===================================================================
@@ -883,8 +883,8 @@ static void add_dquot_ref(struct super_b
 	int reserved = 0;
 #endif
 
-	spin_lock(&sb_inode_list_lock);
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
 		spin_lock(&inode->i_lock);
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE|I_NEW)) {
 			spin_unlock(&inode->i_lock);
@@ -905,7 +905,7 @@ static void add_dquot_ref(struct super_b
 
 		__iget(inode);
 		spin_unlock(&inode->i_lock);
-		spin_unlock(&sb_inode_list_lock);
+		rcu_read_unlock();
 
 		iput(old_inode);
 		__dquot_initialize(inode, type);
@@ -915,9 +915,9 @@ static void add_dquot_ref(struct super_b
 		 * reference and we cannot iput it under inode_lock. So we
 		 * keep the reference and iput it later. */
 		old_inode = inode;
-		spin_lock(&sb_inode_list_lock);
+		rcu_read_lock();
 	}
-	spin_unlock(&sb_inode_list_lock);
+	rcu_read_unlock();
 	iput(old_inode);
 
 #ifdef CONFIG_QUOTA_DEBUG
@@ -995,8 +995,8 @@ static void remove_dquot_ref(struct supe
 {
 	struct inode *inode;
 
-	spin_lock(&sb_inode_list_lock);
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
 		/*
 		 *  We have to scan also I_NEW inodes because they can already
 		 *  have quota pointer initialized. Luckily, we need to touch
@@ -1006,7 +1006,7 @@ static void remove_dquot_ref(struct supe
 		if (!IS_NOQUOTA(inode))
 			remove_inode_dquot_ref(inode, type, tofree_head);
 	}
-	spin_unlock(&sb_inode_list_lock);
+	rcu_read_unlock();
 }
 
 /* Gather all references from inodes and drop them */
#104 lore raw

PATCH 40/52 fs: dcache improve scalability of pseudo filesystems

In reply to [email protected]

Regardless of how much we possibly try to scale dcache, there is likely always going to be some fundamental contention when adding or removing children under the same parent. Pseudo filesystems do not seem need to have connected dentries because by definition they are disconnected.

---
 fs/anon_inodes.c |    4 +++-
 fs/pipe.c        |    5 +++--
 net/socket.c     |    5 +++--
 3 files changed, 9 insertions(+), 5 deletions(-)
net/socket.c +1 -1
Index: linux-2.6/net/socket.c
===================================================================
@@ -371,7 +371,7 @@ static int sock_alloc_file(struct socket
 	if (unlikely(fd < 0))
 		return fd;
 
-	path.dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
+	path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name);
 	if (unlikely(!path.dentry)) {
 		put_unused_fd(fd);
 		return -ENOMEM;
fs/anon_inodes.c +1 -1
Index: linux-2.6/fs/anon_inodes.c
===================================================================
@@ -104,7 +104,7 @@ struct file *anon_inode_getfile(const ch
 	this.name = name;
 	this.len = strlen(name);
 	this.hash = 0;
-	path.dentry = d_alloc(anon_inode_mnt->mnt_sb->s_root, &this);
+	path.dentry = d_alloc_pseudo(anon_inode_mnt->mnt_sb, &this);
 	if (!path.dentry)
 		goto err_module;
 
fs/pipe.c +1 -1
Index: linux-2.6/fs/pipe.c
===================================================================
@@ -997,7 +997,7 @@ struct file *create_write_pipe(int flags
 		goto err;
 
 	err = -ENOMEM;
-	path.dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &name);
+	path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name);
 	if (!path.dentry)
 		goto err_inode;
 	path.mnt = mntget(pipe_mnt);
fs/dcache.c +12 -0
Index: linux-2.6/fs/dcache.c
===================================================================
@@ -1255,6 +1255,18 @@ struct dentry *d_alloc(struct dentry * p
 }
 EXPORT_SYMBOL(d_alloc);
 
+struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
+{
+	struct dentry *dentry = d_alloc(NULL, name);
+	if (dentry) {
+		dentry->d_sb = sb;
+		dentry->d_parent = dentry;
+		dentry->d_flags |= DCACHE_DISCONNECTED;
+	}
+	return dentry;
+}
+EXPORT_SYMBOL(d_alloc_pseudo);
+
 struct dentry *d_alloc_name(struct dentry *parent, const char *name)
 {
 	struct qstr q;
include/linux/dcache.h +1 -0
Index: linux-2.6/include/linux/dcache.h
===================================================================
@@ -225,6 +225,7 @@ extern void d_delete(struct dentry *);
 
 /* allocate/de-allocate */
 extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
+extern struct dentry * d_alloc_pseudo(struct super_block *, const struct qstr *);
 extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
 extern struct dentry * d_add_ci(struct dentry *, struct inode *, struct qstr *);
 extern struct dentry * d_obtain_alias(struct inode *);
#105 lore raw

PATCH 41/52 fs: icache reduce atomics

In reply to [email protected]

Put inode counters under locks to reduce atomics.

---
 fs/fs-writeback.c  |    6 ++----
 fs/inode.c         |   30 ++++++++++++------------------
 include/linux/fs.h |    4 ++--
 3 files changed, 16 insertions(+), 24 deletions(-)
fs/inode.c +15 -21
Index: linux-2.6/fs/inode.c
===================================================================
@@ -141,8 +141,8 @@ static DECLARE_RWSEM(iprune_sem);
  * Statistics gathering..
  */
 struct inodes_stat_t inodes_stat = {
-	.nr_inodes = ATOMIC_INIT(0),
-	.nr_unused = ATOMIC_INIT(0),
+	.nr_inodes = 0,
+	.nr_unused = 0,
 };
 
 static struct kmem_cache *inode_cachep __read_mostly;
@@ -390,7 +390,6 @@ static void dispose_list(struct list_hea
 		destroy_inode(inode);
 		nr_disposed++;
 	}
-	atomic_sub(nr_disposed, &inodes_stat.nr_inodes);
 }
 
 /*
@@ -399,7 +398,7 @@ static void dispose_list(struct list_hea
 static int invalidate_list(struct list_head *head, struct list_head *dispose)
 {
 	struct list_head *next;
-	int busy = 0, count = 0;
+	int busy = 0;
 
 	next = head->next;
 	for (;;) {
@@ -419,19 +418,17 @@ static int invalidate_list(struct list_h
 		if (!inode->i_count) {
 			spin_lock(&wb_inode_list_lock);
 			list_del(&inode->i_list);
+			inodes_stat.nr_unused--;
 			spin_unlock(&wb_inode_list_lock);
 			WARN_ON(inode->i_state & I_NEW);
 			inode->i_state |= I_FREEING;
 			spin_unlock(&inode->i_lock);
 			list_add(&inode->i_list, dispose);
-			count++;
 			continue;
 		}
 		spin_unlock(&inode->i_lock);
 		busy = 1;
 	}
-	/* only unused inodes may be cached with i_count zero */
-	atomic_sub(count, &inodes_stat.nr_unused);
 	return busy;
 }
 
@@ -483,7 +480,6 @@ EXPORT_SYMBOL(invalidate_inodes);
 static void prune_icache(int nr_to_scan)
 {
 	LIST_HEAD(freeable);
-	int nr_pruned = 0;
 	unsigned long reap = 0;
 
 	down_read(&iprune_sem);
@@ -504,7 +500,7 @@ again:
 		if (inode->i_count || (inode->i_state & ~I_REFERENCED)) {
 			list_del_init(&inode->i_list);
 			spin_unlock(&inode->i_lock);
-			atomic_dec(&inodes_stat.nr_unused);
+			inodes_stat.nr_unused--;
 			continue;
 		}
 		if (inode->i_state) {
@@ -530,9 +526,8 @@ again:
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state |= I_FREEING;
 		spin_unlock(&inode->i_lock);
-		nr_pruned++;
+		inodes_stat.nr_unused--;
 	}
-	atomic_sub(nr_pruned, &inodes_stat.nr_unused);
 	if (current_is_kswapd())
 		__count_vm_events(KSWAPD_INODESTEAL, reap);
 	else
@@ -564,8 +559,7 @@ static int shrink_icache_memory(int nr,
 			return -1;
 		prune_icache(nr);
 	}
-	return (atomic_read(&inodes_stat.nr_unused) / 100) *
-					sysctl_vfs_cache_pressure;
+	return inodes_stat.nr_unused / 100 * sysctl_vfs_cache_pressure;
 }
 
 static struct shrinker icache_shrinker = {
@@ -661,9 +655,9 @@ static inline void
 __inode_add_to_lists(struct super_block *sb, struct inode_hash_bucket *b,
 			struct inode *inode)
 {
-	atomic_inc(&inodes_stat.nr_inodes);
 	spin_lock(&sb_inode_list_lock);
 	list_add_rcu(&inode->i_sb_list, &sb->s_inodes);
+	inodes_stat.nr_inodes++;
 	spin_unlock(&sb_inode_list_lock);
 	if (b) {
 		spin_lock_bucket(b);
@@ -1309,17 +1303,17 @@ void generic_delete_inode(struct inode *
 	if (!list_empty(&inode->i_list)) {
 		spin_lock(&wb_inode_list_lock);
 		list_del_init(&inode->i_list);
-		spin_unlock(&wb_inode_list_lock);
 		if (!inode->i_state)
-			atomic_dec(&inodes_stat.nr_unused);
+			inodes_stat.nr_unused--;
+		spin_unlock(&wb_inode_list_lock);
 	}
 	spin_lock(&sb_inode_list_lock);
 	list_del_rcu(&inode->i_sb_list);
+	inodes_stat.nr_inodes--;
 	spin_unlock(&sb_inode_list_lock);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
 	spin_unlock(&inode->i_lock);
-	atomic_dec(&inodes_stat.nr_inodes);
 
 	if (op->delete_inode) {
 		void (*delete)(struct inode *) = op->delete_inode;
@@ -1367,8 +1361,8 @@ int generic_detach_inode(struct inode *i
 					list_empty(&inode->i_list)) {
 				spin_lock(&wb_inode_list_lock);
 				list_add(&inode->i_list, &inode_unused);
+				inodes_stat.nr_unused++;
 				spin_unlock(&wb_inode_list_lock);
-				atomic_inc(&inodes_stat.nr_unused);
 			}
 			spin_unlock(&inode->i_lock);
 			return 0;
@@ -1385,17 +1379,17 @@ int generic_detach_inode(struct inode *i
 	if (!list_empty(&inode->i_list)) {
 		spin_lock(&wb_inode_list_lock);
 		list_del_init(&inode->i_list);
-		spin_unlock(&wb_inode_list_lock);
 		if (!inode->i_state)
-			atomic_dec(&inodes_stat.nr_unused);
+			inodes_stat.nr_unused--;
+		spin_unlock(&wb_inode_list_lock);
 	}
 	spin_lock(&sb_inode_list_lock);
 	list_del_rcu(&inode->i_sb_list);
+	inodes_stat.nr_inodes--;
 	spin_unlock(&sb_inode_list_lock);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
 	spin_unlock(&inode->i_lock);
-	atomic_dec(&inodes_stat.nr_inodes);
 	return 1;
 }
 EXPORT_SYMBOL_GPL(generic_detach_inode);
include/linux/fs.h +6 -6
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -31,6 +31,12 @@
 #define SEEK_END	2	/* seek relative to end of file */
 #define SEEK_MAX	SEEK_END
 
+struct inodes_stat_t {
+	int nr_inodes;
+	int nr_unused;
+	int dummy[5];		/* padding for sysctl ABI compatibility */
+};
+
 /* And dynamically-tunable limits and defaults: */
 struct files_stat_struct {
 	int nr_files;		/* read only */
@@ -411,12 +417,6 @@ typedef int (get_block_t)(struct inode *
 typedef void (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 			ssize_t bytes, void *private);
 
-struct inodes_stat_t {
-	atomic_t nr_inodes;
-	atomic_t nr_unused;
-	int dummy[5];		/* padding for sysctl ABI compatibility */
-};
-
 /*
  * Attribute flags.  These should be or-ed together to figure out what
  * has been changed!
fs/fs-writeback.c +2 -4
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -904,8 +904,7 @@ static long wb_check_old_data_flush(stru
 	wb->last_old_flush = jiffies;
 	nr_pages = global_page_state(NR_FILE_DIRTY) +
 			global_page_state(NR_UNSTABLE_NFS) +
-			(atomic_read(&inodes_stat.nr_inodes) -
-			atomic_read(&inodes_stat.nr_unused));
+			inodes_stat.nr_inodes - inodes_stat.nr_unused;
 
 	if (nr_pages) {
 		struct wb_writeback_args args = {
@@ -1258,8 +1257,7 @@ void writeback_inodes_sb(struct super_bl
 	long nr_to_write;
 
 	nr_to_write = nr_dirty + nr_unstable +
-			(atomic_read(&inodes_stat.nr_inodes) -
-			atomic_read(&inodes_stat.nr_unused));
+			inodes_stat.nr_inodes - inodes_stat.nr_unused;
 
 	bdi_start_writeback(sb->s_bdi, sb, nr_to_write);
 }
#106 lore raw

PATCH 42/52 fs: icache per-cpu last_ino allocator

In reply to [email protected]

From: Eric Dumazet <[email protected]>

new_inode() dirties a contended cache line to get increasing inode numbers.

Solve this problem by providing to each cpu a per_cpu variable, feeded by the shared last_ino, but once every 1024 allocations.

This reduce contention on the shared last_ino, and give same spreading ino numbers than before. (same wraparound after 2^32 allocations)

---
 fs/inode.c |   42 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 7 deletions(-)
fs/inode.c +35 -7
Index: linux-2.6/fs/inode.c
===================================================================
@@ -664,6 +664,40 @@ __inode_add_to_lists(struct super_block
 	}
 }
 
+#ifdef CONFIG_SMP
+/*
+ * Each cpu owns a range of 1024 numbers.
+ * 'shared_last_ino' is dirtied only once out of 1024 allocations,
+ * to renew the exhausted range.
+ *
+ * On a 32bit, non LFS stat() call, glibc will generate an EOVERFLOW
+ * error if st_ino won't fit in target struct field. Use 32bit counter
+ * here to attempt to avoid that.
+ */
+static DEFINE_PER_CPU(int, last_ino);
+static atomic_t shared_last_ino;
+
+static int last_ino_get(void)
+{
+	int *p = &get_cpu_var(last_ino);
+	int res = *p;
+
+	if (unlikely((res & 1023) == 0))
+		res = atomic_add_return(1024, &shared_last_ino) - 1024;
+
+	*p = ++res;
+	put_cpu_var(last_ino);
+	return res;
+}
+#else
+static int last_ino_get(void)
+{
+	static int last_ino;
+
+	return ++last_ino;
+}
+#endif
+
 /**
  * inode_add_to_lists - add a new inode to relevant lists
  * @sb: superblock inode belongs to
@@ -700,19 +734,13 @@ EXPORT_SYMBOL_GPL(inode_add_to_lists);
  */
 struct inode *new_inode(struct super_block *sb)
 {
-	/*
-	 * On a 32bit, non LFS stat() call, glibc will generate an EOVERFLOW
-	 * error if st_ino won't fit in target struct field. Use 32bit counter
-	 * here to attempt to avoid that.
-	 */
-	static atomic_t last_ino = ATOMIC_INIT(0);
 	struct inode *inode;
 
 	inode = alloc_inode(sb);
 	if (inode) {
 		/* XXX: init as locked for speedup */
 		spin_lock(&inode->i_lock);
-		inode->i_ino = atomic_inc_return(&last_ino);
+		inode->i_ino = last_ino_get();
 		inode->i_state = 0;
 		__inode_add_to_lists(sb, NULL, inode);
 		spin_unlock(&inode->i_lock);
#107 lore raw

Re: [patch 42/52] fs: icache per-cpu last_ino allocator

Quoted 6 lines · [email protected] writes:

From: Eric Dumazet <[email protected]>

new_inode() dirties a contended cache line to get increasing inode numbers.

Solve this problem by providing to each cpu a per_cpu variable, feeded by the shared last_ino, but once every 1024 allocations.

Most file systems don't even need this because they
allocate their own inode numbers, right?. So perhaps it could be turned off for all of those, e.g. with a superblock flag.

I guess the main customer is sockets only.

fs/inode.c __inode_add_to_lists(struct super_block · line 675

Quoted 9 lines
fs/inode.c +9 -0
+#ifdef CONFIG_SMP
+/*
+ * Each cpu owns a range of 1024 numbers.
+ * 'shared_last_ino' is dirtied only once out of 1024 allocations,
+ * to renew the exhausted range.
+ *
+ * On a 32bit, non LFS stat() call, glibc will generate an EOVERFLOW
+ * error if st_ino won't fit in target struct field. Use 32bit counter
+ * here to attempt to avoid that.

I don't understand how the 32bit counter should prevent that.

fs/inode.c __inode_add_to_lists(struct super_block · line 678

Quoted 3 lines

+ */

fs/inode.c +2 -0
+static DEFINE_PER_CPU(int, last_ino);
+static atomic_t shared_last_ino;

With the 1024 skip, isn't overflow much more likely, just scaling with the number of CPUs on a large CPU number systems, even if there aren't that many new inodes?

fs/inode.c __inode_add_to_lists(struct super_block · line 686

Quoted 7 lines
fs/inode.c +7 -0
+static int last_ino_get(void)
+{
+	int *p = &get_cpu_var(last_ino);
+	int res = *p;
+
+	if (unlikely((res & 1023) == 0))
+		res = atomic_add_return(1024, &shared_last_ino) - 1024;

The magic numbers really want to be defines?

-Andi

Signature · 1 line
[email protected] -- Speaking for myself only.
#108 lore raw

Re: [patch 42/52] fs: icache per-cpu last_ino allocator

Quoted 4 lines · Andi Kleen
Quoted 2 lines · [email protected] writes:

From: Eric Dumazet <[email protected]>

Quoted 8 lines
Quoted 4 lines

new_inode() dirties a contended cache line to get increasing inode numbers.

Solve this problem by providing to each cpu a per_cpu variable, feeded by the shared last_ino, but once every 1024 allocations.

Most file systems don't even need this because they
allocate their own inode numbers, right?. So perhaps it could be turned off for all of those, e.g. with a superblock flag.

That's right. More or less it just requires alloc_inode to be exported, adding more branches in new_inode would not be a good way to go.

But I didn't want to start microoptimisations in filesystems just yet.

Quoted 1 line

I guess the main customer is sockets only.

I guess. Sockets and ram based filesystems. Interestingly I don't know really what it's for (in socket code it's mostly for reporting and hashing it seems). It sure isn't guaranteed to be unique.

Anyway it's outside the scope of this patchset to change functionality at all.

Quoted 11 lines

fs/inode.c __inode_add_to_lists(struct super_block · line 675

Quoted 9 lines
fs/inode.c +9 -0
+#ifdef CONFIG_SMP
+/*
+ * Each cpu owns a range of 1024 numbers.
+ * 'shared_last_ino' is dirtied only once out of 1024 allocations,
+ * to renew the exhausted range.
+ *
+ * On a 32bit, non LFS stat() call, glibc will generate an EOVERFLOW
+ * error if st_ino won't fit in target struct field. Use 32bit counter
+ * here to attempt to avoid that.

I don't understand how the 32bit counter should prevent that.

Well I think glibc will convert 64 bit stat struct to 32bit for old apps. It detects if the ino can't fit in 32 bits.

Quoted 6 lines

fs/inode.c __inode_add_to_lists(struct super_block · line 678

Quoted 2 lines
fs/inode.c +2 -0
+static DEFINE_PER_CPU(int, last_ino);
+static atomic_t shared_last_ino;

With the 1024 skip, isn't overflow much more likely, just scaling with the number of CPUs on a large CPU number systems, even if there aren't that many new inodes?

Well EOVERFLOW should never happen with only the low 32 significant bits set in the inode. If you are worried about wrapping the counter, then no I don't think it is much more likely.

Because each CPU will only reserve another 1024 inode interval after it has already allocated 1024 numbers. So the most wastage you will get is (1024-1)*NR_CPUS -- somewhere around 1/1000th of the available range.

I guess overflow will be more common now because it will be possible to allocate inodes much faster on such a huge machine :)

Quoted 9 lines

fs/inode.c __inode_add_to_lists(struct super_block · line 686

Quoted 7 lines
fs/inode.c +7 -0
+static int last_ino_get(void)
+{
+	int *p = &get_cpu_var(last_ino);
+	int res = *p;
+
+	if (unlikely((res & 1023) == 0))
+		res = atomic_add_return(1024, &shared_last_ino) - 1024;

The magic numbers really want to be defines?

Sure OK.

#109 lore raw

Re: [patch 42/52] fs: icache per-cpu last_ino allocator

Quoted 3 lines · Nick Piggin

That's right. More or less it just requires alloc_inode to be exported, adding more branches in new_inode would not be a good way to go.

One test/branch shouldn't hurt much.

Quoted 5 lines
Quoted 1 line

I guess the main customer is sockets only.

I guess. Sockets and ram based filesystems. Interestingly I don't know really what it's for (in socket code it's mostly for reporting and hashing it seems). It sure isn't guaranteed to be unique.

Maybe it could be generated lazily on access for those? I suppose stat on a socket is relatively rare. The only problem is would need an accessor.

But ok out of scope.

Quoted 2 lines

Well I think glibc will convert 64 bit stat struct to 32bit for old apps. It detects if the ino can't fit in 32 bits.

... and will fail the stat.

-Andi

Signature · 1 line
[email protected] -- Speaking for myself only.
#110 lore raw

Re: [patch 42/52] fs: icache per-cpu last_ino allocator

Quoted 6 lines · Andi Kleen
Quoted 3 lines · Nick Piggin

That's right. More or less it just requires alloc_inode to be exported, adding more branches in new_inode would not be a good way to go.

One test/branch shouldn't hurt much.

If we go through filesystems anyway may as well just use alloc_inode.

Quoted 11 lines
Quoted 5 lines
Quoted 1 line

I guess the main customer is sockets only.

I guess. Sockets and ram based filesystems. Interestingly I don't know really what it's for (in socket code it's mostly for reporting and hashing it seems). It sure isn't guaranteed to be unique.

Maybe it could be generated lazily on access for those? I suppose stat on a socket is relatively rare. The only problem is would need an accessor.

But ok out of scope.

Yea that might work. sock_i_ino() and ->dname covers a lot.

Quoted 4 lines
Quoted 2 lines

Well I think glibc will convert 64 bit stat struct to 32bit for old apps. It detects if the ino can't fit in 32 bits.

... and will fail the stat.

Which is what we're trying to avoid, I guess.

#111 lore raw

PATCH 43/52 fs: icache per-cpu nr_inodes counter

In reply to [email protected]

From: Eric Dumazet <[email protected]>

Avoids cache line ping pongs between cpus and prepare next patch, because updates of nr_inodes dont need inode_lock anymore.

---
 fs/fs-writeback.c  |    4 ++--
 fs/inode.c         |   31 ++++++++++++++++++++++++++++---
 include/linux/fs.h |    5 ++++-
 kernel/sysctl.c    |    4 ++--
 4 files changed, 36 insertions(+), 8 deletions(-)
fs/fs-writeback.c +2 -2
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -904,7 +904,7 @@ static long wb_check_old_data_flush(stru
 	wb->last_old_flush = jiffies;
 	nr_pages = global_page_state(NR_FILE_DIRTY) +
 			global_page_state(NR_UNSTABLE_NFS) +
-			inodes_stat.nr_inodes - inodes_stat.nr_unused;
+			get_nr_inodes() - inodes_stat.nr_unused;
 
 	if (nr_pages) {
 		struct wb_writeback_args args = {
@@ -1257,7 +1257,7 @@ void writeback_inodes_sb(struct super_bl
 	long nr_to_write;
 
 	nr_to_write = nr_dirty + nr_unstable +
-			inodes_stat.nr_inodes - inodes_stat.nr_unused;
+			get_nr_inodes() - inodes_stat.nr_unused;
 
 	bdi_start_writeback(sb->s_bdi, sb, nr_to_write);
 }
fs/inode.c +28 -3
Index: linux-2.6/fs/inode.c
===================================================================
@@ -144,9 +144,33 @@ struct inodes_stat_t inodes_stat = {
 	.nr_inodes = 0,
 	.nr_unused = 0,
 };
+struct percpu_counter nr_inodes;
 
 static struct kmem_cache *inode_cachep __read_mostly;
 
+int get_nr_inodes(void)
+{
+	return percpu_counter_sum_positive(&nr_inodes);
+}
+
+/*
+ * Handle nr_dentry sysctl
+ */
+#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+int proc_nr_inodes(ctl_table *table, int write,
+		   void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	inodes_stat.nr_inodes = get_nr_inodes();
+	return proc_dointvec(table, write, buffer, lenp, ppos);
+}
+#else
+int proc_nr_inodes(ctl_table *table, int write,
+		   void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	return -ENOSYS;
+}
+#endif
+
 static void wake_up_inode(struct inode *inode)
 {
 	/*
@@ -657,8 +681,8 @@ __inode_add_to_lists(struct super_block
 {
 	spin_lock(&sb_inode_list_lock);
 	list_add_rcu(&inode->i_sb_list, &sb->s_inodes);
-	inodes_stat.nr_inodes++;
 	spin_unlock(&sb_inode_list_lock);
+	percpu_counter_inc(&nr_inodes);
 	if (b) {
 		spin_lock_bucket(b);
 		hlist_bl_add_head_rcu(&inode->i_hash, &b->head);
@@ -1337,8 +1361,8 @@ void generic_delete_inode(struct inode *
 	}
 	spin_lock(&sb_inode_list_lock);
 	list_del_rcu(&inode->i_sb_list);
-	inodes_stat.nr_inodes--;
 	spin_unlock(&sb_inode_list_lock);
+	percpu_counter_dec(&nr_inodes);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
 	spin_unlock(&inode->i_lock);
@@ -1413,8 +1437,8 @@ int generic_detach_inode(struct inode *i
 	}
 	spin_lock(&sb_inode_list_lock);
 	list_del_rcu(&inode->i_sb_list);
-	inodes_stat.nr_inodes--;
 	spin_unlock(&sb_inode_list_lock);
+	percpu_counter_dec(&nr_inodes);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
 	spin_unlock(&inode->i_lock);
@@ -1723,6 +1747,7 @@ void __init inode_init(void)
 {
 	int loop;
 
+	percpu_counter_init(&nr_inodes, 0);
 	/* inode slab cache */
 	inode_cachep = kmem_cache_create("inode_cache",
 					 sizeof(struct inode),
include/linux/fs.h +4 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -406,6 +406,8 @@ extern struct files_stat_struct files_st
 extern int get_max_files(void);
 extern int sysctl_nr_open;
 extern struct inodes_stat_t inodes_stat;
+extern struct percpu_counter nr_inodes;
+extern int get_nr_inodes(void);
 extern int leases_enable, lease_break_time;
 #ifdef CONFIG_DNOTIFY
 extern int dir_notify_enable;
@@ -2514,7 +2516,8 @@ ssize_t simple_attr_write(struct file *f
 struct ctl_table;
 int proc_nr_files(struct ctl_table *table, int write,
 		  void __user *buffer, size_t *lenp, loff_t *ppos);
-
+int proc_nr_inodes(struct ctl_table *table, int write,
+		   void __user *buffer, size_t *lenp, loff_t *ppos);
 int __init get_filesystem_list(char *buf);
 
 #define ACC_MODE(x) ("\004\002\006\006"[(x)&O_ACCMODE])
kernel/sysctl.c +2 -2
Index: linux-2.6/kernel/sysctl.c
===================================================================
@@ -1338,14 +1338,14 @@ static struct ctl_table fs_table[] = {
 		.data		= &inodes_stat,
 		.maxlen		= 2*sizeof(int),
 		.mode		= 0444,
-		.proc_handler	= proc_dointvec,
+		.proc_handler	= proc_nr_inodes,
 	},
 	{
 		.procname	= "inode-state",
 		.data		= &inodes_stat,
 		.maxlen		= 7*sizeof(int),
 		.mode		= 0444,
-		.proc_handler	= proc_dointvec,
+		.proc_handler	= proc_nr_inodes,
 	},
 	{
 		.procname	= "file-nr",
#112 lore raw

PATCH 44/52 fs: icache per-CPU sb inode lists and locks

In reply to [email protected]
---
 fs/drop_caches.c                 |   37 ++++++----
 fs/fs-writeback.c                |   82 +++++++++++++-----------
 fs/inode.c                       |  128 +++++++++++++++++++++++++------------
 fs/notify/inode_mark.c           |  108 +++++++++++++++++--------------
 fs/notify/inotify/inotify.c      |  132 ++++++++++++++++++++-------------------
 fs/quota/dquot.c                 |   98 +++++++++++++++++-----------
 fs/super.c                       |   19 +++++
 include/linux/fs.h               |   10 ++
 include/linux/fsnotify_backend.h |    4 -
 include/linux/inotify.h          |    4 -
 10 files changed, 373 insertions(+), 249 deletions(-)
fs/inode.c +75 -31
Index: linux-2.6/fs/inode.c
===================================================================
@@ -26,10 +26,11 @@
 #include <linux/async.h>
 #include <linux/posix_acl.h>
 #include <linux/bit_spinlock.h>
+#include <linux/lglock.h>
 
 /*
  * Usage:
- * sb_inode_list_lock protects:
+ * inode_list_lglock protects:
  *   s_inodes, i_sb_list
  * inode_hash_bucket lock protects:
  *   inode hash table, i_hash
@@ -45,7 +46,7 @@
  * Ordering:
  * inode_lock
  *   inode->i_lock
- *     sb_inode_list_lock
+ *     inode_list_lglock
  *     wb_inode_list_lock
  *     inode_hash_bucket lock
  */
@@ -120,7 +121,9 @@ static struct inode_hash_bucket *inode_h
  * NOTE! You also have to own the lock if you change
  * the i_state of an inode while it is in use..
  */
-DEFINE_SPINLOCK(sb_inode_list_lock);
+DECLARE_LGLOCK(inode_list_lglock);
+DEFINE_LGLOCK(inode_list_lglock);
+
 DEFINE_SPINLOCK(wb_inode_list_lock);
 
 /*
@@ -382,6 +385,8 @@ void clear_inode(struct inode *inode)
 }
 EXPORT_SYMBOL(clear_inode);
 
+static void inode_sb_list_del(struct inode *inode);
+
 /*
  * dispose_list - dispose of the contents of a local list
  * @head: the head of the list to free
@@ -405,9 +410,7 @@ static void dispose_list(struct list_hea
 
 		spin_lock(&inode->i_lock);
 		__remove_inode_hash(inode);
-		spin_lock(&sb_inode_list_lock);
-		list_del_rcu(&inode->i_sb_list);
-		spin_unlock(&sb_inode_list_lock);
+		inode_sb_list_del(inode);
 		spin_unlock(&inode->i_lock);
 
 		wake_up_inode(inode);
@@ -419,20 +422,12 @@ static void dispose_list(struct list_hea
 /*
  * Invalidate all inodes for a device.
  */
-static int invalidate_list(struct list_head *head, struct list_head *dispose)
+static int invalidate_sb_inodes(struct super_block *sb, struct list_head *dispose)
 {
-	struct list_head *next;
+	struct inode *inode;
 	int busy = 0;
 
-	next = head->next;
-	for (;;) {
-		struct list_head *tmp = next;
-		struct inode *inode;
-
-		next = next->next;
-		if (tmp == head)
-			break;
-		inode = list_entry(tmp, struct inode, i_sb_list);
+	do_inode_list_for_each_entry_rcu(sb, inode) {
 		spin_lock(&inode->i_lock);
 		if (inode->i_state & I_NEW) {
 			spin_unlock(&inode->i_lock);
@@ -452,7 +447,8 @@ static int invalidate_list(struct list_h
 		}
 		spin_unlock(&inode->i_lock);
 		busy = 1;
-	}
+	} while_inode_list_for_each_entry_rcu
+
 	return busy;
 }
 
@@ -476,9 +472,9 @@ int invalidate_inodes(struct super_block
 	 */
 	down_write(&iprune_sem);
 //	spin_lock(&sb_inode_list_lock); XXX: is this safe?
-	inotify_unmount_inodes(&sb->s_inodes);
-	fsnotify_unmount_inodes(&sb->s_inodes);
-	busy = invalidate_list(&sb->s_inodes, &throw_away);
+	inotify_unmount_inodes(sb);
+	fsnotify_unmount_inodes(sb);
+	busy = invalidate_sb_inodes(sb, &throw_away);
 //	spin_unlock(&sb_inode_list_lock);
 
 	dispose_list(&throw_away);
@@ -675,13 +671,63 @@ static unsigned long hash(struct super_b
 	return tmp & I_HASHMASK;
 }
 
+static inline int inode_list_cpu(struct inode *inode)
+{
+#ifdef CONFIG_SMP
+	return inode->i_sb_list_cpu;
+#else
+	return smp_processor_id();
+#endif
+}
+
+/* helper for file_sb_list_add to reduce ifdefs */
+static inline void __inode_sb_list_add(struct inode *inode, struct super_block *sb)
+{
+	struct list_head *list;
+#ifdef CONFIG_SMP
+	int cpu;
+	cpu = smp_processor_id();
+	inode->i_sb_list_cpu = cpu;
+	list = per_cpu_ptr(sb->s_inodes, cpu);
+#else
+	list = &sb->s_inodes;
+#endif
+	list_add_rcu(&inode->i_sb_list, list);
+}
+
+/**
+ * inode_sb_list_add - add an inode to the sb's file list
+ * @inode: inode to add
+ * @sb: sb to add it to
+ *
+ * Use this function to associate an with the superblock it belongs to.
+ */
+static void inode_sb_list_add(struct inode *inode, struct super_block *sb)
+{
+	lg_local_lock(inode_list_lglock);
+	__inode_sb_list_add(inode, sb);
+	lg_local_unlock(inode_list_lglock);
+}
+
+/**
+ * inode_sb_list_del - remove an inode from the sb's inode list
+ * @inode: inode to remove
+ * @sb: sb to remove it from
+ *
+ * Use this function to remove an inode from its superblock.
+ */
+static void inode_sb_list_del(struct inode *inode)
+{
+	lg_local_lock_cpu(inode_list_lglock, inode_list_cpu(inode));
+	list_del_rcu(&inode->i_sb_list);
+	lg_local_unlock_cpu(inode_list_lglock, inode_list_cpu(inode));
+}
+
 static inline void
 __inode_add_to_lists(struct super_block *sb, struct inode_hash_bucket *b,
 			struct inode *inode)
 {
-	spin_lock(&sb_inode_list_lock);
-	list_add_rcu(&inode->i_sb_list, &sb->s_inodes);
-	spin_unlock(&sb_inode_list_lock);
+	inode_sb_list_add(inode, sb);
 	percpu_counter_inc(&nr_inodes);
 	if (b) {
 		spin_lock_bucket(b);
@@ -1221,6 +1267,7 @@ repeat:
 			continue;
 		if (!spin_trylock(&old->i_lock)) {
 			spin_unlock_bucket(b);
+			cpu_relax();
 			goto repeat;
 		}
 		goto found_old;
@@ -1266,7 +1313,6 @@ repeat:
 		if (!spin_trylock(&old->i_lock)) {
 			spin_unlock_bucket(b);
 			cpu_relax();
-			cpu_relax();
 			goto repeat;
 		}
 		goto found_old;
@@ -1361,9 +1407,7 @@ void generic_delete_inode(struct inode *
 			inodes_stat.nr_unused--;
 		spin_unlock(&wb_inode_list_lock);
 	}
-	spin_lock(&sb_inode_list_lock);
-	list_del_rcu(&inode->i_sb_list);
-	spin_unlock(&sb_inode_list_lock);
+	inode_sb_list_del(inode);
 	percpu_counter_dec(&nr_inodes);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
@@ -1437,9 +1481,7 @@ int generic_detach_inode(struct inode *i
 			inodes_stat.nr_unused--;
 		spin_unlock(&wb_inode_list_lock);
 	}
-	spin_lock(&sb_inode_list_lock);
-	list_del_rcu(&inode->i_sb_list);
-	spin_unlock(&sb_inode_list_lock);
+	inode_sb_list_del(inode);
 	percpu_counter_dec(&nr_inodes);
 	WARN_ON(inode->i_state & I_NEW);
 	inode->i_state |= I_FREEING;
@@ -1759,6 +1801,8 @@ void __init inode_init(void)
 					 init_once);
 	register_shrinker(&icache_shrinker);
 
+	lg_lock_init(inode_list_lglock);
+
 	/* Hash may have been set up in inode_init_early */
 	if (!hashdist)
 		return;
include/linux/fs.h +57 -1

80 lines not on this page · raw message

fs/super.c +16 -1
Index: linux-2.6/fs/super.c
===================================================================
@@ -68,12 +68,26 @@ static struct super_block *alloc_super(s
 			for_each_possible_cpu(i)
 				INIT_LIST_HEAD(per_cpu_ptr(s->s_files, i));
 		}
+
+		s->s_inodes = alloc_percpu(struct list_head);
+		if (!s->s_inodes) {
+			free_percpu(s->s_files);
+			security_sb_free(s);
+			kfree(s);
+			s = NULL;
+			goto out;
+		} else {
+			int i;
+
+			for_each_possible_cpu(i)
+				INIT_LIST_HEAD(per_cpu_ptr(s->s_inodes, i));
+		}
 #else
 		INIT_LIST_HEAD(&s->s_files);
+		INIT_LIST_HEAD(&s->s_inodes);
 #endif
 		INIT_LIST_HEAD(&s->s_instances);
 		INIT_HLIST_BL_HEAD(&s->s_anon);
-		INIT_LIST_HEAD(&s->s_inodes);
 		INIT_LIST_HEAD(&s->s_dentry_lru);
 		init_rwsem(&s->s_umount);
 		mutex_init(&s->s_lock);
@@ -125,6 +139,7 @@ out:
 static inline void destroy_super(struct super_block *s)
 {
 #ifdef CONFIG_SMP
+	free_percpu(s->s_inodes);
 	free_percpu(s->s_files);
 #endif
 	security_sb_free(s);
fs/drop_caches.c +2 -2
Index: linux-2.6/fs/drop_caches.c
===================================================================
@@ -17,7 +17,7 @@ static void drop_pagecache_sb(struct sup
 	struct inode *inode, *toput_inode = NULL;
 
 	rcu_read_lock();
-	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
+	do_inode_list_for_each_entry_rcu(sb, inode) {
 		spin_lock(&inode->i_lock);
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE|I_NEW)
 				|| inode->i_mapping->nrpages == 0) {
@@ -31,7 +31,7 @@ static void drop_pagecache_sb(struct sup
 		iput(toput_inode);
 		toput_inode = inode;
 		rcu_read_lock();
-	}
+	} while_inode_list_for_each_entry_rcu
 	rcu_read_unlock();
 	iput(toput_inode);
 }
fs/fs-writeback.c +15 -14
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -1198,17 +1198,17 @@ static void wait_sb_inodes(struct super_
 	 */
 	WARN_ON(!rwsem_is_locked(&sb->s_umount));
 
-	/*
-	 * Data integrity sync. Must wait for all pages under writeback,
-	 * because there may have been pages dirtied before our sync
-	 * call, but which had writeout started before we write it out.
-	 * In which case, the inode may not be on the dirty list, but
-	 * we still have to wait for that writeout.
-	 */
 	rcu_read_lock();
-	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
+	do_inode_list_for_each_entry_rcu(sb, inode) {
 		struct address_space *mapping;
 
+		/*
+		 * Data integrity sync. Must wait for all pages under writeback,
+		 * because there may have been pages dirtied before our sync
+		 * call, but which had writeout started before we write it out.
+		 * In which case, the inode may not be on the dirty list, but
+		 * we still have to wait for that writeout.
+		 */
 		mapping = inode->i_mapping;
 		if (mapping->nrpages == 0)
 			continue;
@@ -1222,11 +1222,12 @@ static void wait_sb_inodes(struct super_
 		spin_unlock(&inode->i_lock);
 		rcu_read_unlock();
 		/*
-		 * We hold a reference to 'inode' so it couldn't have been
-		 * removed from s_inodes list while we dropped the i_lock.  We
-		 * cannot iput the inode now as we can be holding the last
-		 * reference and we cannot iput it under spinlock. So we keep
-		 * the reference and iput it later.
+		 * We hold a reference to 'inode' so it couldn't have
+		 * been removed from s_inodes list while we dropped the
+		 * i_lock.  We cannot iput the inode now as we can be
+		 * holding the last reference and we cannot iput it
+		 * under spinlock. So we keep the reference and iput it
+		 * later.
 		 */
 		iput(old_inode);
 		old_inode = inode;
@@ -1236,7 +1237,7 @@ static void wait_sb_inodes(struct super_
 		cond_resched();
 
 		rcu_read_lock();
-	}
+	} while_inode_list_for_each_entry_rcu
 	rcu_read_unlock();
 	iput(old_inode);
 }
fs/notify/inode_mark.c +3 -3
Index: linux-2.6/fs/notify/inode_mark.c
===================================================================
@@ -361,11 +361,11 @@ int fsnotify_add_mark(struct fsnotify_ma
  * of inodes, and with iprune_mutex held, keeping shrink_icache_memory() at bay.
  * We temporarily drop inode_lock, however, and CAN block.
  */
-void fsnotify_unmount_inodes(struct list_head *list)
+void fsnotify_unmount_inodes(struct super_block *sb)
 {
 	struct inode *inode, *next_i, *need_iput = NULL;
 
-	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+	do_inode_list_for_each_entry_safe(sb, inode, next_i) {
 		struct inode *need_iput_tmp;
 
 		spin_lock(&inode->i_lock);
@@ -421,5 +421,5 @@ void fsnotify_unmount_inodes(struct list
 		fsnotify_inode_delete(inode);
 
 		iput(inode);
-	}
+	} while_inode_list_for_each_entry_safe
 }
fs/notify/inotify/inotify.c +4 -4
Index: linux-2.6/fs/notify/inotify/inotify.c
===================================================================
@@ -381,11 +381,11 @@ EXPORT_SYMBOL_GPL(inotify_get_cookie);
  * of inodes, and with iprune_mutex held, keeping shrink_icache_memory() at bay.
  * We temporarily drop inode_lock, however, and CAN block.
  */
-void inotify_unmount_inodes(struct list_head *list)
+void inotify_unmount_inodes(struct super_block *sb)
 {
 	struct inode *inode, *next_i, *need_iput = NULL;
 
-	list_for_each_entry_safe(inode, next_i, list, i_sb_list) {
+	do_inode_list_for_each_entry_safe(sb, inode, next_i) {
 		struct inotify_watch *watch, *next_w;
 		struct inode *need_iput_tmp;
 		struct list_head *watches;
@@ -450,8 +450,8 @@ void inotify_unmount_inodes(struct list_
 			put_inotify_watch(watch);
 		}
 		mutex_unlock(&inode->inotify_mutex);
-		iput(inode);		
-	}
+		iput(inode);
+	} while_inode_list_for_each_entry_safe
 }
 EXPORT_SYMBOL_GPL(inotify_unmount_inodes);
 
fs/quota/dquot.c +4 -8
Index: linux-2.6/fs/quota/dquot.c
===================================================================
@@ -884,16 +884,12 @@ static void add_dquot_ref(struct super_b
 #endif
 
 	rcu_read_lock();
-	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
+	do_inode_list_for_each_entry_rcu(sb, inode) {
 		spin_lock(&inode->i_lock);
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE|I_NEW)) {
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
-#ifdef CONFIG_QUOTA_DEBUG
-		if (unlikely(inode_get_rsv_space(inode) > 0))
-			reserved = 1;
-#endif
 		if (!atomic_read(&inode->i_writecount)) {
 			spin_unlock(&inode->i_lock);
 			continue;
@@ -916,7 +912,7 @@ static void add_dquot_ref(struct super_b
 		 * keep the reference and iput it later. */
 		old_inode = inode;
 		rcu_read_lock();
-	}
+	} while_inode_list_for_each_entry_rcu
 	rcu_read_unlock();
 	iput(old_inode);
 
@@ -996,7 +992,7 @@ static void remove_dquot_ref(struct supe
 	struct inode *inode;
 
 	rcu_read_lock();
-	list_for_each_entry_rcu(inode, &sb->s_inodes, i_sb_list) {
+	do_inode_list_for_each_entry_rcu(sb, inode) {
 		/*
 		 *  We have to scan also I_NEW inodes because they can already
 		 *  have quota pointer initialized. Luckily, we need to touch
@@ -1005,7 +1001,7 @@ static void remove_dquot_ref(struct supe
 		 */
 		if (!IS_NOQUOTA(inode))
 			remove_inode_dquot_ref(inode, type, tofree_head);
-	}
+	} while_inode_list_for_each_entry_rcu
 	rcu_read_unlock();
 }
 
include/linux/fsnotify_backend.h +2 -2
Index: linux-2.6/include/linux/fsnotify_backend.h
===================================================================
@@ -344,7 +344,7 @@ extern void fsnotify_destroy_mark_by_ent
 extern void fsnotify_clear_marks_by_group(struct fsnotify_group *group);
 extern void fsnotify_get_mark(struct fsnotify_mark_entry *entry);
 extern void fsnotify_put_mark(struct fsnotify_mark_entry *entry);
-extern void fsnotify_unmount_inodes(struct list_head *list);
+extern void fsnotify_unmount_inodes(struct super_block *sb);
 
 /* put here because inotify does some weird stuff when destroying watches */
 extern struct fsnotify_event *fsnotify_create_event(struct inode *to_tell, __u32 mask,
@@ -374,7 +374,7 @@ static inline u32 fsnotify_get_cookie(vo
 	return 0;
 }
 
-static inline void fsnotify_unmount_inodes(struct list_head *list)
+static inline void fsnotify_unmount_inodes(struct super_block *sb)
 {}
 
 #endif	/* CONFIG_FSNOTIFY */
include/linux/inotify.h +2 -2
Index: linux-2.6/include/linux/inotify.h
===================================================================
@@ -111,7 +111,7 @@ extern void inotify_inode_queue_event(st
 				      const char *, struct inode *);
 extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32,
 					      const char *);
-extern void inotify_unmount_inodes(struct list_head *);
+extern void inotify_unmount_inodes(struct super_block *);
 extern void inotify_inode_is_dead(struct inode *);
 extern u32 inotify_get_cookie(void);
 
@@ -161,7 +161,7 @@ static inline void inotify_dentry_parent
 {
 }
 
-static inline void inotify_unmount_inodes(struct list_head *list)
+static inline void inotify_unmount_inodes(struct super_block *sb)
 {
 }
 
include/linux/writeback.h +0 -1
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -9,7 +9,6 @@
 
 struct backing_dev_info;
 
-extern spinlock_t sb_inode_list_lock;
 extern spinlock_t wb_inode_list_lock;
 extern struct list_head inode_unused;
 
#113 lore raw

Re: [patch 44/52] fs: icache per-CPU sb inode lists and locks

Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:56PM +1000, [email protected] wrote:

.....

Quoted 43 lines
include/linux/fs.h +39 -0
@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(str
 
 extern void file_sb_list_add(struct file *f, struct super_block *sb);
 extern void file_sb_list_del(struct file *f);
+#ifdef CONFIG_SMP
+
+/*
+ * These macros iterate all inodes on all CPUs for a given superblock.
+ * rcu_read_lock must be held.
+ */
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\
+	}							\
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_safe((__inode), (__tmp), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_safe			\
+	}							\
+}
+
+#else
+
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\

include/linux/fs.h static inline void insert_inode_hash(str · line 2249

Quoted 10 lines
include/linux/fs.h +10 -0
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), (__tmp), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_safe			\
+}

I can't say that I'm a great fan of hiding loop context in defines like this. It reminds far too much of how parts of Irix slowly ossified because they ended up mess of complex, fragile macros that nobody fully understood...

Cheers.

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#114 lore raw

Re: [patch 44/52] fs: icache per-CPU sb inode lists and locks

Quoted 53 lines · Dave Chinner
Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:56PM +1000, [email protected] wrote:

.....

Quoted 50 lines
include/linux/fs.h +46 -0
@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(str
 
 extern void file_sb_list_add(struct file *f, struct super_block *sb);
 extern void file_sb_list_del(struct file *f);
+#ifdef CONFIG_SMP
+
+/*
+ * These macros iterate all inodes on all CPUs for a given superblock.
+ * rcu_read_lock must be held.
+ */
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\
+	}							\
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_safe((__inode), (__tmp), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_safe			\
+	}							\
+}
+
+#else
+
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), (__tmp), list, i_sb_list)
Quoted 8 lines

include/linux/fs.h static inline void insert_inode_hash(str · line 2229

Quoted 3 lines
include/linux/fs.h +3 -0
+
+#define while_inode_list_for_each_entry_safe			\
+}

I can't say that I'm a great fan of hiding loop context in defines like this. It reminds far too much of how parts of Irix slowly ossified because they ended up mess of complex, fragile macros that nobody fully understood...

It's not perfect. I think it is a lot better than open coding (which I tried before) because that really muddies up the intention of the loop body.

#115 lore raw

Re: [patch 44/52] fs: icache per-CPU sb inode lists and locks

Quoted 63 lines · Nick Piggin
Quoted 53 lines · Dave Chinner
Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:56PM +1000, [email protected] wrote:

.....

Quoted 50 lines
include/linux/fs.h +46 -0
@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(str
 
 extern void file_sb_list_add(struct file *f, struct super_block *sb);
 extern void file_sb_list_del(struct file *f);
+#ifdef CONFIG_SMP
+
+/*
+ * These macros iterate all inodes on all CPUs for a given superblock.
+ * rcu_read_lock must be held.
+ */
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\
+	}							\
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	int i;							\
+	for_each_possible_cpu(i) {				\
+		struct list_head *list;				\
+		list = per_cpu_ptr((__sb)->s_inodes, i);	\
+		list_for_each_entry_safe((__inode), (__tmp), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_safe			\
+	}							\
+}
+
+#else
+
+#define do_inode_list_for_each_entry_rcu(__sb, __inode)		\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), list, i_sb_list)
+
+#define while_inode_list_for_each_entry_rcu			\
+}
+
+#define do_inode_list_for_each_entry_safe(__sb, __inode, __tmp)	\
+{								\
+	struct list_head *list;					\
+	list = &(sb)->s_inodes;					\
+	list_for_each_entry_rcu((__inode), (__tmp), list, i_sb_list)
Quoted 8 lines

include/linux/fs.h static inline void insert_inode_hash(str · line 2229

Quoted 3 lines
include/linux/fs.h +3 -0
+
+#define while_inode_list_for_each_entry_safe			\
+}

I can't say that I'm a great fan of hiding loop context in defines like this. It reminds far too much of how parts of Irix slowly ossified because they ended up mess of complex, fragile macros that nobody fully understood...

Quoted 3 lines

It's not perfect. I think it is a lot better than open coding (which I tried before) because that really muddies up the intention of the loop body.

Something like this doesn't seem particularly bad:

static inline struct list_head *
inode_get_sb_list(struct super_block *sb, int *i)

{
	int cpu;

	cpu = cpumask_next(i, cpu_possible_mask);
	if (cpu >= nr_cpu_ids)
		return NULL;
	*i = cpu;
#ifdef CONFIG_SMP
	return per_cpu_ptr(sb->s_inodes, cpu);
#else
	return &sb->s_inodes;

#endif
}

and:

	struct list_head *list;
	int i;
....
	i = -1;
	while ((list = inode_get_sb_list(sb, &i))) {
		list_for_each_entry_rcu(inode, tmp, list, i_sb_list) {
			.....
		}
	}

I'd much prefer this to hiding the outer loop in macros...

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#116 lore raw

Re: [patch 44/52] fs: icache per-CPU sb inode lists and locks

Quoted 40 lines · Dave Chinner
Quoted 8 lines · Nick Piggin
Quoted 4 lines

I can't say that I'm a great fan of hiding loop context in defines like this. It reminds far too much of how parts of Irix slowly ossified because they ended up mess of complex, fragile macros that nobody fully understood...

It's not perfect. I think it is a lot better than open coding (which I tried before) because that really muddies up the intention of the loop body.

Something like this doesn't seem particularly bad:

static inline struct list_head *
inode_get_sb_list(struct super_block *sb, int *i)

{
	int cpu;

	cpu = cpumask_next(i, cpu_possible_mask);
	if (cpu >= nr_cpu_ids)
		return NULL;
	*i = cpu;
#ifdef CONFIG_SMP
	return per_cpu_ptr(sb->s_inodes, cpu);
#else
	return &sb->s_inodes;

#endif
}

and:

	struct list_head *list;
	int i;
....
	i = -1;
	while ((list = inode_get_sb_list(sb, &i))) {
		list_for_each_entry_rcu(inode, tmp, list, i_sb_list) {
			.....
		}
	}
Quoted 1 line

I'd much prefer this to hiding the outer loop in macros...

I don't see it as an improvement. A macro called inode_sb_list_for_each_entry is rather obvious and idiomatic as to what it does. You don't need to look at the implementation if you just need to walk the inode sb list.

#117 lore raw

PATCH 45/52 fs: icache RCU hash lookups

In reply to [email protected]
---
 fs/inode.c |   38 ++++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 18 deletions(-)
fs/inode.c +16 -16
Index: linux-2.6/fs/inode.c
===================================================================
@@ -603,27 +603,27 @@ static struct inode *find_inode(struct s
 	struct inode *inode = NULL;
 
 repeat:
-	spin_lock_bucket(b);
-	hlist_bl_for_each_entry(inode, node, &b->head, i_hash) {
+	rcu_read_lock();
+	hlist_bl_for_each_entry_rcu(inode, node, &b->head, i_hash) {
 		if (inode->i_sb != sb)
 			continue;
-		if (!spin_trylock(&inode->i_lock)) {
-			spin_unlock_bucket(b);
-			cpu_relax();
-			goto repeat;
+		spin_lock(&inode->i_lock);
+		if (hlist_bl_unhashed(&inode->i_hash)) {
+			spin_unlock(&inode->i_lock);
+			continue;
 		}
 		if (!test(inode, data)) {
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)) {
-			spin_unlock_bucket(b);
+			rcu_read_unlock();
 			__wait_on_freeing_inode(inode);
 			goto repeat;
 		}
 		break;
 	}
-	spin_unlock_bucket(b);
+	rcu_read_unlock();
 	return node ? inode : NULL;
 }
 
@@ -639,25 +639,25 @@ static struct inode *find_inode_fast(str
 	struct inode *inode = NULL;
 
 repeat:
-	spin_lock_bucket(b);
-	hlist_bl_for_each_entry(inode, node, &b->head, i_hash) {
+	rcu_read_lock();
+	hlist_bl_for_each_entry_rcu(inode, node, &b->head, i_hash) {
 		if (inode->i_ino != ino)
 			continue;
 		if (inode->i_sb != sb)
 			continue;
-		if (!spin_trylock(&inode->i_lock)) {
-			spin_unlock_bucket(b);
-			cpu_relax();
-			goto repeat;
+		spin_lock(&inode->i_lock);
+		if (hlist_bl_unhashed(&inode->i_hash)) {
+			spin_unlock(&inode->i_lock);
+			continue;
 		}
 		if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)) {
-			spin_unlock_bucket(b);
+			rcu_read_unlock();
 			__wait_on_freeing_inode(inode);
 			goto repeat;
 		}
 		break;
 	}
-	spin_unlock_bucket(b);
+	rcu_read_unlock();
 	return node ? inode : NULL;
 }
 
#118 lore raw

PATCH 46/52 fs: icache reduce locking

In reply to [email protected]
fs/fs-writeback.c +5 -3
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -484,15 +484,17 @@ writeback_single_inode(struct inode *ino
 	spin_lock(&inode->i_lock);
 	dirty = inode->i_state & I_DIRTY;
 	inode->i_state &= ~(I_DIRTY_SYNC | I_DIRTY_DATASYNC);
-	spin_unlock(&inode->i_lock);
 	/* Don't write the inode if only I_DIRTY_PAGES was set */
 	if (dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) {
-		int err = write_inode(inode, wbc);
+		int err;
+
+		spin_unlock(&inode->i_lock);
+		err = write_inode(inode, wbc);
 		if (ret == 0)
 			ret = err;
+		spin_lock(&inode->i_lock);
 	}
 
-	spin_lock(&inode->i_lock);
 	spin_lock(&wb_inode_list_lock);
 	inode->i_state &= ~I_SYNC;
 	if (!(inode->i_state & (I_FREEING | I_CLEAR))) {
fs/inode.c +0 -3
Index: linux-2.6/fs/inode.c
===================================================================
@@ -810,12 +810,9 @@ struct inode *new_inode(struct super_blo
 
 	inode = alloc_inode(sb);
 	if (inode) {
-		/* XXX: init as locked for speedup */
-		spin_lock(&inode->i_lock);
 		inode->i_ino = last_ino_get();
 		inode->i_state = 0;
 		__inode_add_to_lists(sb, NULL, inode);
-		spin_unlock(&inode->i_lock);
 	}
 	return inode;
 }
#119 lore raw

PATCH 47/52 fs: keep inode with backing-dev

In reply to [email protected]

Having inode on writeback lists of a different bdi than inode->i_mapping->backing_dev_info makes it very difficult to do per-bdi locking of the writeback lists. Add functions to move these inodes over when the mapping backing dev is changed.

i_mapping.backing_dev_info is renamed to i_mapping.a_bdi while we're here. Succinct is nice, and it catches conversion errors.

--

include/linux/backing-dev.h +10 -4
Index: linux-2.6/include/linux/backing-dev.h
===================================================================
@@ -316,19 +316,25 @@ static inline bool bdi_cap_flush_forker(
 	return bdi == &default_backing_dev_info;
 }
 
+void mapping_set_bdi(struct address_space *mapping, struct backing_dev_info *bdi);
+static inline void mapping_new_set_bdi(struct address_space *mapping, struct backing_dev_info *bdi)
+{
+	mapping->a_bdi = bdi;
+}
+
 static inline bool mapping_cap_writeback_dirty(struct address_space *mapping)
 {
-	return bdi_cap_writeback_dirty(mapping->backing_dev_info);
+	return bdi_cap_writeback_dirty(mapping->a_bdi);
 }
 
 static inline bool mapping_cap_account_dirty(struct address_space *mapping)
 {
-	return bdi_cap_account_dirty(mapping->backing_dev_info);
+	return bdi_cap_account_dirty(mapping->a_bdi);
 }
 
 static inline bool mapping_cap_swap_backed(struct address_space *mapping)
 {
-	return bdi_cap_swap_backed(mapping->backing_dev_info);
+	return bdi_cap_swap_backed(mapping->a_bdi);
 }
 
 static inline int bdi_sched_wait(void *word)
@@ -347,7 +353,7 @@ static inline void blk_run_backing_dev(s
 static inline void blk_run_address_space(struct address_space *mapping)
 {
 	if (mapping)
-		blk_run_backing_dev(mapping->backing_dev_info, NULL);
+		blk_run_backing_dev(mapping->a_bdi, NULL);
 }
 
 #endif		/* _LINUX_BACKING_DEV_H */
include/linux/fs.h +1 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -635,7 +635,7 @@ struct address_space {
 	pgoff_t			writeback_index;/* writeback starts here */
 	const struct address_space_operations *a_ops;	/* methods */
 	unsigned long		flags;		/* error bits/gfp mask */
-	struct backing_dev_info *backing_dev_info; /* device readahead, etc */
+	struct backing_dev_info *a_bdi;		/* device readahead, etc */
 	spinlock_t		private_lock;	/* for use by the address_space */
 	struct list_head	private_list;	/* ditto */
 	struct address_space	*assoc_mapping;	/* ditto */
mm/backing-dev.c +57 -3

74 lines not on this page · raw message

drivers/char/mem.c +1 -1
Index: linux-2.6/drivers/char/mem.c
===================================================================
@@ -871,7 +871,7 @@ static int memory_open(struct inode *ino
 
 	filp->f_op = dev->fops;
 	if (dev->dev_info)
-		filp->f_mapping->backing_dev_info = dev->dev_info;
+		mapping_set_bdi(filp->f_mapping, dev->dev_info);
 
 	if (dev->fops->open)
 		return dev->fops->open(inode, filp);
drivers/char/raw.c +1 -1
Index: linux-2.6/drivers/char/raw.c
===================================================================
@@ -109,7 +109,7 @@ static int raw_release(struct inode *ino
 	if (--raw_devices[minor].inuse == 0) {
 		/* Here  inode->i_mapping == bdev->bd_inode->i_mapping  */
 		inode->i_mapping = &inode->i_data;
-		inode->i_mapping->backing_dev_info = &default_backing_dev_info;
+		mapping_set_bdi(inode->i_mapping, &default_backing_dev_info);
 	}
 	mutex_unlock(&raw_mutex);
 
fs/block_dev.c +7 -6
Index: linux-2.6/fs/block_dev.c
===================================================================
@@ -539,7 +539,7 @@ struct block_device *bdget(dev_t dev)
 		inode->i_bdev = bdev;
 		inode->i_data.a_ops = &def_blk_aops;
 		mapping_set_gfp_mask(&inode->i_data, GFP_USER);
-		inode->i_data.backing_dev_info = &default_backing_dev_info;
+		mapping_new_set_bdi(&inode->i_data, &default_backing_dev_info);
 		spin_lock(&bdev_lock);
 		list_add(&bdev->bd_list, &all_bdevs);
 		spin_unlock(&bdev_lock);
@@ -1404,7 +1404,7 @@ static int __blkdev_get(struct block_dev
 				bdi = blk_get_backing_dev_info(bdev);
 				if (bdi == NULL)
 					bdi = &default_backing_dev_info;
-				bdev->bd_inode->i_data.backing_dev_info = bdi;
+				mapping_set_bdi(&bdev->bd_inode->i_data, bdi);
 			}
 			if (bdev->bd_invalidated)
 				rescan_partitions(disk, bdev);
@@ -1419,8 +1419,8 @@ static int __blkdev_get(struct block_dev
 			if (ret)
 				goto out_clear;
 			bdev->bd_contains = whole;
-			bdev->bd_inode->i_data.backing_dev_info =
-			   whole->bd_inode->i_data.backing_dev_info;
+			mapping_set_bdi(&bdev->bd_inode->i_data,
+			   whole->bd_inode->i_data.a_bdi);
 			bdev->bd_part = disk_get_part(disk, partno);
 			if (!(disk->flags & GENHD_FL_UP) ||
 			    !bdev->bd_part || !bdev->bd_part->nr_sects) {
@@ -1454,7 +1454,7 @@ static int __blkdev_get(struct block_dev
 	disk_put_part(bdev->bd_part);
 	bdev->bd_disk = NULL;
 	bdev->bd_part = NULL;
-	bdev->bd_inode->i_data.backing_dev_info = &default_backing_dev_info;
+	mapping_set_bdi(&bdev->bd_inode->i_data, &default_backing_dev_info);
 	if (bdev != bdev->bd_contains)
 		__blkdev_put(bdev->bd_contains, mode, 1);
 	bdev->bd_contains = NULL;
@@ -1551,7 +1551,8 @@ static int __blkdev_put(struct block_dev
 		disk_put_part(bdev->bd_part);
 		bdev->bd_part = NULL;
 		bdev->bd_disk = NULL;
-		bdev->bd_inode->i_data.backing_dev_info = &default_backing_dev_info;
+		mapping_set_bdi(&bdev->bd_inode->i_data,
+				&default_backing_dev_info);
 		if (bdev != bdev->bd_contains)
 			victim = bdev->bd_contains;
 		bdev->bd_contains = NULL;
fs/btrfs/disk-io.c +1 -1
Index: linux-2.6/fs/btrfs/disk-io.c
===================================================================
@@ -1636,7 +1636,7 @@ struct btrfs_root *open_ctree(struct sup
 	 */
 	fs_info->btree_inode->i_size = OFFSET_MAX;
 	fs_info->btree_inode->i_mapping->a_ops = &btree_aops;
-	fs_info->btree_inode->i_mapping->backing_dev_info = &fs_info->bdi;
+	mapping_new_set_bdi(fs_info->btree_inode->i_mapping, &fs_info->bdi);
 
 	RB_CLEAR_NODE(&BTRFS_I(fs_info->btree_inode)->rb_node);
 	extent_io_tree_init(&BTRFS_I(fs_info->btree_inode)->io_tree,
fs/btrfs/inode.c +5 -5
Index: linux-2.6/fs/btrfs/inode.c
===================================================================
@@ -2480,7 +2480,7 @@ static void btrfs_read_locked_inode(stru
 	switch (inode->i_mode & S_IFMT) {
 	case S_IFREG:
 		inode->i_mapping->a_ops = &btrfs_aops;
-		inode->i_mapping->backing_dev_info = &root->fs_info->bdi;
+		mapping_new_set_bdi(inode->i_mapping, &root->fs_info->bdi);
 		BTRFS_I(inode)->io_tree.ops = &btrfs_extent_io_ops;
 		inode->i_fop = &btrfs_file_operations;
 		inode->i_op = &btrfs_file_inode_operations;
@@ -2495,7 +2495,7 @@ static void btrfs_read_locked_inode(stru
 	case S_IFLNK:
 		inode->i_op = &btrfs_symlink_inode_operations;
 		inode->i_mapping->a_ops = &btrfs_symlink_aops;
-		inode->i_mapping->backing_dev_info = &root->fs_info->bdi;
+		mapping_new_set_bdi(inode->i_mapping, &root->fs_info->bdi);
 		break;
 	default:
 		inode->i_op = &btrfs_special_inode_operations;
@@ -4705,7 +4705,7 @@ static int btrfs_create(struct inode *di
 		drop_inode = 1;
 	else {
 		inode->i_mapping->a_ops = &btrfs_aops;
-		inode->i_mapping->backing_dev_info = &root->fs_info->bdi;
+		mapping_new_set_bdi(inode->i_mapping, &root->fs_info->bdi);
 		inode->i_fop = &btrfs_file_operations;
 		inode->i_op = &btrfs_file_inode_operations;
 		BTRFS_I(inode)->io_tree.ops = &btrfs_extent_io_ops;
@@ -6700,7 +6700,7 @@ static int btrfs_symlink(struct inode *d
 		drop_inode = 1;
 	else {
 		inode->i_mapping->a_ops = &btrfs_aops;
-		inode->i_mapping->backing_dev_info = &root->fs_info->bdi;
+		mapping_new_set_bdi(inode->i_mapping, &root->fs_info->bdi);
 		inode->i_fop = &btrfs_file_operations;
 		inode->i_op = &btrfs_file_inode_operations;
 		BTRFS_I(inode)->io_tree.ops = &btrfs_extent_io_ops;
@@ -6740,7 +6740,7 @@ static int btrfs_symlink(struct inode *d
 
 	inode->i_op = &btrfs_symlink_inode_operations;
 	inode->i_mapping->a_ops = &btrfs_symlink_aops;
-	inode->i_mapping->backing_dev_info = &root->fs_info->bdi;
+	mapping_new_set_bdi(inode->i_mapping, &root->fs_info->bdi);
 	inode_set_bytes(inode, name_len);
 	btrfs_i_size_write(inode, name_len - 1);
 	err = btrfs_update_inode(trans, root, inode);
fs/ceph/inode.c +2 -2
Index: linux-2.6/fs/ceph/inode.c
===================================================================
@@ -623,8 +623,8 @@ static int fill_inode(struct inode *inod
 	}
 
 	inode->i_mapping->a_ops = &ceph_aops;
-	inode->i_mapping->backing_dev_info =
-		&ceph_sb_to_client(inode->i_sb)->backing_dev_info;
+	mapping_new_set_bdi(inode->i_mapping,
+			&ceph_sb_to_client(inode->i_sb)->backing_dev_info);
 
 	switch (inode->i_mode & S_IFMT) {
 	case S_IFIFO:
fs/configfs/inode.c +2 -1
Index: linux-2.6/fs/configfs/inode.c
===================================================================
@@ -136,7 +136,8 @@ struct inode * configfs_new_inode(mode_t
 	struct inode * inode = new_inode(configfs_sb);
 	if (inode) {
 		inode->i_mapping->a_ops = &configfs_aops;
-		inode->i_mapping->backing_dev_info = &configfs_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping,
+				&configfs_backing_dev_info);
 		inode->i_op = &configfs_inode_operations;
 
 		if (sd->s_iattr) {
fs/gfs2/glock.c +2 -1
Index: linux-2.6/fs/gfs2/glock.c
===================================================================
@@ -8,6 +8,7 @@
  */
 
 #include <linux/sched.h>
+#include <linux/backing-dev.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/buffer_head.h>
@@ -796,7 +797,7 @@ int gfs2_glock_get(struct gfs2_sbd *sdp,
 		mapping->flags = 0;
 		mapping_set_gfp_mask(mapping, GFP_NOFS);
 		mapping->assoc_mapping = NULL;
-		mapping->backing_dev_info = s->s_bdi;
+		mapping_new_set_bdi(mapping, s->s_bdi);
 		mapping->writeback_index = 0;
 	}
 
fs/hugetlbfs/inode.c +2 -1
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
@@ -476,7 +476,8 @@ static struct inode *hugetlbfs_get_inode
 		inode->i_uid = uid;
 		inode->i_gid = gid;
 		inode->i_mapping->a_ops = &hugetlbfs_aops;
-		inode->i_mapping->backing_dev_info =&hugetlbfs_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping,
+				&hugetlbfs_backing_dev_info);
 		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
 		INIT_LIST_HEAD(&inode->i_mapping->private_list);
 		info = HUGETLBFS_I(inode);
fs/inode.c +3 -3
Index: linux-2.6/fs/inode.c
===================================================================
@@ -237,7 +237,7 @@ int inode_init_always(struct super_block
 	mapping->flags = 0;
 	mapping_set_gfp_mask(mapping, GFP_HIGHUSER_MOVABLE);
 	mapping->assoc_mapping = NULL;
-	mapping->backing_dev_info = &default_backing_dev_info;
+	mapping_new_set_bdi(mapping, &default_backing_dev_info);
 	mapping->writeback_index = 0;
 
 	/*
@@ -248,8 +248,8 @@ int inode_init_always(struct super_block
 	if (sb->s_bdev) {
 		struct backing_dev_info *bdi;
 
-		bdi = sb->s_bdev->bd_inode->i_mapping->backing_dev_info;
-		mapping->backing_dev_info = bdi;
+		bdi = sb->s_bdev->bd_inode->i_mapping->a_bdi;
+		mapping_new_set_bdi(mapping, bdi);
 	}
 	inode->i_private = NULL;
 	inode->i_mapping = mapping;
fs/logfs/inode.c +1 -1
Index: linux-2.6/fs/logfs/inode.c
===================================================================
@@ -258,7 +258,7 @@ struct inode *logfs_new_meta_inode(struc
 		mapping->flags = 0;
 		mapping_set_gfp_mask(mapping, GFP_NOFS);
 		mapping->assoc_mapping = NULL;
-		mapping->backing_dev_info = &default_backing_dev_info;
+		mapping_new_set_bdi(mapping, &default_backing_dev_info);
 		inode->i_mapping = mapping;
 		inode->i_nlink = 1;
 	}
fs/nilfs2/btnode.c +1 -1
Index: linux-2.6/fs/nilfs2/btnode.c
===================================================================
@@ -59,7 +59,7 @@ void nilfs_btnode_cache_init(struct addr
 	btnc->flags = 0;
 	mapping_set_gfp_mask(btnc, GFP_NOFS);
 	btnc->assoc_mapping = NULL;
-	btnc->backing_dev_info = bdi;
+	mapping_new_set_bdi(btnc, bdi);
 	btnc->a_ops = &def_btnode_aops;
 }
 
fs/nilfs2/mdt.c +1 -1
Index: linux-2.6/fs/nilfs2/mdt.c
===================================================================
@@ -516,7 +516,7 @@ nilfs_mdt_new_common(struct the_nilfs *n
 		mapping->flags = 0;
 		mapping_set_gfp_mask(mapping, gfp_mask);
 		mapping->assoc_mapping = NULL;
-		mapping->backing_dev_info = nilfs->ns_bdi;
+		mapping_new_set_bdi(mapping, nilfs->ns_bdi);
 
 		inode->i_mapping = mapping;
 	}
fs/ocfs2/dlmfs/dlmfs.c +2 -2
Index: linux-2.6/fs/ocfs2/dlmfs/dlmfs.c
===================================================================
@@ -403,7 +403,7 @@ static struct inode *dlmfs_get_root_inod
 		inode->i_mode = mode;
 		inode->i_uid = current_fsuid();
 		inode->i_gid = current_fsgid();
-		inode->i_mapping->backing_dev_info = &dlmfs_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping, &dlmfs_backing_dev_info);
 		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
 		inc_nlink(inode);
 
@@ -428,7 +428,7 @@ static struct inode *dlmfs_get_inode(str
 	inode->i_mode = mode;
 	inode->i_uid = current_fsuid();
 	inode->i_gid = current_fsgid();
-	inode->i_mapping->backing_dev_info = &dlmfs_backing_dev_info;
+	mapping_new_set_bdi(inode->i_mapping, &dlmfs_backing_dev_info);
 	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
 
 	ip = DLMFS_I(inode);
fs/ramfs/inode.c +1 -1
Index: linux-2.6/fs/ramfs/inode.c
===================================================================
@@ -60,7 +60,7 @@ struct inode *ramfs_get_inode(struct sup
 	if (inode) {
 		inode_init_owner(inode, dir, mode);
 		inode->i_mapping->a_ops = &ramfs_aops;
-		inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping, &ramfs_backing_dev_info);
 		mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
 		mapping_set_unevictable(inode->i_mapping);
 		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
fs/sysfs/inode.c +1 -1
Index: linux-2.6/fs/sysfs/inode.c
===================================================================
@@ -251,7 +251,7 @@ static void sysfs_init_inode(struct sysf
 
 	inode->i_private = sysfs_get(sd);
 	inode->i_mapping->a_ops = &sysfs_aops;
-	inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
+	mapping_new_set_bdi(inode->i_mapping, &sysfs_backing_dev_info);
 	inode->i_op = &sysfs_inode_operations;
 
 	set_default_inode_attr(inode, sd->s_mode);
fs/ubifs/dir.c +1 -1
Index: linux-2.6/fs/ubifs/dir.c
===================================================================
@@ -109,7 +109,7 @@ struct inode *ubifs_new_inode(struct ubi
 			 ubifs_current_time(inode);
 	inode->i_mapping->nrpages = 0;
 	/* Disable readahead */
-	inode->i_mapping->backing_dev_info = &c->bdi;
+	mapping_new_set_bdi(inode->i_mapping, &c->bdi);
 
 	switch (mode & S_IFMT) {
 	case S_IFREG:
fs/ubifs/super.c +1 -1
Index: linux-2.6/fs/ubifs/super.c
===================================================================
@@ -157,7 +157,7 @@ struct inode *ubifs_iget(struct super_bl
 		goto out_invalid;
 
 	/* Disable read-ahead */
-	inode->i_mapping->backing_dev_info = &c->bdi;
+	mapping_new_set_bdi(inode->i_mapping, &c->bdi);
 
 	switch (inode->i_mode & S_IFMT) {
 	case S_IFREG:
fs/xfs/linux-2.6/xfs_buf.c +2 -2
Index: linux-2.6/fs/xfs/linux-2.6/xfs_buf.c
===================================================================
@@ -636,7 +636,7 @@ xfs_buf_readahead(
 {
 	struct backing_dev_info *bdi;
 
-	bdi = target->bt_mapping->backing_dev_info;
+	bdi = target->bt_mapping->a_bdi;
 	if (bdi_read_congested(bdi))
 		return;
 
@@ -1610,7 +1610,7 @@ xfs_mapping_buftarg(
 		bdi = &default_backing_dev_info;
 	mapping = &inode->i_data;
 	mapping->a_ops = &mapping_aops;
-	mapping->backing_dev_info = bdi;
+	mapping_new_set_bdi(mapping, bdi);
 	mapping_set_gfp_mask(mapping, GFP_NOFS);
 	btp->bt_mapping = mapping;
 	return 0;
kernel/cgroup.c +1 -1
Index: linux-2.6/kernel/cgroup.c
===================================================================
@@ -782,7 +782,7 @@ static struct inode *cgroup_new_inode(mo
 		inode->i_uid = current_fsuid();
 		inode->i_gid = current_fsgid();
 		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
-		inode->i_mapping->backing_dev_info = &cgroup_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping, &cgroup_backing_dev_info);
 	}
 	return inode;
 }
mm/shmem.c +1 -1
Index: linux-2.6/mm/shmem.c
===================================================================
@@ -1561,7 +1561,7 @@ static struct inode *shmem_get_inode(str
 	if (inode) {
 		inode_init_owner(inode, dir, mode);
 		inode->i_blocks = 0;
-		inode->i_mapping->backing_dev_info = &shmem_backing_dev_info;
+		mapping_new_set_bdi(inode->i_mapping, &shmem_backing_dev_info);
 		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
 		inode->i_generation = get_seconds();
 		info = SHMEM_I(inode);
fs/afs/write.c +4 -4
Index: linux-2.6/fs/afs/write.c
===================================================================
@@ -438,7 +438,7 @@ no_more:
  */
 int afs_writepage(struct page *page, struct writeback_control *wbc)
 {
-	struct backing_dev_info *bdi = page->mapping->backing_dev_info;
+	struct backing_dev_info *bdi = page->mapping->a_bdi;
 	struct afs_writeback *wb;
 	int ret;
 
@@ -469,7 +469,7 @@ static int afs_writepages_region(struct
 				 struct writeback_control *wbc,
 				 pgoff_t index, pgoff_t end, pgoff_t *_next)
 {
-	struct backing_dev_info *bdi = mapping->backing_dev_info;
+	struct backing_dev_info *bdi = mapping->a_bdi;
 	struct afs_writeback *wb;
 	struct page *page;
 	int ret, n;
@@ -548,7 +548,7 @@ static int afs_writepages_region(struct
 int afs_writepages(struct address_space *mapping,
 		   struct writeback_control *wbc)
 {
-	struct backing_dev_info *bdi = mapping->backing_dev_info;
+	struct backing_dev_info *bdi = mapping->a_bdi;
 	pgoff_t start, end, next;
 	int ret;
 
@@ -680,7 +680,7 @@ int afs_writeback_all(struct afs_vnode *
 {
 	struct address_space *mapping = vnode->vfs_inode.i_mapping;
 	struct writeback_control wbc = {
-		.bdi		= mapping->backing_dev_info,
+		.bdi		= mapping->a_bdi,
 		.sync_mode	= WB_SYNC_ALL,
 		.nr_to_write	= LONG_MAX,
 		.range_cyclic	= 1,
fs/btrfs/extent_io.c +1 -1
Index: linux-2.6/fs/btrfs/extent_io.c
===================================================================
@@ -2628,7 +2628,7 @@ int extent_write_locked_range(struct ext
 		.sync_io = mode == WB_SYNC_ALL,
 	};
 	struct writeback_control wbc_writepages = {
-		.bdi		= inode->i_mapping->backing_dev_info,
+		.bdi		= inode->i_mapping->a_bdi,
 		.sync_mode	= mode,
 		.older_than_this = NULL,
 		.nr_to_write	= nr_pages * 2,
fs/btrfs/file.c +1 -1
Index: linux-2.6/fs/btrfs/file.c
===================================================================
@@ -872,7 +872,7 @@ static ssize_t btrfs_file_aio_write(stru
 		goto out;
 	count = ocount;
 
-	current->backing_dev_info = inode->i_mapping->backing_dev_info;
+	current->backing_dev_info = inode->i_mapping->a_bdi;
 	err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
 	if (err)
 		goto out;
fs/buffer.c +1 -1
Index: linux-2.6/fs/buffer.c
===================================================================
@@ -3264,7 +3264,7 @@ void block_sync_page(struct page *page)
 	smp_mb();
 	mapping = page_mapping(page);
 	if (mapping)
-		blk_run_backing_dev(mapping->backing_dev_info, page);
+		blk_run_backing_dev(mapping->a_bdi, page);
 }
 EXPORT_SYMBOL(block_sync_page);
 
mm/fadvise.c +2 -2
Index: linux-2.6/mm/fadvise.c
===================================================================
@@ -72,7 +72,7 @@ SYSCALL_DEFINE(fadvise64_64)(int fd, lof
 	else
 		endbyte--;		/* inclusive */
 
-	bdi = mapping->backing_dev_info;
+	bdi = mapping->a_bdi;
 
 	switch (advice) {
 	case POSIX_FADV_NORMAL:
@@ -116,7 +116,7 @@ SYSCALL_DEFINE(fadvise64_64)(int fd, lof
 	case POSIX_FADV_NOREUSE:
 		break;
 	case POSIX_FADV_DONTNEED:
-		if (!bdi_write_congested(mapping->backing_dev_info))
+		if (!bdi_write_congested(mapping->a_bdi))
 			filemap_flush(mapping);
 
 		/* First and last FULL page! */
mm/filemap.c +2 -2
Index: linux-2.6/mm/filemap.c
===================================================================
@@ -136,7 +136,7 @@ void __remove_from_page_cache(struct pag
 	 */
 	if (PageDirty(page) && mapping_cap_account_dirty(mapping)) {
 		dec_zone_page_state(page, NR_FILE_DIRTY);
-		dec_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
+		dec_bdi_stat(mapping->a_bdi, BDI_RECLAIMABLE);
 	}
 }
 
@@ -2375,7 +2375,7 @@ ssize_t __generic_file_aio_write(struct
 	vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
 
 	/* We can write back this queue in page reclaim */
-	current->backing_dev_info = mapping->backing_dev_info;
+	current->backing_dev_info = mapping->a_bdi;
 	written = 0;
 
 	err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
mm/filemap_xip.c +1 -1
Index: linux-2.6/mm/filemap_xip.c
===================================================================
@@ -409,7 +409,7 @@ xip_file_write(struct file *filp, const
 	vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
 
 	/* We can write back this queue in page reclaim */
-	current->backing_dev_info = mapping->backing_dev_info;
+	current->backing_dev_info = mapping->a_bdi;
 
 	ret = generic_write_checks(filp, &pos, &count, S_ISBLK(inode->i_mode));
 	if (ret)
mm/page-writeback.c +6 -7
Index: linux-2.6/mm/page-writeback.c
===================================================================
@@ -491,7 +491,7 @@ static void balance_dirty_pages(struct a
 	unsigned long pages_written = 0;
 	unsigned long pause = 1;
 
-	struct backing_dev_info *bdi = mapping->backing_dev_info;
+	struct backing_dev_info *bdi = mapping->a_bdi;
 
 	for (;;) {
 		struct writeback_control wbc = {
@@ -633,7 +633,7 @@ void balance_dirty_pages_ratelimited_nr(
 	unsigned long *p;
 
 	ratelimit = ratelimit_pages;
-	if (mapping->backing_dev_info->dirty_exceeded)
+	if (mapping->a_bdi->dirty_exceeded)
 		ratelimit = 8;
 
 	/*
@@ -1093,7 +1093,7 @@ void account_page_dirtied(struct page *p
 {
 	if (mapping_cap_account_dirty(mapping)) {
 		__inc_zone_page_state(page, NR_FILE_DIRTY);
-		__inc_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
+		__inc_bdi_stat(mapping->a_bdi, BDI_RECLAIMABLE);
 		task_dirty_inc(current);
 		task_io_account_write(PAGE_CACHE_SIZE);
 	}
@@ -1268,8 +1268,7 @@ int clear_page_dirty_for_io(struct page
 		 */
 		if (TestClearPageDirty(page)) {
 			dec_zone_page_state(page, NR_FILE_DIRTY);
-			dec_bdi_stat(mapping->backing_dev_info,
-					BDI_RECLAIMABLE);
+			dec_bdi_stat(mapping->a_bdi, BDI_RECLAIMABLE);
 			return 1;
 		}
 		return 0;
@@ -1284,7 +1283,7 @@ int test_clear_page_writeback(struct pag
 	int ret;
 
 	if (mapping) {
-		struct backing_dev_info *bdi = mapping->backing_dev_info;
+		struct backing_dev_info *bdi = mapping->a_bdi;
 		unsigned long flags;
 
 		spin_lock_irqsave(&mapping->tree_lock, flags);
@@ -1313,7 +1312,7 @@ int test_set_page_writeback(struct page
 	int ret;
 
 	if (mapping) {
-		struct backing_dev_info *bdi = mapping->backing_dev_info;
+		struct backing_dev_info *bdi = mapping->a_bdi;
 		unsigned long flags;
 
 		spin_lock_irqsave(&mapping->tree_lock, flags);
mm/readahead.c +3 -3
Index: linux-2.6/mm/readahead.c
===================================================================
@@ -25,7 +25,7 @@
 void
 file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping)
 {
-	ra->ra_pages = mapping->backing_dev_info->ra_pages;
+	ra->ra_pages = mapping->a_bdi->ra_pages;
 	ra->prev_pos = -1;
 }
 EXPORT_SYMBOL_GPL(file_ra_state_init);
@@ -549,7 +549,7 @@ page_cache_async_readahead(struct addres
 	/*
 	 * Defer asynchronous read-ahead on IO congestion.
 	 */
-	if (bdi_read_congested(mapping->backing_dev_info))
+	if (bdi_read_congested(mapping->a_bdi))
 		return;
 
 	/* do read-ahead */
@@ -564,7 +564,7 @@ page_cache_async_readahead(struct addres
 	 * explicitly kick off the IO.
 	 */
 	if (PageUptodate(page))
-		blk_run_backing_dev(mapping->backing_dev_info, NULL);
+		blk_run_backing_dev(mapping->a_bdi, NULL);
 #endif
 }
 EXPORT_SYMBOL_GPL(page_cache_async_readahead);
mm/swap.c +1 -1
Index: linux-2.6/mm/swap.c
===================================================================
@@ -501,7 +501,7 @@ void __init swap_setup(void)
 	unsigned long megs = totalram_pages >> (20 - PAGE_SHIFT);
 
 #ifdef CONFIG_SWAP
-	bdi_init(swapper_space.backing_dev_info);
+	bdi_init(swapper_space.a_bdi);
 #endif
 
 	/* Use a smaller cluster for small-memory machines */
mm/swap_state.c +1 -1
Index: linux-2.6/mm/swap_state.c
===================================================================
@@ -45,7 +45,7 @@ struct address_space swapper_space = {
 	.tree_lock	= __SPIN_LOCK_UNLOCKED(swapper_space.tree_lock),
 	.a_ops		= &swap_aops,
 	.i_mmap_nonlinear = LIST_HEAD_INIT(swapper_space.i_mmap_nonlinear),
-	.backing_dev_info = &swap_backing_dev_info,
+	.a_bdi		= &swap_backing_dev_info,
 };
 
 #define INC_CACHE_INFO(x)	do { swap_cache_info.x++; } while (0)
mm/swapfile.c +1 -1
Index: linux-2.6/mm/swapfile.c
===================================================================
@@ -116,7 +116,7 @@ void swap_unplug_io_fn(struct backing_de
 		 */
 		WARN_ON(page_count(page) <= 1);
 
-		bdi = bdev->bd_inode->i_mapping->backing_dev_info;
+		bdi = bdev->bd_inode->i_mapping->a_bdi;
 		blk_run_backing_dev(bdi, page);
 	}
 	up_read(&swap_unplug_sem);
mm/truncate.c +1 -2
Index: linux-2.6/mm/truncate.c
===================================================================
@@ -75,8 +75,7 @@ void cancel_dirty_page(struct page *page
 		struct address_space *mapping = page->mapping;
 		if (mapping && mapping_cap_account_dirty(mapping)) {
 			dec_zone_page_state(page, NR_FILE_DIRTY);
-			dec_bdi_stat(mapping->backing_dev_info,
-					BDI_RECLAIMABLE);
+			dec_bdi_stat(mapping->a_bdi, BDI_RECLAIMABLE);
 			if (account_size)
 				task_io_account_cancelled_write(account_size);
 		}
mm/vmscan.c +1 -1
Index: linux-2.6/mm/vmscan.c
===================================================================
@@ -361,7 +361,7 @@ static pageout_t pageout(struct page *pa
 	}
 	if (mapping->a_ops->writepage == NULL)
 		return PAGE_ACTIVATE;
-	if (!may_write_to_queue(mapping->backing_dev_info))
+	if (!may_write_to_queue(mapping->a_bdi))
 		return PAGE_KEEP;
 
 	if (clear_page_dirty_for_io(page)) {
fs/ext2/ialloc.c +1 -1
Index: linux-2.6/fs/ext2/ialloc.c
===================================================================
@@ -177,7 +177,7 @@ static void ext2_preread_inode(struct in
 	struct ext2_group_desc * gdp;
 	struct backing_dev_info *bdi;
 
-	bdi = inode->i_mapping->backing_dev_info;
+	bdi = inode->i_mapping->a_bdi;
 	if (bdi_read_congested(bdi))
 		return;
 	if (bdi_write_congested(bdi))
fs/fs-writeback.c +1 -1
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -28,7 +28,7 @@
 #include <linux/buffer_head.h>
 #include "internal.h"
 
-#define inode_to_bdi(inode)	((inode)->i_mapping->backing_dev_info)
+#define inode_to_bdi(inode)	((inode)->i_mapping->a_bdi)
 
 /*
  * We don't actually have pdflush, but this one is exported though /proc...
drivers/mtd/mtdchar.c +1 -1
Index: linux-2.6/drivers/mtd/mtdchar.c
===================================================================
@@ -99,7 +99,7 @@ static int mtd_open(struct inode *inode,
 	if (mtd_ino->i_state & I_NEW) {
 		mtd_ino->i_private = mtd;
 		mtd_ino->i_mode = S_IFCHR;
-		mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info;
+		mapping_new_set_bdi(&mtd_ino->i_data, mtd->backing_dev_info);
 		unlock_new_inode(mtd_ino);
 	}
 	file->f_mapping = mtd_ino->i_mapping;
fs/ceph/addr.c +2 -3
Index: linux-2.6/fs/ceph/addr.c
===================================================================
@@ -108,8 +108,7 @@ static int ceph_set_page_dirty(struct pa
 
 		if (mapping_cap_account_dirty(mapping)) {
 			__inc_zone_page_state(page, NR_FILE_DIRTY);
-			__inc_bdi_stat(mapping->backing_dev_info,
-					BDI_RECLAIMABLE);
+			__inc_bdi_stat(mapping->a_bdi, BDI_RECLAIMABLE);
 			task_io_account_write(PAGE_CACHE_SIZE);
 		}
 		radix_tree_tag_set(&mapping->page_tree,
@@ -593,7 +592,7 @@ static int ceph_writepages_start(struct
 				 struct writeback_control *wbc)
 {
 	struct inode *inode = mapping->host;
-	struct backing_dev_info *bdi = mapping->backing_dev_info;
+	struct backing_dev_info *bdi = mapping->a_bdi;
 	struct ceph_inode_info *ci = ceph_inode(inode);
 	struct ceph_client *client;
 	pgoff_t index, start, end;
fs/cifs/file.c +1 -1
Index: linux-2.6/fs/cifs/file.c
===================================================================
@@ -1365,7 +1365,7 @@ static int cifs_partialpagewrite(struct
 static int cifs_writepages(struct address_space *mapping,
 			   struct writeback_control *wbc)
 {
-	struct backing_dev_info *bdi = mapping->backing_dev_info;
+	struct backing_dev_info *bdi = mapping->a_bdi;
 	unsigned int bytes_to_write;
 	unsigned int bytes_written;
 	struct cifs_sb_info *cifs_sb;
fs/fuse/file.c +3 -3
Index: linux-2.6/fs/fuse/file.c
===================================================================
@@ -945,7 +945,7 @@ static ssize_t fuse_file_aio_write(struc
 	vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
 
 	/* We can write back this queue in page reclaim */
-	current->backing_dev_info = mapping->backing_dev_info;
+	current->backing_dev_info = mapping->a_bdi;
 
 	err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
 	if (err)
@@ -1133,7 +1133,7 @@ static void fuse_writepage_finish(struct
 {
 	struct inode *inode = req->inode;
 	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct backing_dev_info *bdi = inode->i_mapping->backing_dev_info;
+	struct backing_dev_info *bdi = inode->i_mapping->a_bdi;
 
 	list_del(&req->writepages_entry);
 	dec_bdi_stat(bdi, BDI_WRITEBACK);
@@ -1247,7 +1247,7 @@ static int fuse_writepage_locked(struct
 	req->end = fuse_writepage_end;
 	req->inode = inode;
 
-	inc_bdi_stat(mapping->backing_dev_info, BDI_WRITEBACK);
+	inc_bdi_stat(mapping->a_bdi, BDI_WRITEBACK);
 	inc_zone_page_state(tmp_page, NR_WRITEBACK_TEMP);
 	end_page_writeback(page);
 
fs/fuse/inode.c +1 -1
Index: linux-2.6/fs/fuse/inode.c
===================================================================
@@ -254,7 +254,7 @@ struct inode *fuse_iget(struct super_blo
 	if ((inode->i_state & I_NEW)) {
 		inode->i_flags |= S_NOATIME|S_NOCMTIME;
 		inode->i_generation = generation;
-		inode->i_data.backing_dev_info = &fc->bdi;
+		mapping_new_set_bdi(&inode->i_data, &fc->bdi);
 		fuse_init_inode(inode, attr);
 		unlock_new_inode(inode);
 	} else if ((inode->i_mode ^ attr->mode) & S_IFMT) {
fs/nfs/inode.c +1 -1
Index: linux-2.6/fs/nfs/inode.c
===================================================================
@@ -280,7 +280,7 @@ nfs_fhget(struct super_block *sb, struct
 		if (S_ISREG(inode->i_mode)) {
 			inode->i_fop = &nfs_file_operations;
 			inode->i_data.a_ops = &nfs_file_aops;
-			inode->i_data.backing_dev_info = &NFS_SB(sb)->backing_dev_info;
+			mapping_new_set_bdi(&inode->i_data, &NFS_SB(sb)->backing_dev_info);
 		} else if (S_ISDIR(inode->i_mode)) {
 			inode->i_op = NFS_SB(sb)->nfs_client->rpc_ops->dir_inode_ops;
 			inode->i_fop = &nfs_dir_operations;
fs/nfs/write.c +3 -4
Index: linux-2.6/fs/nfs/write.c
===================================================================
@@ -444,7 +444,7 @@ nfs_mark_request_commit(struct nfs_page
 	nfsi->ncommit++;
 	spin_unlock(&inode->i_lock);
 	inc_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
-	inc_bdi_stat(req->wb_page->mapping->backing_dev_info, BDI_RECLAIMABLE);
+	inc_bdi_stat(req->wb_page->mapping->a_bdi, BDI_RECLAIMABLE);
 	__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
 }
 
@@ -455,7 +455,7 @@ nfs_clear_request_commit(struct nfs_page
 
 	if (test_and_clear_bit(PG_CLEAN, &(req)->wb_flags)) {
 		dec_zone_page_state(page, NR_UNSTABLE_NFS);
-		dec_bdi_stat(page->mapping->backing_dev_info, BDI_RECLAIMABLE);
+		dec_bdi_stat(page->mapping->a_bdi, BDI_RECLAIMABLE);
 		return 1;
 	}
 	return 0;
@@ -1307,8 +1307,7 @@ nfs_commit_list(struct inode *inode, str
 		nfs_list_remove_request(req);
 		nfs_mark_request_commit(req);
 		dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
-		dec_bdi_stat(req->wb_page->mapping->backing_dev_info,
-				BDI_RECLAIMABLE);
+		dec_bdi_stat(req->wb_page->mapping->a_bdi, BDI_RECLAIMABLE);
 		nfs_clear_page_tag_locked(req);
 	}
 	nfs_commit_clear_lock(NFS_I(inode));
fs/nilfs2/the_nilfs.c +1 -1
Index: linux-2.6/fs/nilfs2/the_nilfs.c
===================================================================
@@ -613,7 +613,7 @@ int init_nilfs(struct the_nilfs *nilfs,
 
 	nilfs->ns_mount_state = le16_to_cpu(sbp->s_state);
 
-	bdi = nilfs->ns_bdev->bd_inode->i_mapping->backing_dev_info;
+	bdi = nilfs->ns_bdev->bd_inode->i_mapping->a_bdi;
 	nilfs->ns_bdi = bdi ? : &default_backing_dev_info;
 
 	/* Finding last segment */
fs/ntfs/file.c +1 -1
Index: linux-2.6/fs/ntfs/file.c
===================================================================
@@ -2088,7 +2088,7 @@ static ssize_t ntfs_file_aio_write_noloc
 	pos = *ppos;
 	vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
 	/* We can write back this queue in page reclaim. */
-	current->backing_dev_info = mapping->backing_dev_info;
+	current->backing_dev_info = mapping->a_bdi;
 	written = 0;
 	err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
 	if (err)
fs/ocfs2/file.c +1 -1
Index: linux-2.6/fs/ocfs2/file.c
===================================================================
@@ -2129,7 +2129,7 @@ relock:
 			goto out_dio;
 		}
 	} else {
-		current->backing_dev_info = file->f_mapping->backing_dev_info;
+		current->backing_dev_info = file->f_mapping->a_bdi;
 		written = generic_file_buffered_write(iocb, iov, nr_segs, *ppos,
 						      ppos, count, 0);
 		current->backing_dev_info = NULL;
fs/romfs/super.c +2 -2
Index: linux-2.6/fs/romfs/super.c
===================================================================
@@ -356,8 +356,8 @@ static struct inode *romfs_iget(struct s
 		i->i_fop = &romfs_ro_fops;
 		i->i_data.a_ops = &romfs_aops;
 		if (i->i_sb->s_mtd)
-			i->i_data.backing_dev_info =
-				i->i_sb->s_mtd->backing_dev_info;
+			mapping_new_set_bdi(&i->i_data,
+				i->i_sb->s_mtd->backing_dev_info);
 		if (nextfh & ROMFH_EXEC)
 			mode |= S_IXUGO;
 		break;
fs/xfs/linux-2.6/xfs_file.c +1 -1
Index: linux-2.6/fs/xfs/linux-2.6/xfs_file.c
===================================================================
@@ -756,7 +756,7 @@ start:
 		goto out_unlock_internal;
 
 	/* We can write back this queue in page reclaim */
-	current->backing_dev_info = mapping->backing_dev_info;
+	current->backing_dev_info = mapping->a_bdi;
 
 	if ((ioflags & IO_ISDIRECT)) {
 		if (mapping->nrpages) {
#120 lore raw

PATCH 48/52 fs: icache split IO and LRU lists

In reply to [email protected]

Split inode reclaim and writeback lists in preparation to scale them up (per-bdi locking for i_io and per-zone locking for i_lru)

--

fs/fs-writeback.c +17 -13
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -291,11 +291,11 @@ static void redirty_tail(struct inode *i
 	if (!list_empty(&wb->b_dirty)) {
 		struct inode *tail;
 
-		tail = list_entry(wb->b_dirty.next, struct inode, i_list);
+		tail = list_entry(wb->b_dirty.next, struct inode, i_io);
 		if (time_before(inode->dirtied_when, tail->dirtied_when))
 			inode->dirtied_when = jiffies;
 	}
-	list_move(&inode->i_list, &wb->b_dirty);
+	list_move(&inode->i_io, &wb->b_dirty);
 }
 
 /*
@@ -306,7 +306,7 @@ static void requeue_io(struct inode *ino
 	struct bdi_writeback *wb = &inode_to_bdi(inode)->wb;
 
 	assert_spin_locked(&wb_inode_list_lock);
-	list_move(&inode->i_list, &wb->b_more_io);
+	list_move(&inode->i_io, &wb->b_more_io);
 }
 
 static void inode_sync_complete(struct inode *inode)
@@ -348,14 +348,14 @@ static void move_expired_inodes(struct l
 
 	assert_spin_locked(&wb_inode_list_lock);
 	while (!list_empty(delaying_queue)) {
-		inode = list_entry(delaying_queue->prev, struct inode, i_list);
+		inode = list_entry(delaying_queue->prev, struct inode, i_io);
 		if (older_than_this &&
 		    inode_dirtied_after(inode, *older_than_this))
 			break;
 		if (sb && sb != inode->i_sb)
 			do_sb_sort = 1;
 		sb = inode->i_sb;
-		list_move(&inode->i_list, &tmp);
+		list_move(&inode->i_io, &tmp);
 	}
 
 	/* just one sb in list, splice to dispatch_queue and we're done */
@@ -366,12 +366,12 @@ static void move_expired_inodes(struct l
 
 	/* Move inodes from one superblock together */
 	while (!list_empty(&tmp)) {
-		inode = list_entry(tmp.prev, struct inode, i_list);
+		inode = list_entry(tmp.prev, struct inode, i_io);
 		sb = inode->i_sb;
 		list_for_each_prev_safe(pos, node, &tmp) {
-			inode = list_entry(pos, struct inode, i_list);
+			inode = list_entry(pos, struct inode, i_io);
 			if (inode->i_sb == sb)
-				list_move(&inode->i_list, dispatch_queue);
+				list_move(&inode->i_io, dispatch_queue);
 		}
 	}
 }
@@ -556,7 +556,11 @@ select_queue:
 			}
 		} else {
 			/* The inode is clean */
-			list_move(&inode->i_list, &inode_unused);
+			list_del_init(&inode->i_io);
+			if (list_empty(&inode->i_lru)) {
+				list_add(&inode->i_lru, &inode_unused);
+				inodes_stat.nr_unused++;
+			}
 		}
 	}
 	inode_sync_complete(inode);
@@ -623,7 +627,7 @@ again:
 	while (!list_empty(&wb->b_io)) {
 		long pages_skipped;
 		struct inode *inode = list_entry(wb->b_io.prev,
-						 struct inode, i_list);
+						 struct inode, i_io);
 		if (!spin_trylock(&inode->i_lock)) {
 			spin_unlock(&wb_inode_list_lock);
 			spin_lock(&wb_inode_list_lock);
@@ -696,7 +700,7 @@ again:
 
 	while (!list_empty(&wb->b_io)) {
 		struct inode *inode = list_entry(wb->b_io.prev,
-						 struct inode, i_list);
+						 struct inode, i_io);
 		struct super_block *sb = inode->i_sb;
 		enum sb_pin_state state;
 
@@ -845,7 +849,7 @@ retry:
 		spin_lock(&wb_inode_list_lock);
 		if (!list_empty(&wb->b_more_io))  {
 			inode = list_entry(wb->b_more_io.prev,
-						struct inode, i_list);
+						struct inode, i_io);
 			if (!spin_trylock(&inode->i_lock)) {
 				spin_unlock(&wb_inode_list_lock);
 				goto retry;
@@ -1164,7 +1168,7 @@ void __mark_inode_dirty(struct inode *in
 
 			inode->dirtied_when = jiffies;
 			spin_lock(&wb_inode_list_lock);
-			list_move(&inode->i_list, &wb->b_dirty);
+			list_move(&inode->i_io, &wb->b_dirty);
 			spin_unlock(&wb_inode_list_lock);
 		}
 	}
include/linux/fs.h +2 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -726,7 +726,8 @@ struct posix_acl;
 
 struct inode {
 	struct hlist_bl_node	i_hash;
-	struct list_head	i_list;		/* backing dev IO list */
+	struct list_head	i_io;		/* backing dev IO list */
+	struct list_head	i_lru;
 	struct list_head	i_sb_list;
 	union {
 		struct list_head	i_dentry;
mm/backing-dev.c +3 -3
Index: linux-2.6/mm/backing-dev.c
===================================================================
@@ -80,11 +80,11 @@ static int bdi_debug_stats_show(struct s
 	spin_lock(&wb_inode_list_lock);
 	list_for_each_entry(wb, &bdi->wb_list, list) {
 		nr_wb++;
-		list_for_each_entry(inode, &wb->b_dirty, i_list)
+		list_for_each_entry(inode, &wb->b_dirty, i_io)
 			nr_dirty++;
-		list_for_each_entry(inode, &wb->b_io, i_list)
+		list_for_each_entry(inode, &wb->b_io, i_io)
 			nr_io++;
-		list_for_each_entry(inode, &wb->b_more_io, i_list)
+		list_for_each_entry(inode, &wb->b_more_io, i_io)
 			nr_more_io++;
 	}
 	spin_unlock(&wb_inode_list_lock);
fs/inode.c +34 -22

144 lines not on this page · raw message

#121 lore raw

PATCH 49/52 fs: icache scale writeback list locking

In reply to [email protected]

Split wb_inode_list_lock lock into two locks, inode_lru_lock to protect inode LRU list, and a per-bdi lock to protect the inode writeback lists. Inode is given another list anchor so it can be present on both the LRU and the writeback lists, for simplicity.

--

fs/fs-writeback.c +55 -53
Index: linux-2.6/fs/fs-writeback.c
===================================================================
@@ -283,11 +283,9 @@ void bdi_start_writeback(struct backing_
  * 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)
+static void redirty_tail(struct bdi_writeback *wb, struct inode *inode)
 {
-	struct bdi_writeback *wb = &inode_to_bdi(inode)->wb;
-
-	assert_spin_locked(&wb_inode_list_lock);
+	assert_spin_locked(&wb->b_lock);
 	if (!list_empty(&wb->b_dirty)) {
 		struct inode *tail;
 
@@ -301,11 +299,9 @@ static void redirty_tail(struct inode *i
 /*
  * requeue inode for re-scanning after bdi->b_io list is exhausted.
  */
-static void requeue_io(struct inode *inode)
+static void requeue_io(struct bdi_writeback *wb, struct inode *inode)
 {
-	struct bdi_writeback *wb = &inode_to_bdi(inode)->wb;
-
-	assert_spin_locked(&wb_inode_list_lock);
+	assert_spin_locked(&wb->b_lock);
 	list_move(&inode->i_io, &wb->b_more_io);
 }
 
@@ -346,7 +342,6 @@ static void move_expired_inodes(struct l
 	struct inode *inode;
 	int do_sb_sort = 0;
 
-	assert_spin_locked(&wb_inode_list_lock);
 	while (!list_empty(delaying_queue)) {
 		inode = list_entry(delaying_queue->prev, struct inode, i_io);
 		if (older_than_this &&
@@ -395,18 +390,19 @@ static int write_inode(struct inode *ino
 /*
  * Wait for writeback on an inode to complete.
  */
-static void inode_wait_for_writeback(struct inode *inode)
+static void inode_wait_for_writeback(struct bdi_writeback *wb,
+					struct inode *inode)
 {
 	DEFINE_WAIT_BIT(wq, &inode->i_state, __I_SYNC);
 	wait_queue_head_t *wqh;
 
 	wqh = bit_waitqueue(&inode->i_state, __I_SYNC);
 	while (inode->i_state & I_SYNC) {
-		spin_unlock(&wb_inode_list_lock);
+		spin_unlock(&wb->b_lock);
 		spin_unlock(&inode->i_lock);
 		__wait_on_bit(wqh, &wq, inode_wait, TASK_UNINTERRUPTIBLE);
 		spin_lock(&inode->i_lock);
-		spin_lock(&wb_inode_list_lock);
+		spin_lock(&wb->b_lock);
 	}
 }
 
@@ -424,7 +420,8 @@ static void inode_wait_for_writeback(str
  * Called under inode_lock.
  */
 static int
-writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
+writeback_single_inode(struct bdi_writeback *wb, struct inode *inode,
+			struct writeback_control *wbc)
 {
 	struct address_space *mapping = inode->i_mapping;
 	unsigned dirty;
@@ -445,14 +442,14 @@ writeback_single_inode(struct inode *ino
 		 * completed a full scan of b_io.
 		 */
 		if (wbc->sync_mode != WB_SYNC_ALL) {
-			requeue_io(inode);
+			requeue_io(wb, inode);
 			return 0;
 		}
 
 		/*
 		 * It's a data-integrity sync.  We must wait.
 		 */
-		inode_wait_for_writeback(inode);
+		inode_wait_for_writeback(wb, inode);
 	}
 
 	BUG_ON(inode->i_state & I_SYNC);
@@ -460,7 +457,7 @@ writeback_single_inode(struct inode *ino
 	/* Set I_SYNC, reset I_DIRTY_PAGES */
 	inode->i_state |= I_SYNC;
 	inode->i_state &= ~I_DIRTY_PAGES;
-	spin_unlock(&wb_inode_list_lock);
+	spin_unlock(&wb->b_lock);
 	spin_unlock(&inode->i_lock);
 
 	ret = do_writepages(mapping, wbc);
@@ -495,7 +492,7 @@ writeback_single_inode(struct inode *ino
 		spin_lock(&inode->i_lock);
 	}
 
-	spin_lock(&wb_inode_list_lock);
+	spin_lock(&wb->b_lock);
 	inode->i_state &= ~I_SYNC;
 	if (!(inode->i_state & (I_FREEING | I_CLEAR))) {
 		if ((inode->i_state & I_DIRTY_PAGES) && wbc->for_kupdate) {
@@ -508,7 +505,7 @@ writeback_single_inode(struct inode *ino
 			 * At least XFS will redirty the inode during the
 			 * writeback (delalloc) and on io completion (isize).
 			 */
-			redirty_tail(inode);
+			redirty_tail(wb, inode);
 		} else if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) {
 			/*
 			 * We didn't write back all the pages.  nfs_writepages()
@@ -536,12 +533,12 @@ select_queue:
 					/*
 					 * slice used up: queue for next turn
 					 */
-					requeue_io(inode);
+					requeue_io(wb, inode);
 				} else {
 					/*
 					 * somehow blocked: retry later
 					 */
-					redirty_tail(inode);
+					redirty_tail(wb, inode);
 				}
 			} else {
 				/*
@@ -552,15 +549,13 @@ select_queue:
 				 * all the other files.
 				 */
 				inode->i_state |= I_DIRTY_PAGES;
-				redirty_tail(inode);
+				redirty_tail(wb, inode);
 			}
 		} else {
 			/* The inode is clean */
 			list_del_init(&inode->i_io);
-			if (list_empty(&inode->i_lru)) {
-				list_add(&inode->i_lru, &inode_unused);
-				inodes_stat.nr_unused++;
-			}
+			if (list_empty(&inode->i_lru))
+				__inode_lru_list_add(inode);
 		}
 	}
 	inode_sync_complete(inode);
@@ -629,14 +624,15 @@ again:
 		struct inode *inode = list_entry(wb->b_io.prev,
 						 struct inode, i_io);
 		if (!spin_trylock(&inode->i_lock)) {
-			spin_unlock(&wb_inode_list_lock);
-			spin_lock(&wb_inode_list_lock);
+			spin_unlock(&wb->b_lock);
+			cpu_relax();
+			spin_lock(&wb->b_lock);
 			goto again;
 		}
 		if (wbc->sb && sb != inode->i_sb) {
 			/* super block given and doesn't
 			   match, skip this inode */
-			redirty_tail(inode);
+			redirty_tail(wb, inode);
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
@@ -646,7 +642,7 @@ again:
 			return 0;
 		}
 		if (inode->i_state & (I_NEW | I_WILL_FREE)) {
-			requeue_io(inode);
+			requeue_io(wb, inode);
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
@@ -662,19 +658,19 @@ again:
 		BUG_ON(inode->i_state & (I_FREEING | I_CLEAR));
 		__iget(inode);
 		pages_skipped = wbc->pages_skipped;
-		writeback_single_inode(inode, wbc);
+		writeback_single_inode(wb, inode, wbc);
 		if (wbc->pages_skipped != pages_skipped) {
 			/*
 			 * writeback is not making progress due to locked
 			 * buffers.  Skip this inode for now.
 			 */
-			redirty_tail(inode);
+			redirty_tail(wb, inode);
 		}
-		spin_unlock(&wb_inode_list_lock);
+		spin_unlock(&wb->b_lock);
 		spin_unlock(&inode->i_lock);
 		iput(inode);
 		cond_resched();
-		spin_lock(&wb_inode_list_lock);
+		spin_lock(&wb->b_lock);
 		if (wbc->nr_to_write <= 0) {
 			wbc->more_io = 1;
 			return 1;
@@ -693,7 +689,7 @@ static void writeback_inodes_wb(struct b
 
 	wbc->wb_start = jiffies; /* livelock avoidance */
 again:
-	spin_lock(&wb_inode_list_lock);
+	spin_lock(&wb->b_lock);
 
 	if (!wbc->for_kupdate || list_empty(&wb->b_io))
 		queue_io(wb, wbc->older_than_this);
@@ -708,10 +704,11 @@ again:
 			/* super block given and doesn't
 			   match, skip this inode */
 			if (!spin_trylock(&inode->i_lock)) {
-				spin_unlock(&wb_inode_list_lock);
+				spin_unlock(&wb->b_lock);
+				cpu_relax();
 				goto again;
 			}
-			redirty_tail(inode);
+			redirty_tail(wb, inode);
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
@@ -719,10 +716,11 @@ again:
 
 		if (state == SB_PIN_FAILED) {
 			if (!spin_trylock(&inode->i_lock)) {
-				spin_unlock(&wb_inode_list_lock);
+				spin_unlock(&wb->b_lock);
+				cpu_relax();
 				goto again;
 			}
-			requeue_io(inode);
+			requeue_io(wb, inode);
 			spin_unlock(&inode->i_lock);
 			continue;
 		}
@@ -733,7 +731,7 @@ again:
 		if (ret)
 			break;
 	}
-	spin_unlock(&wb_inode_list_lock);
+	spin_unlock(&wb->b_lock);
 	/* Leave any unwritten inodes on b_io */
 }
 
@@ -846,18 +844,19 @@ static long wb_writeback(struct bdi_writ
 		 * we'll just busyloop.
 		 */
 retry:
-		spin_lock(&wb_inode_list_lock);
+		spin_lock(&wb->b_lock);
 		if (!list_empty(&wb->b_more_io))  {
 			inode = list_entry(wb->b_more_io.prev,
 						struct inode, i_io);
 			if (!spin_trylock(&inode->i_lock)) {
-				spin_unlock(&wb_inode_list_lock);
+				spin_unlock(&wb->b_lock);
+				cpu_relax();
 				goto retry;
 			}
-			inode_wait_for_writeback(inode);
+			inode_wait_for_writeback(wb, inode);
 			spin_unlock(&inode->i_lock);
 		}
-		spin_unlock(&wb_inode_list_lock);
+		spin_unlock(&wb->b_lock);
 	}
 
 	return wrote;
@@ -1156,7 +1155,7 @@ void __mark_inode_dirty(struct inode *in
 		 * reposition it (that would break b_dirty time-ordering).
 		 */
 		if (!was_dirty) {
-			struct bdi_writeback *wb = &inode_to_bdi(inode)->wb;
+			struct bdi_writeback *wb = inode_to_wb(inode);
 			struct backing_dev_info *bdi = wb->bdi;
 
 			if (bdi_cap_writeback_dirty(bdi) &&
@@ -1167,9 +1166,10 @@ void __mark_inode_dirty(struct inode *in
 			}
 
 			inode->dirtied_when = jiffies;
-			spin_lock(&wb_inode_list_lock);
-			list_move(&inode->i_io, &wb->b_dirty);
-			spin_unlock(&wb_inode_list_lock);
+			spin_lock(&wb->b_lock);
+			BUG_ON(!list_empty(&inode->i_io));
+			list_add(&inode->i_io, &wb->b_dirty);
+			spin_unlock(&wb->b_lock);
 		}
 	}
 out:
@@ -1313,6 +1313,7 @@ EXPORT_SYMBOL(sync_inodes_sb);
  */
 int write_inode_now(struct inode *inode, int sync)
 {
+	struct bdi_writeback *wb = inode_to_wb(inode);
 	int ret;
 	struct writeback_control wbc = {
 		.nr_to_write = LONG_MAX,
@@ -1326,9 +1327,9 @@ int write_inode_now(struct inode *inode,
 
 	might_sleep();
 	spin_lock(&inode->i_lock);
-	spin_lock(&wb_inode_list_lock);
-	ret = writeback_single_inode(inode, &wbc);
-	spin_unlock(&wb_inode_list_lock);
+	spin_lock(&wb->b_lock);
+	ret = writeback_single_inode(wb, inode, &wbc);
+	spin_unlock(&wb->b_lock);
 	spin_unlock(&inode->i_lock);
 	if (sync)
 		inode_sync_wait(inode);
@@ -1349,12 +1350,13 @@ EXPORT_SYMBOL(write_inode_now);
  */
 int sync_inode(struct inode *inode, struct writeback_control *wbc)
 {
+	struct bdi_writeback *wb = inode_to_wb(inode);
 	int ret;
 
 	spin_lock(&inode->i_lock);
-	spin_lock(&wb_inode_list_lock);
-	ret = writeback_single_inode(inode, wbc);
-	spin_unlock(&wb_inode_list_lock);
+	spin_lock(&wb->b_lock);
+	ret = writeback_single_inode(wb, inode, wbc);
+	spin_unlock(&wb->b_lock);
 	spin_unlock(&inode->i_lock);
 	return ret;
 }
fs/inode.c +56 -28

200 lines not on this page · raw message

include/linux/backing-dev.h +2 -0
Index: linux-2.6/include/linux/backing-dev.h
===================================================================
@@ -16,6 +16,7 @@
 #include <linux/sched.h>
 #include <linux/timer.h>
 #include <linux/writeback.h>
+#include <linux/spinlock.h>
 #include <asm/atomic.h>
 
 struct page;
@@ -53,6 +54,7 @@ struct bdi_writeback {
 	unsigned long last_old_flush;		/* last old data flush */
 
 	struct task_struct	*task;		/* writeback task */
+	spinlock_t		b_lock;		/* lock for inode lists */
 	struct list_head	b_dirty;	/* dirty inodes */
 	struct list_head	b_io;		/* parked for writeback */
 	struct list_head	b_more_io;	/* parked for more writeback */
include/linux/writeback.h +0 -3
Index: linux-2.6/include/linux/writeback.h
===================================================================
@@ -9,9 +9,6 @@
 
 struct backing_dev_info;
 
-extern spinlock_t wb_inode_list_lock;
-extern struct list_head inode_unused;
-
 /*
  * fs/fs-writeback.c
  */
mm/backing-dev.c +24 -7
Index: linux-2.6/mm/backing-dev.c
===================================================================
@@ -75,19 +75,22 @@ static int bdi_debug_stats_show(struct s
 	/*
 	 * inode lock is enough here, the bdi->wb_list is protected by
 	 * RCU on the reader side
+	 * (so why not for_each_entry_rcu, and why no explicit rcu disable??)
 	 */
 	nr_wb = nr_dirty = nr_io = nr_more_io = 0;
-	spin_lock(&wb_inode_list_lock);
-	list_for_each_entry(wb, &bdi->wb_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(wb, &bdi->wb_list, list) {
 		nr_wb++;
+		spin_lock(&wb->b_lock);
 		list_for_each_entry(inode, &wb->b_dirty, i_io)
 			nr_dirty++;
 		list_for_each_entry(inode, &wb->b_io, i_io)
 			nr_io++;
 		list_for_each_entry(inode, &wb->b_more_io, i_io)
 			nr_more_io++;
+		spin_unlock(&wb->b_lock);
 	}
-	spin_unlock(&wb_inode_list_lock);
+	rcu_read_unlock();
 
 	get_dirty_limits(&background_thresh, &dirty_thresh, &bdi_thresh, bdi);
 
@@ -267,6 +270,7 @@ static void bdi_wb_init(struct bdi_write
 
 	wb->bdi = bdi;
 	wb->last_old_flush = jiffies;
+	spin_lock_init(&wb->b_lock);
 	INIT_LIST_HEAD(&wb->b_dirty);
 	INIT_LIST_HEAD(&wb->b_io);
 	INIT_LIST_HEAD(&wb->b_more_io);
@@ -700,6 +704,17 @@ err:
 }
 EXPORT_SYMBOL(bdi_init);
 
+static void bdi_lock_two(struct backing_dev_info *bdi1, struct backing_dev_info *bdi2)
+{
+	if (bdi1 < bdi2) {
+		spin_lock(&bdi1->wb.b_lock);
+		spin_lock_nested(&bdi2->wb.b_lock, 1);
+	} else {
+		spin_lock(&bdi2->wb.b_lock);
+		spin_lock_nested(&bdi1->wb.b_lock, 1);
+	}
+}
+
 void mapping_set_bdi(struct address_space *mapping, struct backing_dev_info *bdi)
 {
 	struct inode *inode = mapping->host;
@@ -708,7 +723,7 @@ void mapping_set_bdi(struct address_spac
 	if (unlikely(old == bdi))
 		return;
 
-	spin_lock(&wb_inode_list_lock);
+	bdi_lock_two(bdi, old);
 	if (!list_empty(&inode->i_io)) {
 		struct inode *i;
 
@@ -737,7 +752,8 @@ void mapping_set_bdi(struct address_spac
 	}
 found:
 	mapping->a_bdi = bdi;
-	spin_unlock(&wb_inode_list_lock);
+	spin_unlock(&bdi->wb.b_lock);
+	spin_unlock(&old->wb.b_lock);
 }
 EXPORT_SYMBOL(mapping_set_bdi);
 
@@ -753,7 +769,7 @@ void bdi_destroy(struct backing_dev_info
 		struct bdi_writeback *dst = &default_backing_dev_info.wb;
 		struct inode *i;
 
-		spin_lock(&wb_inode_list_lock);
+		bdi_lock_two(bdi, &default_backing_dev_info);
 		list_for_each_entry(i, &bdi->wb.b_dirty, i_io) {
 			list_del(&i->i_io);
 			list_add(&i->i_io, &dst->b_dirty);
@@ -769,7 +785,8 @@ void bdi_destroy(struct backing_dev_info
 			list_add(&i->i_io, &dst->b_more_io);
 			i->i_mapping->a_bdi = bdi;
 		}
-		spin_unlock(&wb_inode_list_lock);
+		spin_unlock(&bdi->wb.b_lock);
+		spin_unlock(&dst->b_lock);
 	}
 
 	bdi_unregister(bdi);
fs/internal.h +2 -0
Index: linux-2.6/fs/internal.h
===================================================================
@@ -15,6 +15,8 @@ struct super_block;
 struct linux_binprm;
 struct path;
 
+#define inode_to_wb(inode)   (&(inode)->i_mapping->a_bdi->wb)
+
 /*
  * block_dev.c
  */
include/linux/fs.h +2 -0
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -2076,6 +2076,8 @@ extern int check_disk_change(struct bloc
 extern int __invalidate_device(struct block_device *);
 extern int invalidate_partition(struct gendisk *, int);
 #endif
+extern void __inode_lru_list_add(struct inode *inode);
+extern void __inode_lru_list_del(struct inode *inode);
 extern int invalidate_inodes(struct super_block *);
 unsigned long invalidate_mapping_pages(struct address_space *mapping,
 					pgoff_t start, pgoff_t end);
#122 lore raw

PATCH 50/52 mm: implement per-zone shrinker

In reply to [email protected]

Allow the shrinker to do per-zone shrinking. This means it is called for each zone scanned. The shrinker is now completely responsible for calculating and batching (given helpers), which provides better flexibility.

Finding the ratio of objects to scan requires scaling the ratio of pagecache objects scanned. By passing down both the per-zone and the global reclaimable pages, per-zone caches and global caches can be calculated correctly.

Finally, add some fixed-point scaling to the ratio, which helps calculations.

---
 fs/dcache.c        |    2 
 fs/drop_caches.c   |    2 
 fs/inode.c         |    2 
 fs/mbcache.c       |    4 -
 fs/nfs/dir.c       |    2 
 fs/nfs/internal.h  |    2 
 fs/quota/dquot.c   |    2 
 include/linux/mm.h |    6 +-
 mm/vmscan.c        |  131 ++++++++++++++---------------------------------------
 9 files changed, 47 insertions(+), 106 deletions(-)
include/linux/mm.h +9 -7
Index: linux-2.6/include/linux/mm.h
===================================================================
@@ -999,16 +999,19 @@ static inline void sync_mm_rss(struct ta
  * querying the cache size, so a fastpath for that case is appropriate.
  */
 struct shrinker {
-	int (*shrink)(int nr_to_scan, gfp_t gfp_mask);
-	int seeks;	/* seeks to recreate an obj */
-
+	int (*shrink)(struct zone *zone, unsigned long scanned, unsigned long total,
+					unsigned long global, gfp_t gfp_mask);
 	/* These are for internal use */
 	struct list_head list;
-	long nr;	/* objs pending delete */
 };
-#define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
+#define DEFAULT_SEEKS	(128UL*2) /* A good number if you don't know better. */
+#define SHRINK_BATCH	128	/* A good number if you don't know better */
 extern void register_shrinker(struct shrinker *);
 extern void unregister_shrinker(struct shrinker *);
+extern void shrinker_add_scan(unsigned long *dst,
+				unsigned long scanned, unsigned long total,
+				unsigned long objects, unsigned int ratio);
+extern unsigned long shrinker_do_scan(unsigned long *dst, unsigned long batch);
 
 int vma_wants_writenotify(struct vm_area_struct *vma);
 
@@ -1422,8 +1425,7 @@ int in_gate_area_no_task(unsigned long a
 
 int drop_caches_sysctl_handler(struct ctl_table *, int,
 					void __user *, size_t *, loff_t *);
-unsigned long shrink_slab(unsigned long scanned, gfp_t gfp_mask,
-			unsigned long lru_pages);
+void shrink_all_slab(void);
 
 #ifndef CONFIG_MMU
 #define randomize_va_space 0
mm/vmscan.c +82 -98

307 lines not on this page · raw message

fs/dcache.c +20 -14
Index: linux-2.6/fs/dcache.c
===================================================================
@@ -748,20 +748,26 @@ again2:
  *
  * This function may fail to free any resources if all the dentries are in use.
  */
-static void prune_dcache(int count)
+static void prune_dcache(struct zone *zone, unsigned long scanned,
+			unsigned long total, gfp_t gfp_mask)
+
 {
+	unsigned long nr_to_scan;
 	struct super_block *sb, *n;
 	int w_count;
-	int unused = dentry_stat.nr_unused;
 	int prune_ratio;
-	int pruned;
+	int count, pruned;
 
-	if (unused == 0 || count == 0)
+	shrinker_add_scan(&nr_to_scan, scanned, total, dentry_stat.nr_unused,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+done:
+	count = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (dentry_stat.nr_unused == 0 || count == 0)
 		return;
-	if (count >= unused)
+	if (count >= dentry_stat.nr_unused)
 		prune_ratio = 1;
 	else
-		prune_ratio = unused / count;
+		prune_ratio = dentry_stat.nr_unused / count;
 	spin_lock(&sb_lock);
 	list_for_each_entry_safe(sb, n, &super_blocks, s_list) {
 		if (list_empty(&sb->s_instances))
@@ -810,6 +816,10 @@ static void prune_dcache(int count)
 			break;
 	}
 	spin_unlock(&sb_lock);
+	if (count <= 0) {
+		cond_resched();
+		goto done;
+	}
 }
 
 /**
@@ -1176,19 +1186,15 @@ EXPORT_SYMBOL(shrink_dcache_parent);
  *
  * In this case we return -1 to tell the caller that we baled.
  */
-static int shrink_dcache_memory(int nr, gfp_t gfp_mask)
+static int shrink_dcache_memory(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
-	if (nr) {
-		if (!(gfp_mask & __GFP_FS))
-			return -1;
-		prune_dcache(nr);
-	}
-	return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
+	prune_dcache(zone, scanned, global, gfp_mask);
+	return 0;
 }
 
 static struct shrinker dcache_shrinker = {
 	.shrink = shrink_dcache_memory,
-	.seeks = DEFAULT_SEEKS,
 };
 
 /**
fs/inode.c +17 -13
Index: linux-2.6/fs/inode.c
===================================================================
@@ -527,7 +527,7 @@ EXPORT_SYMBOL(invalidate_inodes);
  * If the inode has metadata buffers attached to mapping->private_list then
  * try to remove them.
  */
-static void prune_icache(int nr_to_scan)
+static void prune_icache(struct zone *zone, unsigned long nr_to_scan)
 {
 	LIST_HEAD(freeable);
 	unsigned long reap = 0;
@@ -597,24 +597,28 @@ again:
  * This function is passed the number of inodes to scan, and it returns the
  * total number of remaining possibly-reclaimable inodes.
  */
-static int shrink_icache_memory(int nr, gfp_t gfp_mask)
+static int shrink_icache_memory(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
-	if (nr) {
-		/*
-		 * Nasty deadlock avoidance.  We may hold various FS locks,
-		 * and we don't want to recurse into the FS that called us
-		 * in clear_inode() and friends..
-		 */
-		if (!(gfp_mask & __GFP_FS))
-			return -1;
-		prune_icache(nr);
+	static unsigned long nr_to_scan;
+	unsigned long nr;
+
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			inodes_stat.nr_unused,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+	if (!(gfp_mask & __GFP_FS))
+	       return 0;
+
+	while ((nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH))) {
+		prune_icache(zone, nr);
+		cond_resched();
 	}
-	return inodes_stat.nr_unused / 100 * sysctl_vfs_cache_pressure;
+
+	return 0;
 }
 
 static struct shrinker icache_shrinker = {
 	.shrink = shrink_icache_memory,
-	.seeks = DEFAULT_SEEKS,
 };
 
 static void __wait_on_freeing_inode(struct inode *inode);
fs/mbcache.c +24 -10
Index: linux-2.6/fs/mbcache.c
===================================================================
@@ -115,11 +115,12 @@ mb_cache_indexes(struct mb_cache *cache)
  * What the mbcache registers as to get shrunk dynamically.
  */
 
-static int mb_cache_shrink_fn(int nr_to_scan, gfp_t gfp_mask);
+static int
+mb_cache_shrink_fn(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask);
 
 static struct shrinker mb_cache_shrinker = {
 	.shrink = mb_cache_shrink_fn,
-	.seeks = DEFAULT_SEEKS,
 };
 
 static inline int
@@ -197,11 +198,14 @@ forget:
  * Returns the number of objects which are present in the cache.
  */
 static int
-mb_cache_shrink_fn(int nr_to_scan, gfp_t gfp_mask)
+mb_cache_shrink_fn(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
 	LIST_HEAD(free_list);
 	struct list_head *l, *ltmp;
-	int count = 0;
+	unsigned long count = 0;
+	unsigned long nr;
 
 	spin_lock(&mb_cache_spinlock);
 	list_for_each(l, &mb_cache_list) {
@@ -211,28 +215,38 @@ mb_cache_shrink_fn(int nr_to_scan, gfp_t
 			  atomic_read(&cache->c_entry_count));
 		count += atomic_read(&cache->c_entry_count);
 	}
+	shrinker_add_scan(&nr_to_scan, scanned, global, count,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
 	mb_debug("trying to free %d entries", nr_to_scan);
-	if (nr_to_scan == 0) {
+
+again:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr) {
 		spin_unlock(&mb_cache_spinlock);
-		goto out;
+		return 0;
 	}
-	while (nr_to_scan-- && !list_empty(&mb_cache_lru_list)) {
+	while (!list_empty(&mb_cache_lru_list)) {
 		struct mb_cache_entry *ce =
 			list_entry(mb_cache_lru_list.next,
 				   struct mb_cache_entry, e_lru_list);
 		list_move_tail(&ce->e_lru_list, &free_list);
 		__mb_cache_entry_unhash(ce);
+		cond_resched_lock(&mb_cache_spinlock);
+		if (!--nr)
+			break;
 	}
 	spin_unlock(&mb_cache_spinlock);
 	list_for_each_safe(l, ltmp, &free_list) {
 		__mb_cache_entry_forget(list_entry(l, struct mb_cache_entry,
 						   e_lru_list), gfp_mask);
 	}
-out:
-	return (count / 100) * sysctl_vfs_cache_pressure;
+	if (!nr) {
+		spin_lock(&mb_cache_spinlock);
+		goto again;
+	}
+	return 0;
 }
 
-
 /*
  * mb_cache_create()  create a new cache
  *
fs/nfs/dir.c +20 -9
Index: linux-2.6/fs/nfs/dir.c
===================================================================
@@ -1709,21 +1709,31 @@ static void nfs_access_free_list(struct
 	}
 }
 
-int nfs_access_cache_shrinker(int nr_to_scan, gfp_t gfp_mask)
+int nfs_access_cache_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
 	LIST_HEAD(head);
-	struct nfs_inode *nfsi;
 	struct nfs_access_entry *cache;
-
-	if ((gfp_mask & GFP_KERNEL) != GFP_KERNEL)
-		return (nr_to_scan == 0) ? 0 : -1;
+	unsigned long nr;
 
 	spin_lock(&nfs_access_lru_lock);
-	list_for_each_entry(nfsi, &nfs_access_lru_list, access_cache_inode_lru) {
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			atomic_long_read(&nfs_access_nr_entries),
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+	if (!(gfp_mask & __GFP_FS) || nr_to_scan < SHRINK_BATCH) {
+		spin_unlock(&nfs_access_lru_lock);
+		return 0;
+	}
+	nr = ACCESS_ONCE(nr_to_scan);
+	nr_to_scan = 0;
+
+	while (nr-- && !list_empty(&nfs_access_lru_list)) {
+		struct nfs_inode *nfsi;
 		struct inode *inode;
 
-		if (nr_to_scan-- == 0)
-			break;
+		nfsi = list_entry(nfs_access_lru_list.next,
+				struct nfs_inode, access_cache_inode_lru);
 		inode = &nfsi->vfs_inode;
 		spin_lock(&inode->i_lock);
 		if (list_empty(&nfsi->access_cache_entry_lru))
@@ -1743,10 +1753,11 @@ remove_lru_entry:
 			smp_mb__after_clear_bit();
 		}
 		spin_unlock(&inode->i_lock);
+		cond_resched_lock(&nfs_access_lru_lock);
 	}
 	spin_unlock(&nfs_access_lru_lock);
 	nfs_access_free_list(&head);
-	return (atomic_long_read(&nfs_access_nr_entries) / 100) * sysctl_vfs_cache_pressure;
+	return 0;
 }
 
 static void __nfs_access_zap_cache(struct nfs_inode *nfsi, struct list_head *head)
fs/nfs/internal.h +2 -1
Index: linux-2.6/fs/nfs/internal.h
===================================================================
@@ -205,7 +205,8 @@ extern struct rpc_procinfo nfs4_procedur
 void nfs_close_context(struct nfs_open_context *ctx, int is_sync);
 
 /* dir.c */
-extern int nfs_access_cache_shrinker(int nr_to_scan, gfp_t gfp_mask);
+extern int nfs_access_cache_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask);
 
 /* inode.c */
 extern struct workqueue_struct *nfsiod_workqueue;
fs/quota/dquot.c +14 -7
Index: linux-2.6/fs/quota/dquot.c
===================================================================
@@ -655,7 +655,7 @@ int dquot_quota_sync(struct super_block
 EXPORT_SYMBOL(dquot_quota_sync);
 
 /* Free unused dquots from cache */
-static void prune_dqcache(int count)
+static void prune_dqcache(unsigned long count)
 {
 	struct list_head *head;
 	struct dquot *dquot;
@@ -676,21 +676,28 @@ static void prune_dqcache(int count)
  * This is called from kswapd when we think we need some
  * more memory
  */
-static int shrink_dqcache_memory(int nr, gfp_t gfp_mask)
+static int shrink_dqcache_memory(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
-	if (nr) {
+	static unsigned long nr_to_scan;
+	unsigned long nr;
+
+	shrinker_add_scan(&nr_to_scan, scanned, total,
+	       percpu_counter_read_positive(&dqstats.counter[DQST_FREE_DQUOTS]),
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+
+	while ((nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH))) {
 		spin_lock(&dq_list_lock);
 		prune_dqcache(nr);
 		spin_unlock(&dq_list_lock);
+		cond_resched();
 	}
-	return ((unsigned)
-		percpu_counter_read_positive(&dqstats.counter[DQST_FREE_DQUOTS])
-		/100) * sysctl_vfs_cache_pressure;
+
+	return 0;
 }
 
 static struct shrinker dqcache_shrinker = {
 	.shrink = shrink_dqcache_memory,
-	.seeks = DEFAULT_SEEKS,
 };
 
 /*
fs/drop_caches.c +1 -5
Index: linux-2.6/fs/drop_caches.c
===================================================================
@@ -38,11 +38,7 @@ static void drop_pagecache_sb(struct sup
 
 static void drop_slab(void)
 {
-	int nr_objects;
-
-	do {
-		nr_objects = shrink_slab(1000, GFP_KERNEL, 1000);
-	} while (nr_objects > 10);
+	shrink_all_slab();
 }
 
 int drop_caches_sysctl_handler(ctl_table *table, int write,
mm/memory-failure.c +2 -8
Index: linux-2.6/mm/memory-failure.c
===================================================================
@@ -229,14 +229,8 @@ void shake_page(struct page *p, int acce
 	 * Only all shrink_slab here (which would also
 	 * shrink other caches) if access is not potentially fatal.
 	 */
-	if (access) {
-		int nr;
-		do {
-			nr = shrink_slab(1000, GFP_KERNEL, 1000);
-			if (page_count(p) == 0)
-				break;
-		} while (nr > 10);
-	}
+	if (access)
+		shrink_all_slab();
 }
 EXPORT_SYMBOL_GPL(shake_page);
 
arch/x86/kvm/mmu.c +23 -12
Index: linux-2.6/arch/x86/kvm/mmu.c
===================================================================
@@ -2924,14 +2924,29 @@ static int kvm_mmu_remove_some_alloc_mmu
 	return kvm_mmu_zap_page(kvm, page) + 1;
 }
 
-static int mmu_shrink(int nr_to_scan, gfp_t gfp_mask)
+static int mmu_shrink(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
 	struct kvm *kvm;
 	struct kvm *kvm_freed = NULL;
-	int cache_count = 0;
+	unsigned long cache_count = 0;
 
 	spin_lock(&kvm_lock);
+	list_for_each_entry(kvm, &vm_list, vm_list) {
+		cache_count += kvm->arch.n_alloc_mmu_pages -
+			 kvm->arch.n_free_mmu_pages;
+	}
 
+	shrinker_add_scan(&nr_to_scan, scanned, global, cache_count,
+			DEFAULT_SEEKS*10);
+
+done:
+	cache_count = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!cache_count) {
+		spin_unlock(&kvm_lock);
+		return 0;
+	}
 	list_for_each_entry(kvm, &vm_list, vm_list) {
 		int npages, idx, freed_pages;
 
@@ -2939,28 +2954,24 @@ static int mmu_shrink(int nr_to_scan, gf
 		spin_lock(&kvm->mmu_lock);
 		npages = kvm->arch.n_alloc_mmu_pages -
 			 kvm->arch.n_free_mmu_pages;
-		cache_count += npages;
-		if (!kvm_freed && nr_to_scan > 0 && npages > 0) {
+		if (!kvm_freed && npages > 0) {
 			freed_pages = kvm_mmu_remove_some_alloc_mmu_pages(kvm);
-			cache_count -= freed_pages;
 			kvm_freed = kvm;
 		}
-		nr_to_scan--;
-
 		spin_unlock(&kvm->mmu_lock);
 		srcu_read_unlock(&kvm->srcu, idx);
+
+		if (!--cache_count)
+			break;
 	}
 	if (kvm_freed)
 		list_move_tail(&kvm_freed->vm_list, &vm_list);
-
-	spin_unlock(&kvm_lock);
-
-	return cache_count;
+	cond_resched_lock(&kvm_lock);
+	goto done;
 }
 
 static struct shrinker mmu_shrinker = {
 	.shrink = mmu_shrink,
-	.seeks = DEFAULT_SEEKS * 10,
 };
 
 static void mmu_destroy_caches(void)
drivers/gpu/drm/i915/i915_gem.c +31 -38

125 lines not on this page · raw message

drivers/gpu/drm/ttm/ttm_page_alloc.c +24 -16
Index: linux-2.6/drivers/gpu/drm/ttm/ttm_page_alloc.c
===================================================================
@@ -395,30 +395,38 @@ static int ttm_pool_get_num_unused_pages
 /**
  * Callback for mm to request pool to reduce number of page held.
  */
-static int ttm_pool_mm_shrink(int shrink_pages, gfp_t gfp_mask)
+static int ttm_pool_mm_shrink(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
-	static atomic_t start_pool = ATOMIC_INIT(0);
-	unsigned i;
-	unsigned pool_offset = atomic_add_return(1, &start_pool);
-	struct ttm_page_pool *pool;
+	static unsigned long nr_to_scan;
+	unsigned long shrink_pages;
 
-	pool_offset = pool_offset % NUM_POOLS;
-	/* select start pool in round robin fashion */
-	for (i = 0; i < NUM_POOLS; ++i) {
-		unsigned nr_free = shrink_pages;
-		if (shrink_pages == 0)
-			break;
-		pool = &_manager.pools[(i + pool_offset)%NUM_POOLS];
-		shrink_pages = ttm_page_pool_free(pool, nr_free);
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			ttm_pool_get_num_unused_pages(),
+			SHRINK_FIXED);
+
+	while ((shrink_pages = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH))) {
+		static atomic_t start_pool = ATOMIC_INIT(0);
+		unsigned pool_offset = atomic_add_return(1, &start_pool);
+		struct ttm_page_pool *pool;
+		unsigned i;
+
+		pool_offset = pool_offset % NUM_POOLS;
+		/* select start pool in round robin fashion */
+		for (i = 0; i < NUM_POOLS; ++i) {
+			unsigned nr_free = shrink_pages;
+			if (shrink_pages == 0)
+				break;
+			pool = &_manager.pools[(i + pool_offset)%NUM_POOLS];
+			shrink_pages = ttm_page_pool_free(pool, nr_free);
+		}
 	}
-	/* return estimated number of unused pages in pool */
-	return ttm_pool_get_num_unused_pages();
+	return 0;
 }
 
 static void ttm_pool_mm_shrink_init(struct ttm_pool_manager *manager)
 {
 	manager->mm_shrink.shrink = &ttm_pool_mm_shrink;
-	manager->mm_shrink.seeks = 1;
 	register_shrinker(&manager->mm_shrink);
 }
 
fs/gfs2/glock.c +16 -7
Index: linux-2.6/fs/gfs2/glock.c
===================================================================
@@ -1349,18 +1349,27 @@ void gfs2_glock_complete(struct gfs2_glo
 }
 
 
-static int gfs2_shrink_glock_memory(int nr, gfp_t gfp_mask)
+static int gfs2_shrink_glock_memory(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
+	unsigned long nr;
 	struct gfs2_glock *gl;
 	int may_demote;
 	int nr_skipped = 0;
 	LIST_HEAD(skipped);
 
-	if (nr == 0)
-		goto out;
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			atomic_read(&lru_count),
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
 
 	if (!(gfp_mask & __GFP_FS))
-		return -1;
+		return 0;
+
+done:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr)
+		return 0;
 
 	spin_lock(&lru_lock);
 	while(nr && !list_empty(&lru_list)) {
@@ -1392,13 +1401,13 @@ static int gfs2_shrink_glock_memory(int
 	list_splice(&skipped, &lru_list);
 	atomic_add(nr_skipped, &lru_count);
 	spin_unlock(&lru_lock);
-out:
-	return (atomic_read(&lru_count) / 100) * sysctl_vfs_cache_pressure;
+	if (!nr)
+		goto done;
+	return 0;
 }
 
 static struct shrinker glock_shrinker = {
 	.shrink = gfs2_shrink_glock_memory,
-	.seeks = DEFAULT_SEEKS,
 };
 
 /**
fs/gfs2/main.c +0 -1
Index: linux-2.6/fs/gfs2/main.c
===================================================================
@@ -27,7 +27,6 @@
 
 static struct shrinker qd_shrinker = {
 	.shrink = gfs2_shrink_qd_memory,
-	.seeks = DEFAULT_SEEKS,
 };
 
 static void gfs2_init_inode_once(void *foo)
fs/gfs2/quota.c +17 -7
Index: linux-2.6/fs/gfs2/quota.c
===================================================================
@@ -43,6 +43,7 @@
 #include <linux/buffer_head.h>
 #include <linux/sort.h>
 #include <linux/fs.h>
+#include <linux/mm.h>
 #include <linux/bio.h>
 #include <linux/gfs2_ondisk.h>
 #include <linux/kthread.h>
@@ -77,16 +78,25 @@ static LIST_HEAD(qd_lru_list);
 static atomic_t qd_lru_count = ATOMIC_INIT(0);
 static DEFINE_SPINLOCK(qd_lru_lock);
 
-int gfs2_shrink_qd_memory(int nr, gfp_t gfp_mask)
+int gfs2_shrink_qd_memory(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
+	unsigned long nr;
 	struct gfs2_quota_data *qd;
 	struct gfs2_sbd *sdp;
 
-	if (nr == 0)
-		goto out;
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			atomic_read(&qd_lru_count),
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
 
 	if (!(gfp_mask & __GFP_FS))
-		return -1;
+		return 0;
+
+done:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr)
+		return 0;
 
 	spin_lock(&qd_lru_lock);
 	while (nr && !list_empty(&qd_lru_list)) {
@@ -113,9 +123,9 @@ int gfs2_shrink_qd_memory(int nr, gfp_t
 		nr--;
 	}
 	spin_unlock(&qd_lru_lock);
-
-out:
-	return (atomic_read(&qd_lru_count) * sysctl_vfs_cache_pressure) / 100;
+	if (!nr)
+		goto done;
+	return 0;
 }
 
 static u64 qd2offset(struct gfs2_quota_data *qd)
fs/gfs2/quota.h +2 -1
Index: linux-2.6/fs/gfs2/quota.h
===================================================================
@@ -51,7 +51,8 @@ static inline int gfs2_quota_lock_check(
 	return ret;
 }
 
-extern int gfs2_shrink_qd_memory(int nr, gfp_t gfp_mask);
+extern int gfs2_shrink_qd_memory(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask);
 extern const struct quotactl_ops gfs2_quotactl_ops;
 
 #endif /* __QUOTA_DOT_H__ */
fs/nfs/super.c +0 -1
Index: linux-2.6/fs/nfs/super.c
===================================================================
@@ -350,7 +350,6 @@ static const struct super_operations nfs
 
 static struct shrinker acl_shrinker = {
 	.shrink		= nfs_access_cache_shrinker,
-	.seeks		= DEFAULT_SEEKS,
 };
 
 /*
fs/ubifs/shrinker.c +18 -11
Index: linux-2.6/fs/ubifs/shrinker.c
===================================================================
@@ -277,13 +277,16 @@ static int kick_a_thread(void)
 	return 0;
 }
 
-int ubifs_shrinker(int nr, gfp_t gfp_mask)
+int ubifs_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
+	unsigned long nr;
 	int freed, contention = 0;
 	long clean_zn_cnt = atomic_long_read(&ubifs_clean_zn_cnt);
 
-	if (nr == 0)
-		return clean_zn_cnt;
+	shrinker_add_scan(&nr_to_scan, scanned, global, clean_zn_cnt,
+			DEFAULT_SEEKS);
 
 	if (!clean_zn_cnt) {
 		/*
@@ -297,24 +300,28 @@ int ubifs_shrinker(int nr, gfp_t gfp_mas
 		return kick_a_thread();
 	}
 
+done:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr)
+		return 0;
+
 	freed = shrink_tnc_trees(nr, OLD_ZNODE_AGE, &contention);
 	if (freed >= nr)
-		goto out;
+		goto done;
 
 	dbg_tnc("not enough old znodes, try to free young ones");
 	freed += shrink_tnc_trees(nr - freed, YOUNG_ZNODE_AGE, &contention);
 	if (freed >= nr)
-		goto out;
+		goto done;
 
 	dbg_tnc("not enough young znodes, free all");
 	freed += shrink_tnc_trees(nr - freed, 0, &contention);
+	if (freed >= nr)
+		goto done;
 
-	if (!freed && contention) {
-		dbg_tnc("freed nothing, but contention");
-		return -1;
-	}
+	if (!freed && contention)
+		nr_to_scan += nr;
 
-out:
-	dbg_tnc("%d znodes were freed, requested %d", freed, nr);
+	dbg_tnc("%d znodes were freed, requested %lu", freed, nr);
 	return freed;
 }
fs/ubifs/super.c +0 -1
Index: linux-2.6/fs/ubifs/super.c
===================================================================
@@ -50,7 +50,6 @@ struct kmem_cache *ubifs_inode_slab;
 /* UBIFS TNC shrinker description */
 static struct shrinker ubifs_shrinker_info = {
 	.shrink = ubifs_shrinker,
-	.seeks = DEFAULT_SEEKS,
 };
 
 /**
fs/ubifs/ubifs.h +2 -1
Index: linux-2.6/fs/ubifs/ubifs.h
===================================================================
@@ -1575,7 +1575,8 @@ int ubifs_tnc_start_commit(struct ubifs_
 int ubifs_tnc_end_commit(struct ubifs_info *c);
 
 /* shrinker.c */
-int ubifs_shrinker(int nr_to_scan, gfp_t gfp_mask);
+int ubifs_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask);
 
 /* commit.c */
 int ubifs_bg_thread(void *info);
fs/xfs/linux-2.6/xfs_buf.c +8 -5
Index: linux-2.6/fs/xfs/linux-2.6/xfs_buf.c
===================================================================
@@ -45,11 +45,11 @@
 
 static kmem_zone_t *xfs_buf_zone;
 STATIC int xfsbufd(void *);
-STATIC int xfsbufd_wakeup(int, gfp_t);
+STATIC int xfsbufd_wakeup(struct zone *,
+		unsigned long, unsigned long, unsigned long, gfp_t);
 STATIC void xfs_buf_delwri_queue(xfs_buf_t *, int);
 static struct shrinker xfs_buf_shake = {
 	.shrink = xfsbufd_wakeup,
-	.seeks = DEFAULT_SEEKS,
 };
 
 static struct workqueue_struct *xfslogd_workqueue;
@@ -340,7 +340,7 @@ _xfs_buf_lookup_pages(
 					__func__, gfp_mask);
 
 			XFS_STATS_INC(xb_page_retries);
-			xfsbufd_wakeup(0, gfp_mask);
+			xfsbufd_wakeup(NULL, 0, 0, 0, gfp_mask);
 			congestion_wait(BLK_RW_ASYNC, HZ/50);
 			goto retry;
 		}
@@ -1762,8 +1762,11 @@ xfs_buf_runall_queues(
 
 STATIC int
 xfsbufd_wakeup(
-	int			priority,
-	gfp_t			mask)
+	struct zone		*zone,
+	unsigned long		scanned,
+	unsigned long		total,
+	unsigned long		global,
+	gfp_t			gfp_mask)
 {
 	xfs_buftarg_t		*btp;
 
fs/xfs/linux-2.6/xfs_sync.c +28 -19

71 lines not on this page · raw message

fs/xfs/quota/xfs_qm.c +11 -4
Index: linux-2.6/fs/xfs/quota/xfs_qm.c
===================================================================
@@ -69,11 +69,11 @@ STATIC void	xfs_qm_list_destroy(xfs_dqli
 
 STATIC int	xfs_qm_init_quotainos(xfs_mount_t *);
 STATIC int	xfs_qm_init_quotainfo(xfs_mount_t *);
-STATIC int	xfs_qm_shake(int, gfp_t);
+STATIC int	xfs_qm_shake(struct zone *, unsigned long, unsigned long,
+			unsigned long, gfp_t);
 
 static struct shrinker xfs_qm_shaker = {
 	.shrink = xfs_qm_shake,
-	.seeks = DEFAULT_SEEKS,
 };
 
 #ifdef DEBUG
@@ -2119,7 +2119,12 @@ xfs_qm_shake_freelist(
  */
 /* ARGSUSED */
 STATIC int
-xfs_qm_shake(int nr_to_scan, gfp_t gfp_mask)
+xfs_qm_shake(
+	struct zone	*zone,
+	unsigned long	scanned,
+	unsigned long	total,
+	unsigned long	global,
+	gfp_t		gfp_mask)
 {
 	int	ndqused, nfree, n;
 
@@ -2140,7 +2145,9 @@ xfs_qm_shake(int nr_to_scan, gfp_t gfp_m
 	ndqused *= xfs_Gqm->qm_dqfree_ratio;	/* target # of free dquots */
 	n = nfree - ndqused - ndquot;		/* # over target */
 
-	return xfs_qm_shake_freelist(MAX(nfree, n));
+	xfs_qm_shake_freelist(MAX(nfree, n));
+
+	return 0;
 }
 
 
net/sunrpc/auth.c +18 -10
Index: linux-2.6/net/sunrpc/auth.c
===================================================================
@@ -227,8 +227,8 @@ EXPORT_SYMBOL_GPL(rpcauth_destroy_credca
 /*
  * Remove stale credentials. Avoid sleeping inside the loop.
  */
-static int
-rpcauth_prune_expired(struct list_head *free, int nr_to_scan)
+static void
+rpcauth_prune_expired(struct list_head *free, unsigned long nr_to_scan)
 {
 	spinlock_t *cache_lock;
 	struct rpc_cred *cred, *next;
@@ -244,7 +244,7 @@ rpcauth_prune_expired(struct list_head *
 		 */
 		if (time_in_range(cred->cr_expire, expired, jiffies) &&
 		    test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) != 0)
-			return 0;
+			break;
 
 		list_del_init(&cred->cr_lru);
 		number_cred_unused--;
@@ -260,27 +260,36 @@ rpcauth_prune_expired(struct list_head *
 		}
 		spin_unlock(cache_lock);
 	}
-	return (number_cred_unused / 100) * sysctl_vfs_cache_pressure;
 }
 
 /*
  * Run memory cache shrinker.
  */
 static int
-rpcauth_cache_shrinker(int nr_to_scan, gfp_t gfp_mask)
+rpcauth_cache_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
+	unsigned long nr;
 	LIST_HEAD(free);
-	int res;
 
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			number_cred_unused,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
 	if ((gfp_mask & GFP_KERNEL) != GFP_KERNEL)
-		return (nr_to_scan == 0) ? 0 : -1;
+		return 0;
+again:
 	if (list_empty(&cred_unused))
 		return 0;
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr)
+       		return 0;
 	spin_lock(&rpc_credcache_lock);
-	res = rpcauth_prune_expired(&free, nr_to_scan);
+	rpcauth_prune_expired(&free, nr);
 	spin_unlock(&rpc_credcache_lock);
 	rpcauth_destroy_credlist(&free);
-	return res;
+	cond_resched();
+	goto again;
 }
 
 /*
@@ -584,7 +593,6 @@ rpcauth_uptodatecred(struct rpc_task *ta
 
 static struct shrinker rpc_cred_shrinker = {
 	.shrink = rpcauth_cache_shrinker,
-	.seeks = DEFAULT_SEEKS,
 };
 
 void __init rpcauth_init_module(void)

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#123 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

Quoted 3 lines · [email protected] writes:

Allow the shrinker to do per-zone shrinking. This means it is called for each zone scanned. The shrinker is now completely responsible for calculating and batching (given helpers), which provides better flexibility.

Beyond the scope of this patch, but at some point this probably needs to be even more fine grained. With large number of cores/threads in each socket a "zone" is actually shared by quite a large number of CPUs now and this can cause problems.

mm/vmscan.c void unregister_shrinker(struct shrinker · line 188

Quoted 9 lines
mm/vmscan.c +9 -0
+void shrinker_add_scan(unsigned long *dst,
+			unsigned long scanned, unsigned long total,
+			unsigned long objects, unsigned int ratio)
+{
+	unsigned long long delta;
+
+	delta = (unsigned long long)scanned * objects * ratio;
+	do_div(delta, total + 1);
+	delta /= (128ULL / 4ULL);

Again I object to the magic numbers ...

mm/vmscan.c unsigned long shrink_slab(unsigned long · line 260

Quoted 3 lines
mm/vmscan.c +3 -0
+		nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
+	if (nr >= 10)
+		goto again;

And here.

Overall it seems good, but I have not read all the shrinker callback changes in all subsystems.
-Andi

Signature · 7 lines
[email protected] -- Speaking for myself only.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#124 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

Quoted 10 lines · Andi Kleen
Quoted 3 lines · [email protected] writes:

Allow the shrinker to do per-zone shrinking. This means it is called for each zone scanned. The shrinker is now completely responsible for calculating and batching (given helpers), which provides better flexibility.

Beyond the scope of this patch, but at some point this probably needs to be even more fine grained. With large number of cores/threads in each socket a "zone" is actually shared by quite a large number of CPUs now and this can cause problems.

Yes, possibly. At least it is a much better step than the big dumb global list.

Quoted 12 lines

mm/vmscan.c void unregister_shrinker(struct shrinker · line 188

Quoted 9 lines
mm/vmscan.c +9 -0
+void shrinker_add_scan(unsigned long *dst,
+			unsigned long scanned, unsigned long total,
+			unsigned long objects, unsigned int ratio)
+{
+	unsigned long long delta;
+
+	delta = (unsigned long long)scanned * objects * ratio;
+	do_div(delta, total + 1);
+	delta /= (128ULL / 4ULL);

Again I object to the magic numbers ...

Quoted 5 lines

mm/vmscan.c unsigned long shrink_slab(unsigned long · line 260

Quoted 3 lines
mm/vmscan.c +3 -0
+		nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
+	if (nr >= 10)
+		goto again;

And here.

I don't like them either -- problem is they were inherited from the old code (actually 128 is the fixed point scale, I do have a define for it just forgot to use it).

I don't know where 4 came from. And 10 is just a random number someone picked out of a hat :P

Quoted 2 lines

Overall it seems good, but I have not read all the shrinker callback changes in all subsystems.

Thanks for looking over it Andi.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#125 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

Quoted 4 lines
Quoted 2 lines

Overall it seems good, but I have not read all the shrinker callback changes in all subsystems.

Thanks for looking over it Andi.

FWIW i skimmed over most of the patches and nothing stood out that I really disliked. But I have gone over the code in very deep detail.

-Andi

Signature · 7 lines
[email protected] -- Speaking for myself only.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#126 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

Quoted 7 lines · Andi Kleen
Quoted 4 lines
Quoted 2 lines

Overall it seems good, but I have not read all the shrinker callback changes in all subsystems.

Thanks for looking over it Andi.

FWIW i skimmed over most of the patches and nothing stood out that I really disliked. But I have gone over the code in very deep detail.

haven't

-Andi

Signature · 7 lines
[email protected] -- Speaking for myself only.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#127 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

In reply to Andi Kleen
Quoted 7 lines · Andi Kleen
Quoted 4 lines
Quoted 2 lines

Overall it seems good, but I have not read all the shrinker callback changes in all subsystems.

Thanks for looking over it Andi.

FWIW i skimmed over most of the patches and nothing stood out that I really disliked. But I have gone over the code in very deep detail.

s/have/haven't/

-Andi

Signature · 7 lines
[email protected] -- Speaking for myself only.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#128 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

In reply to [email protected]
Quoted 11 lines · On Thu, Jun 24, 2010 at 01:03:02PM +1000, [email protected] wrote:

Allow the shrinker to do per-zone shrinking. This means it is called for each zone scanned. The shrinker is now completely responsible for calculating and batching (given helpers), which provides better flexibility.

Finding the ratio of objects to scan requires scaling the ratio of pagecache objects scanned. By passing down both the per-zone and the global reclaimable pages, per-zone caches and global caches can be calculated correctly.

Finally, add some fixed-point scaling to the ratio, which helps calculations.

Quoted 11 lines
---
 fs/dcache.c        |    2 
 fs/drop_caches.c   |    2 
 fs/inode.c         |    2 
 fs/mbcache.c       |    4 -
 fs/nfs/dir.c       |    2 
 fs/nfs/internal.h  |    2 
 fs/quota/dquot.c   |    2 
 include/linux/mm.h |    6 +-
 mm/vmscan.c        |  131 ++++++++++++++---------------------------------------
 9 files changed, 47 insertions(+), 106 deletions(-)

The diffstat doesn't match the patch ;)

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1003

Quoted 13 lines

Index: linux-2.6/include/linux/mm.h
===================================================================

include/linux/mm.h +2 -3
@@ -999,16 +999,19 @@ static inline void sync_mm_rss(struct ta
  * querying the cache size, so a fastpath for that case is appropriate.
  */
 struct shrinker {
-	int (*shrink)(int nr_to_scan, gfp_t gfp_mask);
-	int seeks;	/* seeks to recreate an obj */
-
+	int (*shrink)(struct zone *zone, unsigned long scanned, unsigned long total,
+					unsigned long global, gfp_t gfp_mask);

Can we add the shrinker structure to taht callback, too, so that we can get away from needing global context for the shrinker?

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1006

Quoted 3 lines
include/linux/mm.h +3 -0
+unsigned long shrinker_do_scan(unsigned long *dst, unsigned long batch)
+{
+	unsigned long nr = ACCESS_ONCE(*dst);

What's the point of ACCESS_ONCE() here?

/me gets most of the way into the patch

Oh, it's because you are using static variables for nr_to_scan and hence when concurrent shrinkers are running they are all incrementing and decrementing the same variable. That doesn't sound like a good idea to me - concurrent shrinkers are much more likely with per-zone shrinker callouts. It seems to me that a reclaim thread could be kept in a shrinker long after it has run it's scan count if new shrinker calls from a different reclaim context occur before the first has finished....

As a further question - why do some shrinkerѕ get converted to a single global nr_to_scan, and others get converted to a private nr_to_scan? Shouldn't they all use the same method? The static variable method looks to me to be full of races - concurrent callers to shrinker_add_scan() does not look at all thread safe to me.

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1008

Quoted 2 lines
include/linux/mm.h +2 -0
+	if (nr < batch)
+		return 0;

Why wouldn't we return nr here to drain the remaining objects? Doesn't this mean we can't shrink caches that have a scan count of less than SHRINK_BATCH?

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1009

Quoted 1 line
include/linux/mm.h +1 -0
+	*dst = nr - batch;

Similarly, that is not a threadsafe update.

Quoted 53 lines
include/linux/mm.h +6 -33
+	return batch;
+}
+EXPORT_SYMBOL(shrinker_do_scan);
+
 /*
  * Call the shrink functions to age shrinkable caches
  *
@@ -198,8 +228,8 @@ EXPORT_SYMBOL(unregister_shrinker);
  *
  * Returns the number of slab objects which we shrunk.
  */
-unsigned long shrink_slab(unsigned long scanned, gfp_t gfp_mask,
-			unsigned long lru_pages)
+static unsigned long shrink_slab(struct zone *zone, unsigned long scanned, unsigned long total,
+			unsigned long global, gfp_t gfp_mask)
 {
 	struct shrinker *shrinker;
 	unsigned long ret = 0;
@@ -211,55 +241,25 @@ unsigned long shrink_slab(unsigned long
 		return 1;	/* Assume we'll be able to shrink next time */
 
 	list_for_each_entry(shrinker, &shrinker_list, list) {
-		unsigned long long delta;
-		unsigned long total_scan;
-		unsigned long max_pass = (*shrinker->shrink)(0, gfp_mask);
-
-		delta = (4 * scanned) / shrinker->seeks;
-		delta *= max_pass;
-		do_div(delta, lru_pages + 1);
-		shrinker->nr += delta;
-		if (shrinker->nr < 0) {
-			printk(KERN_ERR "shrink_slab: %pF negative objects to "
-			       "delete nr=%ld\n",
-			       shrinker->shrink, shrinker->nr);
-			shrinker->nr = max_pass;
-		}
-
-		/*
-		 * Avoid risking looping forever due to too large nr value:
-		 * never try to free more than twice the estimate number of
-		 * freeable entries.
-		 */
-		if (shrinker->nr > max_pass * 2)
-			shrinker->nr = max_pass * 2;
-
-		total_scan = shrinker->nr;
-		shrinker->nr = 0;
-
-		while (total_scan >= SHRINK_BATCH) {
-			long this_scan = SHRINK_BATCH;
-			int shrink_ret;
-			int nr_before;
-

include/linux/mm.h unsigned long shrink_slab(unsigned long · line 254

Quoted 10 lines
include/linux/mm.h +0 -10
-			nr_before = (*shrinker->shrink)(0, gfp_mask);
-			shrink_ret = (*shrinker->shrink)(this_scan, gfp_mask);
-			if (shrink_ret == -1)
-				break;
-			if (shrink_ret < nr_before)
-				ret += nr_before - shrink_ret;
-			count_vm_events(SLABS_SCANNED, this_scan);
-			total_scan -= this_scan;
-
-			cond_resched();

Removing this means we need cond_resched() in all shrinker loops now to maintain the same latencies as we currently have. I note that you've done this for most of the shrinkers, but the documentation needs to be updated to mention this...

include/linux/mm.h unsigned long shrink_slab(unsigned long · line 257

Quoted 3 lines
include/linux/mm.h +0 -3
-		}
-
-		shrinker->nr += total_scan;

And dropping this means we do not carry over the remainder of the previous scan into the next scan. This means we could be scanning a lot less with this new code.

Quoted 7 lines
include/linux/mm.h +2 -0
+		(*shrinker->shrink)(zone, scanned, total, global, gfp_mask);
 	}
 	up_read(&shrinker_rwsem);
 	return ret;
 }
 
+void shrink_all_slab(void)

include/linux/mm.h unsigned long shrink_slab(unsigned long · line 260

Quoted 10 lines
include/linux/mm.h +10 -0
+{
+	struct zone *zone;
+	unsigned long nr;
+
+again:
+	nr = 0;
+	for_each_zone(zone)
+		nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
+	if (nr >= 10)
+		goto again;
	do {
		nr = 0;
		for_each_zone(zone)
			nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
	} while (nr >= 10);
Quoted 8 lines
include/linux/mm.h +4 -0
@@ -1705,6 +1708,23 @@ static void shrink_zone(int priority, st
 	if (inactive_anon_is_low(zone, sc) && nr_swap_pages > 0)
 		shrink_active_list(SWAP_CLUSTER_MAX, zone, sc, priority, 0);
 
+	/*
+	 * Don't shrink slabs when reclaiming memory from
+	 * over limit cgroups
+	 */

include/linux/mm.h static void shrink_zone(int priority, st · line 1724

Quoted 10 lines
include/linux/mm.h +10 -0
+	if (scanning_global_lru(sc)) {
+		struct reclaim_state *reclaim_state = current->reclaim_state;
+
+		shrink_slab(zone, sc->nr_scanned - nr_scanned,
+			lru_pages, global_lru_pages, sc->gfp_mask);
+		if (reclaim_state) {
+			nr_reclaimed += reclaim_state->reclaimed_slab;
+			reclaim_state->reclaimed_slab = 0;
+		}
+	}

So effectively we are going to be calling shrink_slab() once per zone instead of once per priority loop, right? That means we are going to be doing a lot more concurrent shrink_slab() calls that the current code. Combine that with the removal of residual aggregation, I think this will alter the reclaim balance somewhat. Have you tried to quantify this?

Quoted 55 lines

Index: linux-2.6/fs/dcache.c
===================================================================

fs/dcache.c +18 -7
@@ -748,20 +748,26 @@ again2:
  *
  * This function may fail to free any resources if all the dentries are in use.
  */
-static void prune_dcache(int count)
+static void prune_dcache(struct zone *zone, unsigned long scanned,
+			unsigned long total, gfp_t gfp_mask)
+
 {
+	unsigned long nr_to_scan;
 	struct super_block *sb, *n;
 	int w_count;
-	int unused = dentry_stat.nr_unused;
 	int prune_ratio;
-	int pruned;
+	int count, pruned;
 
-	if (unused == 0 || count == 0)
+	shrinker_add_scan(&nr_to_scan, scanned, total, dentry_stat.nr_unused,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+done:
+	count = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (dentry_stat.nr_unused == 0 || count == 0)
 		return;
-	if (count >= unused)
+	if (count >= dentry_stat.nr_unused)
 		prune_ratio = 1;
 	else
-		prune_ratio = unused / count;
+		prune_ratio = dentry_stat.nr_unused / count;
 	spin_lock(&sb_lock);
 	list_for_each_entry_safe(sb, n, &super_blocks, s_list) {
 		if (list_empty(&sb->s_instances))
@@ -810,6 +816,10 @@ static void prune_dcache(int count)
 			break;
 	}
 	spin_unlock(&sb_lock);
+	if (count <= 0) {
+		cond_resched();
+		goto done;
+	}
 }
 
 /**
@@ -1176,19 +1186,15 @@ EXPORT_SYMBOL(shrink_dcache_parent);
  *
  * In this case we return -1 to tell the caller that we baled.
  */
-static int shrink_dcache_memory(int nr, gfp_t gfp_mask)
+static int shrink_dcache_memory(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)

fs/dcache.c EXPORT_SYMBOL(shrink_dcache_parent); · line 1194

Quoted 10 lines
fs/dcache.c +2 -6
 {
-	if (nr) {
-		if (!(gfp_mask & __GFP_FS))
-			return -1;
-		prune_dcache(nr);
-	}
-	return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
+	prune_dcache(zone, scanned, global, gfp_mask);
+	return 0;
 }

I would have thought that putting the shrinker_add_scan/ shrinker_do_scan loop in shrink_dcache_memory() and leaving prune_dcache untouched would have been a better separation. I note that this is what you did with prune_icache(), so consistency between the two would be good ;)

Also, this patch drops the __GFP_FS check from the dcache shrinker - not intentional, right?

Quoted 28 lines
fs/dcache.c +11 -3
@@ -211,28 +215,38 @@ mb_cache_shrink_fn(int nr_to_scan, gfp_t
 			  atomic_read(&cache->c_entry_count));
 		count += atomic_read(&cache->c_entry_count);
 	}
+	shrinker_add_scan(&nr_to_scan, scanned, global, count,
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
 	mb_debug("trying to free %d entries", nr_to_scan);
-	if (nr_to_scan == 0) {
+
+again:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr) {
 		spin_unlock(&mb_cache_spinlock);
-		goto out;
+		return 0;
 	}
-	while (nr_to_scan-- && !list_empty(&mb_cache_lru_list)) {
+	while (!list_empty(&mb_cache_lru_list)) {
 		struct mb_cache_entry *ce =
 			list_entry(mb_cache_lru_list.next,
 				   struct mb_cache_entry, e_lru_list);
 		list_move_tail(&ce->e_lru_list, &free_list);
 		__mb_cache_entry_unhash(ce);
+		cond_resched_lock(&mb_cache_spinlock);
+		if (!--nr)
+			break;
 	}
 	spin_unlock(&mb_cache_spinlock);

fs/dcache.c mb_cache_shrink_fn(int nr_to_scan, gfp_t · line 246

Quoted 10 lines
fs/dcache.c +4 -2
 	list_for_each_safe(l, ltmp, &free_list) {
 		__mb_cache_entry_forget(list_entry(l, struct mb_cache_entry,
 						   e_lru_list), gfp_mask);
 	}
-out:
-	return (count / 100) * sysctl_vfs_cache_pressure;
+	if (!nr) {
+		spin_lock(&mb_cache_spinlock);
+		goto again;
+	}

Another candidate for a do-while loop.

Quoted 29 lines
fs/dcache.c +1 -1
+	return 0;
 }
 
-
 /*
  * mb_cache_create()  create a new cache
  *

Index: linux-2.6/fs/nfs/dir.c
===================================================================

fs/nfs/dir.c +4 -5
@@ -1709,21 +1709,31 @@ static void nfs_access_free_list(struct
 	}
 }
 
-int nfs_access_cache_shrinker(int nr_to_scan, gfp_t gfp_mask)
+int nfs_access_cache_shrinker(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
 	LIST_HEAD(head);
-	struct nfs_inode *nfsi;
 	struct nfs_access_entry *cache;
-
-	if ((gfp_mask & GFP_KERNEL) != GFP_KERNEL)
-		return (nr_to_scan == 0) ? 0 : -1;
+	unsigned long nr;
 
 	spin_lock(&nfs_access_lru_lock);

fs/nfs/dir.c static void nfs_access_free_list(struct · line 1729

Quoted 10 lines
fs/nfs/dir.c +9 -1
-	list_for_each_entry(nfsi, &nfs_access_lru_list, access_cache_inode_lru) {
+	shrinker_add_scan(&nr_to_scan, scanned, global,
+			atomic_long_read(&nfs_access_nr_entries),
+			DEFAULT_SEEKS * sysctl_vfs_cache_pressure / 100);
+	if (!(gfp_mask & __GFP_FS) || nr_to_scan < SHRINK_BATCH) {
+		spin_unlock(&nfs_access_lru_lock);
+		return 0;
+	}
+	nr = ACCESS_ONCE(nr_to_scan);
+	nr_to_scan = 0;

That's not safe for concurrent callers. Both could get nr = nr_to_scan rather than nr(1) = nr_to_scan and nr(2) = 0 which I think is the intent....

Quoted 23 lines

Index: linux-2.6/arch/x86/kvm/mmu.c
===================================================================

arch/x86/kvm/mmu.c +8 -2
@@ -2924,14 +2924,29 @@ static int kvm_mmu_remove_some_alloc_mmu
 	return kvm_mmu_zap_page(kvm, page) + 1;
 }
 
-static int mmu_shrink(int nr_to_scan, gfp_t gfp_mask)
+static int mmu_shrink(struct zone *zone, unsigned long scanned,
+                unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
+	static unsigned long nr_to_scan;
 	struct kvm *kvm;
 	struct kvm *kvm_freed = NULL;
-	int cache_count = 0;
+	unsigned long cache_count = 0;
 
 	spin_lock(&kvm_lock);
+	list_for_each_entry(kvm, &vm_list, vm_list) {
+		cache_count += kvm->arch.n_alloc_mmu_pages -
+			 kvm->arch.n_free_mmu_pages;
+	}

arch/x86/kvm/mmu.c static int kvm_mmu_remove_some_alloc_mmu · line 2949

Quoted 10 lines
arch/x86/kvm/mmu.c +9 -0
 
+	shrinker_add_scan(&nr_to_scan, scanned, global, cache_count,
+			DEFAULT_SEEKS*10);
+
+done:
+	cache_count = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!cache_count) {
+		spin_unlock(&kvm_lock);
+		return 0;
+	}

I note that this use of a static scan count is thread safe because all the calculations are done under the kvm_lock. THat's three different ways the shrinkers implement the same functionality now....

Quoted 38 lines

Index: linux-2.6/fs/xfs/linux-2.6/xfs_sync.c
===================================================================

fs/xfs/linux-2.6/xfs_sync.c +7 -16
@@ -838,43 +838,52 @@ static struct rw_semaphore xfs_mount_lis
 
 static int
 xfs_reclaim_inode_shrink(
-	int		nr_to_scan,
+	struct zone	*zone,
+	unsigned long	scanned,
+	unsigned long	total,
+	unsigned long	global,
 	gfp_t		gfp_mask)
 {
+	static unsigned long nr_to_scan;
+	int		nr;
 	struct xfs_mount *mp;
 	struct xfs_perag *pag;
 	xfs_agnumber_t	ag;
-	int		reclaimable = 0;
-
-	if (nr_to_scan) {
-		if (!(gfp_mask & __GFP_FS))
-			return -1;
-
-		down_read(&xfs_mount_list_lock);
-		list_for_each_entry(mp, &xfs_mount_list, m_mplist) {
-			xfs_inode_ag_iterator(mp, xfs_reclaim_inode, 0,
-					XFS_ICI_RECLAIM_TAG, 1, &nr_to_scan);
-			if (nr_to_scan <= 0)
-				break;
-		}
-		up_read(&xfs_mount_list_lock);
-	}
+	unsigned long	nr_reclaimable = 0;
 
 	down_read(&xfs_mount_list_lock);

fs/xfs/linux-2.6/xfs_sync.c static struct rw_semaphore xfs_mount_lis · line 863

Quoted 10 lines
fs/xfs/linux-2.6/xfs_sync.c +3 -1
 	list_for_each_entry(mp, &xfs_mount_list, m_mplist) {
 		for (ag = 0; ag < mp->m_sb.sb_agcount; ag++) {
 			pag = xfs_perag_get(mp, ag);
-			reclaimable += pag->pag_ici_reclaimable;
+			nr_reclaimable += pag->pag_ici_reclaimable;
 			xfs_perag_put(pag);
 		}
 	}
+	shrinker_add_scan(&nr_to_scan, scanned, global, nr_reclaimable,
+				DEFAULT_SEEKS);

That's not thread safe - it's under a read lock. This code really needs a shrinker context....

Quoted 7 lines
fs/xfs/linux-2.6/xfs_sync.c +7 -0
+	if (!(gfp_mask & __GFP_FS)) {
+		up_read(&xfs_mount_list_lock);
+		return 0;
+	}
+
+done:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);

fs/xfs/linux-2.6/xfs_sync.c static struct rw_semaphore xfs_mount_lis · line 880

Quoted 10 lines
fs/xfs/linux-2.6/xfs_sync.c +10 -0
+	if (!nr) {
+		up_read(&xfs_mount_list_lock);
+		return 0;
+	}
+	list_for_each_entry(mp, &xfs_mount_list, m_mplist) {
+		xfs_inode_ag_iterator(mp, xfs_reclaim_inode, 0,
+				XFS_ICI_RECLAIM_TAG, 1, &nr);
+		if (nr <= 0)
+			goto done;
+	}

That's missing conditional reschedules....

Cheers,

Dave.

Signature · 8 lines
Dave Chinner
[email protected]

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#129 lore raw

Re: [patch 50/52] mm: implement per-zone shrinker

Wow, some reviewing! Thanks Dave.

Quoted 4 lines · Dave Chinner
Quoted 1 line · On Thu, Jun 24, 2010 at 01:03:02PM +1000, [email protected] wrote:
 9 files changed, 47 insertions(+), 106 deletions(-)

The diffstat doesn't match the patch ;)

Bah, sorry.

Quoted 8 lines
Quoted 8 lines

Index: linux-2.6/include/linux/mm.h
===================================================================

include/linux/mm.h +0 -0
@@ -999,16 +999,19 @@ static inline void sync_mm_rss(struct ta
  * querying the cache size, so a fastpath for that case is appropriate.
  */
 struct shrinker {
Quoted 8 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1003

Quoted 5 lines
include/linux/mm.h +2 -3
-	int (*shrink)(int nr_to_scan, gfp_t gfp_mask);
-	int seeks;	/* seeks to recreate an obj */
-
+	int (*shrink)(struct zone *zone, unsigned long scanned, unsigned long total,
+					unsigned long global, gfp_t gfp_mask);

Can we add the shrinker structure to taht callback, too, so that we can get away from needing global context for the shrinker?

I was planning to merge this on top of your shrinker change (which I like how the locking / refcounting worked out). So I was just going to leave that part for you :)

Quoted 8 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1006

Quoted 3 lines
include/linux/mm.h +3 -0
+unsigned long shrinker_do_scan(unsigned long *dst, unsigned long batch)
+{
+	unsigned long nr = ACCESS_ONCE(*dst);

What's the point of ACCESS_ONCE() here?

/me gets most of the way into the patch

Quoted 8 lines

Oh, it's because you are using static variables for nr_to_scan and hence when concurrent shrinkers are running they are all incrementing and decrementing the same variable. That doesn't sound like a good idea to me - concurrent shrinkers are much more likely with per-zone shrinker callouts. It seems to me that a reclaim thread could be kept in a shrinker long after it has run it's scan count if new shrinker calls from a different reclaim context occur before the first has finished....

I don't think parallelism will be much changed. The existing shrinker didn't provide any serialisation. It likewise did not serialise any updates to shrinker->nr accumulator (reclaim is a crappy heuristic anyway so it apparently doesn't matter too much that it is racy). So a lot of your criticism of racy access to the accumulators isn't really inherent to this patch

(where it's easy, I did put them under locks, but I didn't go out of my way -- a subsequent patch could do that if we really wanted)

Quoted 5 lines

As a further question - why do some shrinkerѕ get converted to a single global nr_to_scan, and others get converted to a private nr_to_scan? Shouldn't they all use the same method? The static variable method looks to me to be full of races - concurrent callers to shrinker_add_scan() does not look at all thread safe to me.

Hmm, they should all have their own nr_to_scan.

Quoted 4 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1008

Quoted 2 lines
include/linux/mm.h +2 -0
+	if (nr < batch)
+		return 0;

Why wouldn't we return nr here to drain the remaining objects?

I was thinking, because it's not worth taking locks for a small number of objects.

Quoted 2 lines

Doesn't this mean we can't shrink caches that have a scan count of less than SHRINK_BATCH?

No, they just accumulate slowly until hitting the batch size.

Quoted 9 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1008

Quoted 4 lines
include/linux/mm.h +0 -4
-			count_vm_events(SLABS_SCANNED, this_scan);
-			total_scan -= this_scan;
-
-			cond_resched();

Removing this means we need cond_resched() in all shrinker loops now to maintain the same latencies as we currently have. I note that you've done this for most of the shrinkers, but the documentation needs to be updated to mention this...

That's true, yes.

Quoted 7 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1011

Quoted 3 lines
include/linux/mm.h +0 -3
-		}
-
-		shrinker->nr += total_scan;

And dropping this means we do not carry over the remainder of the previous scan into the next scan. This means we could be scanning a lot less with this new code.

We do because they accumulate to static variables. It's effectively the same as accumulating to shrinker->nr, but it allows the per-zone patches to change to accumulate to per-zone counters.

Quoted 4 lines
Quoted 4 lines
include/linux/mm.h +4 -0
+again:
+	nr = 0;
+	for_each_zone(zone)
+		nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
Quoted 8 lines

include/linux/mm.h static inline void sync_mm_rss(struct ta · line 1014

Quoted 2 lines
include/linux/mm.h +2 -0
+	if (nr >= 10)
+		goto again;
	do {
		nr = 0;
		for_each_zone(zone)
			nr += shrink_slab(zone, 1, 1, 1, GFP_KERNEL);
	} while (nr >= 10);

OK.

Quoted 13 lines
Quoted 13 lines
include/linux/mm.h +9 -0
@@ -1705,6 +1708,23 @@ static void shrink_zone(int priority, st
 	if (inactive_anon_is_low(zone, sc) && nr_swap_pages > 0)
 		shrink_active_list(SWAP_CLUSTER_MAX, zone, sc, priority, 0);
 
+	/*
+	 * Don't shrink slabs when reclaiming memory from
+	 * over limit cgroups
+	 */
+	if (scanning_global_lru(sc)) {
+		struct reclaim_state *reclaim_state = current->reclaim_state;
+
+		shrink_slab(zone, sc->nr_scanned - nr_scanned,
+			lru_pages, global_lru_pages, sc->gfp_mask);
Quoted 8 lines

include/linux/mm.h static void shrink_zone(int priority, st · line 1724

Quoted 5 lines
include/linux/mm.h +5 -0
+		if (reclaim_state) {
+			nr_reclaimed += reclaim_state->reclaimed_slab;
+			reclaim_state->reclaimed_slab = 0;
+		}
+	}

So effectively we are going to be calling shrink_slab() once per zone instead of once per priority loop, right? That means we are

Yes.

Quoted 4 lines

going to be doing a lot more concurrent shrink_slab() calls that the current code. Combine that with the removal of residual aggregation, I think this will alter the reclaim balance somewhat. Have you tried to quantify this?

It will alter reclaim a bit. I don't think it will change the concurrency too much (per-prio which gets chopped into batch size calls into shrinker versus per-zone call which the shrinker chops up itself).

Basically, the number of items to scan should be about the same, and chopped into the same number of batches. It just depends on exactly when it gets done.

Quoted 11 lines
Quoted 11 lines
include/linux/mm.h +3 -7
-static int shrink_dcache_memory(int nr, gfp_t gfp_mask)
+static int shrink_dcache_memory(struct zone *zone, unsigned long scanned,
+		unsigned long total, unsigned long global, gfp_t gfp_mask)
 {
-	if (nr) {
-		if (!(gfp_mask & __GFP_FS))
-			return -1;
-		prune_dcache(nr);
-	}
-	return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
+	prune_dcache(zone, scanned, global, gfp_mask);
Quoted 8 lines

include/linux/mm.h

Quoted 2 lines
include/linux/mm.h +1 -0
+	return 0;
 }

I would have thought that putting the shrinker_add_scan/ shrinker_do_scan loop in shrink_dcache_memory() and leaving prune_dcache untouched would have been a better separation. I note that this is what you did with prune_icache(), so consistency between the two would be good ;)

You're probably right, I'll go back and take a look.

Quoted 2 lines

Also, this patch drops the __GFP_FS check from the dcache shrinker - not intentional, right?

Right, thanks.

Quoted 23 lines
Quoted 23 lines
include/linux/mm.h +8 -2
+again:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr) {
 		spin_unlock(&mb_cache_spinlock);
-		goto out;
+		return 0;
 	}
-	while (nr_to_scan-- && !list_empty(&mb_cache_lru_list)) {
+	while (!list_empty(&mb_cache_lru_list)) {
 		struct mb_cache_entry *ce =
 			list_entry(mb_cache_lru_list.next,
 				   struct mb_cache_entry, e_lru_list);
 		list_move_tail(&ce->e_lru_list, &free_list);
 		__mb_cache_entry_unhash(ce);
+		cond_resched_lock(&mb_cache_spinlock);
+		if (!--nr)
+			break;
 	}
 	spin_unlock(&mb_cache_spinlock);
 	list_for_each_safe(l, ltmp, &free_list) {
 		__mb_cache_entry_forget(list_entry(l, struct mb_cache_entry,
 						   e_lru_list), gfp_mask);
 	}
Quoted 8 lines

include/linux/mm.h

Quoted 6 lines
include/linux/mm.h +4 -2
-out:
-	return (count / 100) * sysctl_vfs_cache_pressure;
+	if (!nr) {
+		spin_lock(&mb_cache_spinlock);
+		goto again;
+	}

Another candidate for a do-while loop.

Maybe. I prefer not to indent so much (then one would argue to put the body in a seperate function :), but meh)

Quoted 6 lines

include/linux/mm.h

Quoted 2 lines
include/linux/mm.h +2 -0
+	nr = ACCESS_ONCE(nr_to_scan);
+	nr_to_scan = 0;

That's not safe for concurrent callers. Both could get nr = nr_to_scan rather than nr(1) = nr_to_scan and nr(2) = 0 which I think is the intent....

...

Quoted 4 lines

I note that this use of a static scan count is thread safe because all the calculations are done under the kvm_lock. THat's three different ways the shrinkers implement the same functionality now....

...

Quoted 2 lines

That's not thread safe - it's under a read lock. This code really needs a shrinker context....

So as I said above, lost updates are not cared about.

Quoted 11 lines
Quoted 11 lines
include/linux/mm.h +11 -0
+	if (!(gfp_mask & __GFP_FS)) {
+		up_read(&xfs_mount_list_lock);
+		return 0;
+	}
+
+done:
+	nr = shrinker_do_scan(&nr_to_scan, SHRINK_BATCH);
+	if (!nr) {
+		up_read(&xfs_mount_list_lock);
+		return 0;
+	}
Quoted 8 lines

include/linux/mm.h

Quoted 6 lines
include/linux/mm.h +6 -0
+	list_for_each_entry(mp, &xfs_mount_list, m_mplist) {
+		xfs_inode_ag_iterator(mp, xfs_reclaim_inode, 0,
+				XFS_ICI_RECLAIM_TAG, 1, &nr);
+		if (nr <= 0)
+			goto done;
+	}

That's missing conditional reschedules....

Thanks

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#130 lore raw

PATCH 51/52 fs: per-zone dentry and inode LRU

In reply to [email protected]

Per-zone LRUs and shrinkers for dentry and inode caches.

include/linux/fs.h +21 -3
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -382,6 +382,7 @@ struct files_stat_struct {
 #include <linux/semaphore.h>
 #include <linux/fiemap.h>
 #include <linux/rculist_bl.h>
+#include <linux/mmzone.h>
 
 #include <asm/atomic.h>
 #include <asm/byteorder.h>
@@ -1325,6 +1326,25 @@ extern int send_sigurg(struct fown_struc
 extern struct list_head super_blocks;
 extern spinlock_t sb_lock;
 
+#define sb_zone_info(sb, ___z)			\
+	&sb->s_reclaim.node[zone_to_nid(___z)].zone[zone_idx(___z)]
+
+struct sb_zoneinfo {
+	/* protected by zone->dentry_lru_lock */
+	spinlock_t		s_dentry_lru_lock;
+	struct list_head	s_dentry_lru;
+	long			s_nr_dentry_scan;
+	unsigned long		s_nr_dentry_unused;
+};
+
+struct sb_nodeinfo {
+	struct sb_zoneinfo zone[MAX_NR_ZONES];
+};
+
+struct sb_reclaim {
+	struct sb_nodeinfo node[MAX_NUMNODES];
+};
+
 struct super_block {
 	struct list_head	s_list;		/* Keep this first */
 	dev_t			s_dev;		/* search index; _not_ kdev_t */
@@ -1357,9 +1377,7 @@ struct super_block {
 	struct list_head	s_inodes;	/* all inodes */
 	struct list_head	s_files;
 #endif
-	/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
-	struct list_head	s_dentry_lru;	/* unused dentry lru */
-	int			s_nr_dentry_unused;	/* # of dentry on lru */
+	struct sb_reclaim	s_reclaim;
 
 	struct block_device	*s_bdev;
 	struct backing_dev_info *s_bdi;
fs/dcache.c +93 -86

332 lines not on this page · raw message

fs/inode.c +53 -52

231 lines not on this page · raw message

fs/super.c +11 -1
Index: linux-2.6/fs/super.c
===================================================================
@@ -50,6 +50,8 @@ static struct super_block *alloc_super(s
 	static const struct super_operations default_op;
 
 	if (s) {
+		struct zone *zone;
+
 		if (security_sb_alloc(s)) {
 			kfree(s);
 			s = NULL;
@@ -88,7 +90,6 @@ static struct super_block *alloc_super(s
 #endif
 		INIT_LIST_HEAD(&s->s_instances);
 		INIT_HLIST_BL_HEAD(&s->s_anon);
-		INIT_LIST_HEAD(&s->s_dentry_lru);
 		init_rwsem(&s->s_umount);
 		mutex_init(&s->s_lock);
 		lockdep_set_class(&s->s_umount, &type->s_umount_key);
@@ -125,6 +126,15 @@ static struct super_block *alloc_super(s
 		s->s_maxbytes = MAX_NON_LFS;
 		s->s_op = &default_op;
 		s->s_time_gran = 1000000000;
+
+		for_each_zone(zone) {
+			struct sb_zoneinfo *sbz = sb_zone_info(s, zone);
+
+			spin_lock_init(&sbz->s_dentry_lru_lock);
+			INIT_LIST_HEAD(&sbz->s_dentry_lru);
+			sbz->s_nr_dentry_scan = 0;
+			sbz->s_nr_dentry_unused = 0;
+		}
 	}
 out:
 	return s;
include/linux/mmzone.h +7 -0
Index: linux-2.6/include/linux/mmzone.h
===================================================================
@@ -370,6 +370,13 @@ struct zone {
 
 
 	ZONE_PADDING(_pad2_)
+
+	spinlock_t inode_lru_lock;
+	struct list_head inode_lru;
+	unsigned long inode_nr_lru;
+	unsigned long inode_nr_scan;
+
+	ZONE_PADDING(_pad3_)
 	/* Rarely used or read-mostly fields */
 
 	/*
include/linux/mm.h +2 -1
Index: linux-2.6/include/linux/mm.h
===================================================================
@@ -1004,7 +1004,8 @@ struct shrinker {
 	/* These are for internal use */
 	struct list_head list;
 };
-#define DEFAULT_SEEKS	(128UL*2) /* A good number if you don't know better. */
+#define SHRINK_FIXED	(128UL)	/* Fixed point for shrinker ratio */
+#define DEFAULT_SEEKS	(SHRINK_FIXED*2) /* A good number if you don't know better. */
 #define SHRINK_BATCH	128	/* A good number if you don't know better */
 extern void register_shrinker(struct shrinker *);
 extern void unregister_shrinker(struct shrinker *);
#131 lore raw

Re: [patch 51/52] fs: per-zone dentry and inode LRU

Quoted 3 lines · On Thu, Jun 24, 2010 at 01:03:03PM +1000, [email protected] wrote:

Per-zone LRUs and shrinkers for dentry and inode caches.

This destroys any notion we have of global LRU-ness of inode and dentry caches, doesn't it? Can you outline in more detail what sort of reclaim pattern this results in. e.g. is a workload running on a single node now effectively limited to a dentry/icache size within the local node because of local node slab allocation and per-zone reclaim?

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#132 lore raw

Re: [patch 51/52] fs: per-zone dentry and inode LRU

Quoted 7 lines · Dave Chinner
Quoted 3 lines · On Thu, Jun 24, 2010 at 01:03:03PM +1000, [email protected] wrote:

Per-zone LRUs and shrinkers for dentry and inode caches.

This destroys any notion we have of global LRU-ness of inode and dentry caches, doesn't it?

Yes.

Quoted 5 lines

Can you outline in more detail what sort
of reclaim pattern this results in. e.g. is a workload running on a single node now effectively limited to a dentry/icache size within the local node because of local node slab allocation and per-zone reclaim?

No, it will just make the vfs cache reclaim work similarly to pagecache reclaim. Reclaimers (kswapd) can work on node local memory; memory pressure in a particular zone can be targetted specifically instead of shrinking all zones until it is OK; zone reclaim will work properly for vfs caches; partitioning of workloads can be better.

Workload on a single node can certainly still use up all global memory if that is how the policy is configured.

Global LRU ordering is lost, but it is approximated when there is a global memory shortage by scanning a little from each zone head. When there is a local memory shortage it should be superior because that can be resolved without reclaiming so much memory.

Thanks,
Nick

#133 lore raw

PATCH 52/52 fs: icache less I_FREEING time

In reply to [email protected]

Problem with inode reclaim is that it puts inodes into I_FREEING state and then continues to gather more, during which it may iput, invalidate_mapping_pages, be preempted, etc. Holding these inodes in I_FREEING can cause pauses.

After the inode scalability work, there is not a big reason to batch up inodes to reclaim them, so dispose them as they are found from the LRU.

fs/inode.c +20 -14
Index: linux-2.6/fs/inode.c
===================================================================
@@ -390,6 +390,19 @@ EXPORT_SYMBOL(clear_inode);
 
 static void inode_sb_list_del(struct inode *inode);
 
+static void dispose_one_inode(struct inode *inode)
+{
+	clear_inode(inode);
+
+	spin_lock(&inode->i_lock);
+	__remove_inode_hash(inode);
+	inode_sb_list_del(inode);
+	spin_unlock(&inode->i_lock);
+
+	wake_up_inode(inode);
+	destroy_inode(inode);
+}
+
 /*
  * dispose_list - dispose of the contents of a local list
  * @head: the head of the list to free
@@ -409,15 +422,8 @@ static void dispose_list(struct list_hea
 
 		if (inode->i_data.nrpages)
 			truncate_inode_pages(&inode->i_data, 0);
-		clear_inode(inode);
-
-		spin_lock(&inode->i_lock);
-		__remove_inode_hash(inode);
-		inode_sb_list_del(inode);
-		spin_unlock(&inode->i_lock);
-
-		wake_up_inode(inode);
-		destroy_inode(inode);
+		dispose_one_inode(inode);
+		cond_resched();
 		nr_disposed++;
 	}
 }
@@ -526,7 +532,6 @@ EXPORT_SYMBOL(invalidate_inodes);
  */
 static void prune_icache(struct zone *zone, unsigned long nr_to_scan)
 {
-	LIST_HEAD(freeable);
 	unsigned long reap = 0;
 
 	down_read(&iprune_sem);
@@ -563,8 +568,6 @@ again:
 			__iget(inode);
 			spin_unlock(&inode->i_lock);
 
-			dispose_list(&freeable);
-
 			if (remove_inode_buffers(inode))
 				reap += invalidate_mapping_pages(&inode->i_data,
 								0, -1);
@@ -572,11 +575,15 @@ again:
 			spin_lock(&zone->inode_lru_lock);
 			continue;
 		}
-		list_move(&inode->i_lru, &freeable);
+		list_del_init(&inode->i_lru);
 		WARN_ON(inode->i_state & I_NEW);
 		inode->i_state |= I_FREEING;
 		spin_unlock(&inode->i_lock);
 		zone->inode_nr_lru--;
+		spin_unlock(&zone->inode_lru_lock);
+		dispose_one_inode(inode);
+		cond_resched();
+		spin_lock(&zone->inode_lru_lock);
 	}
 	if (current_is_kswapd())
 		__count_vm_events(KSWAPD_INODESTEAL, reap);
@@ -584,7 +591,6 @@ again:
 		__count_vm_events(PGINODESTEAL, reap);
 	spin_unlock(&zone->inode_lru_lock);
 
-	dispose_list(&freeable);
 	up_read(&iprune_sem);
 }
 
#134 lore raw

Re: [patch 52/52] fs: icache less I_FREEING time

Quoted 4 lines · On Thu, Jun 24, 2010 at 01:03:04PM +1000, [email protected] wrote:

Problem with inode reclaim is that it puts inodes into I_FREEING state and then continues to gather more, during which it may iput, invalidate_mapping_pages, be preempted, etc. Holding these inodes in I_FREEING can cause pauses.

What sort of pauses? I can't see how holding a few inodes in I_FREEING state would cause any serious sort of holdoff...

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#135 lore raw

Re: [patch 52/52] fs: icache less I_FREEING time

Quoted 8 lines · Dave Chinner
Quoted 4 lines · On Thu, Jun 24, 2010 at 01:03:04PM +1000, [email protected] wrote:

Problem with inode reclaim is that it puts inodes into I_FREEING state and then continues to gather more, during which it may iput, invalidate_mapping_pages, be preempted, etc. Holding these inodes in I_FREEING can cause pauses.

What sort of pauses? I can't see how holding a few inodes in I_FREEING state would cause any serious sort of holdoff...

Well if the inode is accessed again, it has to wait for potentially hundreds of inodes to be found from the LRU, pagecache invalidated, and destroyed.

#136 lore raw

Re: [patch 52/52] fs: icache less I_FREEING time

Quoted 7 lines · Nick Piggin
Quoted 6 lines · Dave Chinner
Quoted 4 lines · On Thu, Jun 24, 2010 at 01:03:04PM +1000, [email protected] wrote:

Problem with inode reclaim is that it puts inodes into I_FREEING state and then continues to gather more, during which it may iput, invalidate_mapping_pages, be preempted, etc. Holding these inodes in I_FREEING can cause pauses.

Quoted 6 lines
Quoted 2 lines

What sort of pauses? I can't see how holding a few inodes in I_FREEING state would cause any serious sort of holdoff...

Well if the inode is accessed again, it has to wait for potentially hundreds of inodes to be found from the LRU, pagecache invalidated, and destroyed.

So it's a theoretical concern you have, not something that's actually been demonstrated as a problem?

As it is, If the inode is accessed immediately after teardown has started, then we failed to hold on to the inode at a higher level for long enough. Changing the I_FREEING behaviour is trying to address the issue at the wrong level...

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#137 lore raw

Re: [patch 52/52] fs: icache less I_FREEING time

Quoted 11 lines · Dave Chinner
Quoted 10 lines · Nick Piggin
Quoted 8 lines · Dave Chinner
Quoted 4 lines · On Thu, Jun 24, 2010 at 01:03:04PM +1000, [email protected] wrote:

Problem with inode reclaim is that it puts inodes into I_FREEING state and then continues to gather more, during which it may iput, invalidate_mapping_pages, be preempted, etc. Holding these inodes in I_FREEING can cause pauses.

What sort of pauses? I can't see how holding a few inodes in I_FREEING state would cause any serious sort of holdoff...

Quoted 6 lines
Quoted 3 lines

Well if the inode is accessed again, it has to wait for potentially hundreds of inodes to be found from the LRU, pagecache invalidated, and destroyed.

So it's a theoretical concern you have, not something that's actually been demonstrated as a problem?

Yes it causes holdoffs. I actually started visibly seeing the problem badly in my parallel git diff workload as I started experimenting with increasing batch size.

But even smaller batches could have a problem if the wrong inode is hit. I didn't add instrumentation in there, but I just didn't see such a benefit from batching after the lock breakups, so I prefer to go the other way and add back batching and its downsides IF that proves to be an improvement.

Is this of any consequence for the filesystem?

Quoted 3 lines

As it is, If the inode is accessed immediately after teardown has started, then we failed to hold on to the inode at a higher level for long enough.

Or simply didn't anticpate userspace access pattern.

Quoted 2 lines

Changing the I_FREEING behaviour is trying to
address the issue at the wrong level...

Reclaim should of course be as good as possible. But it is inherntly going to have problems. You need to design everything around reclaim thinking about the possibility that it will go wrong. Because it will, in unreproducable ways, on a customer's machine.

#138 lore raw
In reply to [email protected]

If you actuall want to get this work in reposting huge patchkit again and again probably doesn't help. Start to prioritize areas and work on small sets to get them ready.

files_lock and vfsmount_lock seem like rather easy targets to start with. But for files_lock I really want to see something to generalize the tty special case. If you touch that are in detail that wart needs to go. Al didn't seem to like my variant very much, so he might have a better idea for it - otherwise it really makes the VFS locking simple by removing any tty interaction with the superblock files list. The other suggestion would be to only open regular (maybe even just writeable) files to the list. In addition to reducing the number of list operations require it will also make the tty code a lot easier.

As for the other patches: I don't think the massive fine-grained locking in the hash tables is a good idea. I would recommend to defer them for now, and then look into better data structures for these caches instead of working around the inherent problems of global hash tables.

#139 lore raw
Quoted 3 lines · Christoph Hellwig

If you actuall want to get this work in reposting huge patchkit again and again probably doesn't help. Start to prioritize areas and work on small sets to get them ready.

Sure, I haven't been posting the same thing (haven't posted it for a long time). This simply had a lot of new stuff and improvements to all existing patches.

I didn't cc anyone in particular because it's only for interested people to take a look at. As you saw last time I cc'ed Al I exactly was just trying to get those easier targets merged.

Quoted 6 lines

files_lock and vfsmount_lock seem like rather easy targets to start with. But for files_lock I really want to see something to generalize the tty special case. If you touch that are in detail that wart needs to go. Al didn't seem to like my variant very much, so he might have a better idea for it - otherwise it really makes the VFS locking simple by removing any tty interaction with the superblock files list.

Actually I didn't like it because the error handling in the tty code was broken and difficult to fix properly. The concept was OK though.

But the fact is that today already tty "knows" that vfs doesn't need its files on the superblock list, and so it may take them off and use that list_head privately. Currently it is also using files lock to protect that private usage. These are two independent problems. My patch fixes the second, and anything that fixes the first also needs to fix the second in exactly the same way.

Quoted 4 lines

The
other suggestion would be to only open regular (maybe even just writeable) files to the list. In addition to reducing the number of list operations require it will also make the tty code a lot easier.

This was my suggestion, yes. Either way is conceptually the same, this one just avoids the memory allocation and error handling problems that yours had.

But again, locking change is still required and it would look exactly the same as my patch really.

Quoted 4 lines

As for the other patches: I don't think the massive fine-grained locking in the hash tables is a good idea. I would recommend to defer them for now, and then look into better data structures for these caches instead of working around the inherent problems of global hash tables.

I don't agree actually. I don't think there is any downside to fine grained locking the hash with bit spinlocks. Until I see one, I will keep them.

I agree that some other data structure may be better, but it should be compared with the best possible hash implementation, which is a scalable hash like this one.

Also, our big impending performance problem is SMP scalability, not hash lookup, AFAIKS.

#140 lore raw
In reply to [email protected]
Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:12PM +1000, [email protected] wrote:

http://www.kernel.org/pub/linux/kernel/people/npiggin/patches/fs-scale/

Can you put a git tree up somewhere?

Quoted 1 line

Update to vfs scalability patches:

....

Now that I've had a look at the whole series, I'll make an overall
comment: I suspect that the locking is sufficiently complex that we can count the number of people that will be able to debug it on one hand. This patch set didn't just fall off the locking cliff, it fell into a bottomless pit...

Quoted 6 lines

Performance:
Last time I was testing on a 32-node Altix which could be considered as not a sweet-spot for Linux performance target (ie. improvements there may not justify complexity). So recently I've been testing with a tightly interconnected 4-socket Nehalem (4s/32c/64t). Linux needs to perform well on this size of system.

Sure, but I have to question how much of this is actually necessary? A lot of it looks like scalability for scalabilities sake, not because there is a demonstrated need...

Quoted 7 lines

*** Single-thread microbenchmark (simple syscall loops, lower is better):

Test                    Difference at 95.0% confidence (50 runs)
open/close              -6.07% +/- 1.075%
creat/unlink            27.83% +/- 0.522%

Open/close is a little faster, which should be due to one less atomic in the dput common case. Creat/unlink is significantly slower, which is due to RCU freeing inodes.

That's a pretty big ouch. Why does RCU freeing of inodes cause that much regression? The RCU freeing is out of line, so where does the big impact come from?

Quoted 8 lines

*** 64 parallel git diff on 64 kernel trees fully cached (avg of 5 runs):

                vanilla         vfs
real            0m4.911s        0m0.183s
user            0m1.920s        0m1.610s
sys             4m58.670s       0m5.770s

After vfs patches, 26x increase in throughput, however parallelism is limited by test spawning and exit phases. sys time improvement shows closer to 50x improvement. vanilla is bottlenecked on dcache_lock.

So if we cherry pick patches out of the series, what is the bare minimum set needed to obtain a result in this ballpark? Same for the other tests?

Quoted 6 lines

*** Reclaim
I have not done much reclaim testing yet. It should be more scalable and lower latency due to significant reduction in lru locks interfering with other critical sections in inode/dentry code, and because we have per-zone locks. Per-zone LRUs mean that reclaim is targetted to the correct zone, and that kswapd will operate on lists of node-local memory objects.

This means we no longer have any global LRUness to inode or dentry reclaim, which is going to significantly change caching behaviour. It's also got interesting corner cases like a workload running on a single node with a dentry/icache working set larger than the VM wants to hold on a single node.

We went through these sorts of problems with cpusets a few years back, and the workaround for it was not to limit the slab cache to the cpuset's nodes. Handling this sort of problem correctly seems distinctly non-trivial, so I'm really very reluctant to move in this direction without clear evidence that we have no other alternative....

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#141 lore raw
Quoted 4 lines · Dave Chinner
Quoted 1 line · On Thu, Jun 24, 2010 at 01:02:12PM +1000, [email protected] wrote:

http://www.kernel.org/pub/linux/kernel/people/npiggin/patches/fs-scale/

Can you put a git tree up somewhere?

I suppose I should. I'll try to set one up.

Quoted 8 lines
Quoted 1 line

Update to vfs scalability patches:

....

Now that I've had a look at the whole series, I'll make an overall
comment: I suspect that the locking is sufficiently complex that we can count the number of people that will be able to debug it on one hand.

As opposed to everyone who understood it beforehand? :)

Quoted 2 lines

This patch set didn't just fall off the locking cliff, it
fell into a bottomless pit...

I actually think it's simpler in ways. It has more locks, but a lot of those protect small, well defined data.

Filesystems have required surprisingly minimal changes (except autofs4, but that's fairly special case).

Quoted 10 lines
Quoted 6 lines

Performance:
Last time I was testing on a 32-node Altix which could be considered as not a sweet-spot for Linux performance target (ie. improvements there may not justify complexity). So recently I've been testing with a tightly interconnected 4-socket Nehalem (4s/32c/64t). Linux needs to perform well on this size of system.

Sure, but I have to question how much of this is actually necessary? A lot of it looks like scalability for scalabilities sake, not because there is a demonstrated need...

People are complaining about vfs scalability already (at least Intel, Google, IBM, and networking people). By the time people start shouting, it's too late because it will take years to get the patches merged. I'm not counting -rt people who have a bad time with global vfs locks.

You saw the "batched dput+iput" hacks that google posted a couple of years ago. Those were in the days of 4 core Core2 CPUs, long before 16 thread Nehalems that will scale well to 4/8 sockets at low cost.

At the high end, vaguely extrapolating from my numbers, a big POWER7 may do under 100 open/close operations per second per hw thread. A big UV probably under 10 per core.

But actually it's not all for scalability. I have some follow on patches (that require RCU inodes, among other things) that actually improve single threaded performance significnatly. git diff workload IIRC was several % improved from speeding up stat(2).

Quoted 11 lines
Quoted 7 lines

*** Single-thread microbenchmark (simple syscall loops, lower is better):

Test                    Difference at 95.0% confidence (50 runs)
open/close              -6.07% +/- 1.075%
creat/unlink            27.83% +/- 0.522%

Open/close is a little faster, which should be due to one less atomic in the dput common case. Creat/unlink is significantly slower, which is due to RCU freeing inodes.

That's a pretty big ouch. Why does RCU freeing of inodes cause that much regression? The RCU freeing is out of line, so where does the big impact come from?

That comes mostly from inability to reuse the cache-hot inode structure, and the cost to go over the deferred RCU list and free them after they get cache cold.

Quoted 9 lines
Quoted 8 lines

*** 64 parallel git diff on 64 kernel trees fully cached (avg of 5 runs):

                vanilla         vfs
real            0m4.911s        0m0.183s
user            0m1.920s        0m1.610s
sys             4m58.670s       0m5.770s

After vfs patches, 26x increase in throughput, however parallelism is limited by test spawning and exit phases. sys time improvement shows closer to 50x improvement. vanilla is bottlenecked on dcache_lock.

Quoted 3 lines

So if we cherry pick patches out of the series, what is the bare minimum set needed to obtain a result in this ballpark? Same for the other tests?

Well it's very hard to just scale up bits and pieces because the dcache_lock is currently basically global (except for d_flags and some cases of d_count manipulations).

Start chipping away at bits and pieces of it as people hit bottlenecks and I think it will end in a bigger mess than we have now.

I don't think this should be done lightly, but I think it is going to be required soon.

Quoted 13 lines
Quoted 6 lines

*** Reclaim
I have not done much reclaim testing yet. It should be more scalable and lower latency due to significant reduction in lru locks interfering with other critical sections in inode/dentry code, and because we have per-zone locks. Per-zone LRUs mean that reclaim is targetted to the correct zone, and that kswapd will operate on lists of node-local memory objects.

This means we no longer have any global LRUness to inode or dentry reclaim, which is going to significantly change caching behaviour. It's also got interesting corner cases like a workload running on a single node with a dentry/icache working set larger than the VM wants to hold on a single node.

Quoted 6 lines

We went through these sorts of problems with cpusets a few years back, and the workaround for it was not to limit the slab cache to the cpuset's nodes. Handling this sort of problem correctly seems distinctly non-trivial, so I'm really very reluctant to move in this direction without clear evidence that we have no other alternative....

As I explained in the other mail, that's not actaully how the per-zone reclaim works.

Thanks,
Nick

#143 lore raw
In reply to Nick Piggin
Quoted 9 lines · Nick Piggin
Quoted 8 lines · Dave Chinner
Quoted 6 lines · On Thu, Jun 24, 2010 at 01:02:12PM +1000, [email protected] wrote:

Performance:
Last time I was testing on a 32-node Altix which could be considered as not a sweet-spot for Linux performance target (ie. improvements there may not justify complexity). So recently I've been testing with a tightly interconnected 4-socket Nehalem (4s/32c/64t). Linux needs to perform well on this size of system.

Quoted 8 lines
Quoted 3 lines

Sure, but I have to question how much of this is actually necessary? A lot of it looks like scalability for scalabilities sake, not because there is a demonstrated need...

People are complaining about vfs scalability already (at least Intel, Google, IBM, and networking people). By the time people start shouting, it's too late because it will take years to get the patches merged. I'm not counting -rt people who have a bad time with global vfs locks.

I'm not denying it that we need to do work here - I'm questioning the "change everything at once" approach this patch set takes. You've started from the assumption that everything the dcache_lock and inode_lock protect are a problem and goes from there.

However, if we move some things out fom under the dcache lock, then the pressure on the lock goes down and the remaining operations may not hinder scalability. That's what I'm trying to understand, and why I'm suggesting that you need to break this down into smaller, more easily verifable, benchamrked patch sets. IMO, I have no way of verifying if any of these patches are necessary or not, and I need to understand that as part of reviewing them...

Quoted 13 lines
Quoted 9 lines
Quoted 8 lines

*** 64 parallel git diff on 64 kernel trees fully cached (avg of 5 runs):

                vanilla         vfs
real            0m4.911s        0m0.183s
user            0m1.920s        0m1.610s
sys             4m58.670s       0m5.770s

After vfs patches, 26x increase in throughput, however parallelism is limited by test spawning and exit phases. sys time improvement shows closer to 50x improvement. vanilla is bottlenecked on dcache_lock.

Quoted 3 lines

So if we cherry pick patches out of the series, what is the bare minimum set needed to obtain a result in this ballpark? Same for the other tests?

Quoted 6 lines

Well it's very hard to just scale up bits and pieces because the dcache_lock is currently basically global (except for d_flags and some cases of d_count manipulations).

Start chipping away at bits and pieces of it as people hit bottlenecks and I think it will end in a bigger mess than we have now.

I'm not suggesting that we should do this randomly. A more structured approach that demonstrates the improvement as groups of changes are made will help us evaluate the changes more effectively. It may be that we need every single change in the patch series, but there is no way we can verify that with the information that has been provided.

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
#144 lore raw
Quoted 24 lines · Dave Chinner
Quoted 9 lines · Nick Piggin
Quoted 8 lines · Dave Chinner
Quoted 6 lines · On Thu, Jun 24, 2010 at 01:02:12PM +1000, [email protected] wrote:

Performance:
Last time I was testing on a 32-node Altix which could be considered as not a sweet-spot for Linux performance target (ie. improvements there may not justify complexity). So recently I've been testing with a tightly interconnected 4-socket Nehalem (4s/32c/64t). Linux needs to perform well on this size of system.

Quoted 8 lines
Quoted 3 lines

Sure, but I have to question how much of this is actually necessary? A lot of it looks like scalability for scalabilities sake, not because there is a demonstrated need...

People are complaining about vfs scalability already (at least Intel, Google, IBM, and networking people). By the time people start shouting, it's too late because it will take years to get the patches merged. I'm not counting -rt people who have a bad time with global vfs locks.

I'm not denying it that we need to do work here - I'm questioning the "change everything at once" approach this patch set takes. You've started from the assumption that everything the dcache_lock and inode_lock protect are a problem and goes from there.

Quoted 7 lines

However, if we move some things out fom under the dcache lock, then the pressure on the lock goes down and the remaining operations may not hinder scalability. That's what I'm trying to understand, and why I'm suggesting that you need to break this down into smaller, more easily verifable, benchamrked patch sets. IMO, I have no way of verifying if any of these patches are necessary or not, and I need to understand that as part of reviewing them...

I can see where you're coming from, and I tried to do that, but it got pretty hard and messy. Also, it was pretty difficult to lift dcache and inode lock out of many paths unless *everything* else was protected by other locks. It is also hard not to introduce more atomic operations and slow down single thread performance.

It's not so much the lock hold times as the cacheline bouncing that hurts most. So when adding or removing a dentry for example, we manipulate hash, lru, inode alias, parent, and the fields in the dentry itself. If you have to take the dcache_lock for any of those manipulations, you incur the global cacheline bounce for that operation.

Honestly, I like the way the locking turned out. In dcache.c, inode.c and fs-writeback.c it is complex, but it always has been. For filesystems I would say it is simpler.

Need to stabilize a dentry? Take dentry->d_lock. This freezes all its fields, its refcount, pins it in (or out of) data structures, and pins its immediate parent and children, and inode we point to. Same for inodes.

The rest of the data structures (hash, lru, io lists, inode alias lists etc) that they may belong to, are protected by individual, narrow locks.

Quoted 20 lines
Quoted 13 lines
Quoted 9 lines
Quoted 8 lines

*** 64 parallel git diff on 64 kernel trees fully cached (avg of 5 runs):

                vanilla         vfs
real            0m4.911s        0m0.183s
user            0m1.920s        0m1.610s
sys             4m58.670s       0m5.770s

After vfs patches, 26x increase in throughput, however parallelism is limited by test spawning and exit phases. sys time improvement shows closer to 50x improvement. vanilla is bottlenecked on dcache_lock.

Quoted 3 lines

So if we cherry pick patches out of the series, what is the bare minimum set needed to obtain a result in this ballpark? Same for the other tests?

Quoted 6 lines

Well it's very hard to just scale up bits and pieces because the dcache_lock is currently basically global (except for d_flags and some cases of d_count manipulations).

Start chipping away at bits and pieces of it as people hit bottlenecks and I think it will end in a bigger mess than we have now.

Quoted 6 lines

I'm not suggesting that we should do this randomly. A more structured approach that demonstrates the improvement as groups of changes are made will help us evaluate the changes more effectively. It may be that we need every single change in the patch series, but there is no way we can verify that with the information that has been provided.

I didn't say randomly, but piece-wise, reducing locks bit by bit as problems are quantified. Doing that means that all the code has to go *far more* locking-scheme transitions and it's harder to come to a clean overall end result.

#145 lore raw
In reply to Dave Chinner
Quoted 5 lines · Dave Chinner

I'm not denying it that we need to do work here - I'm questioning the "change everything at once" approach this patch set takes. You've started from the assumption that everything the dcache_lock and inode_lock protect are a problem and goes from there.

Global code locks in a core subsystem are definitely a problem.

In many ways they're as bad a a BKL. There will be always workloads where they hurt. They are bad coding style. They just have to go.

I don't understand how anyone can even defend them.

Especially bad are code locks that protect lots of different things. Those are not only bad for scalability, but also bad for maintainability, because few people can really understand them even. With smaller well defined locks that's usually easier.

-Andi

Signature · 1 line
[email protected] -- Speaking for myself only.
#146 lore raw
In reply to Nick Piggin
Quoted 4 lines · Nick Piggin

But actually it's not all for scalability. I have some follow on patches (that require RCU inodes, among other things) that actually improve single threaded performance significnatly. git diff workload IIRC was several % improved from speeding up stat(2).

I rewrote the store-free path walk patch that goes on top of this patchset (it's now much cleaner and more optimised, I'll post a patch soonish). It is quicker than I remembered.

A single thread running stat(2) in a loop on a file "./file" has the following cost (on an 2s8c Barcelona):

2.6.35-rc3 595 ns/op
patched 336 ns/op

stat(2) takes 56% the time with patches. It's something like 13 fewer atomic operations per syscall.

What's that good for? A single threaded, cached git diff on the linux kernel tree takes just 81% of the time after the vfs patches (0.27s vs 0.33s).

#147 lore raw
Quoted 4 lines · Nick Piggin

What's that good for? A single threaded, cached git diff on the linux kernel tree takes just 81% of the time after the vfs patches (0.27s vs 0.33s).

That's very cool!

Hopefully we can make some progress on the whole patchkit now.

-Andi

#148 lore raw
In reply to Nick Piggin
Quoted 16 lines · Nick Piggin
Quoted 4 lines · Nick Piggin

But actually it's not all for scalability. I have some follow on patches (that require RCU inodes, among other things) that actually improve single threaded performance significnatly. git diff workload IIRC was several % improved from speeding up stat(2).

I rewrote the store-free path walk patch that goes on top of this patchset (it's now much cleaner and more optimised, I'll post a patch soonish). It is quicker than I remembered.

A single thread running stat(2) in a loop on a file "./file" has the following cost (on an 2s8c Barcelona):

2.6.35-rc3 595 ns/op
patched 336 ns/op

Quoted 6 lines

stat(2) takes 56% the time with patches. It's something like 13 fewer atomic operations per syscall.

What's that good for? A single threaded, cached git diff on the linux kernel tree takes just 81% of the time after the vfs patches (0.27s vs 0.33s).

At the other end of the scale, I tried dbench on ramfs on the little 32n64c Altix. Dbench actually has statfs() call completely removed from the workload -- it's still a little problematic and patched kernel throughput is ~halved with statfs().

dbench procs 1 64
2.6.35-rc3 235MB/s 95MB/s ( 0.6% scaling)
patched 245MB/s 14870MB/s (94.8% scaling)

(note all these numbers are with store-free path walking patches on top of the posted patchset -- dbench procs do path walking from common cwds so it will never scale this well if we have to take refcounts on common dentries)

Thanks,
Nick

#149 lore raw
In reply to Nick Piggin
Quoted 8 lines · Nick Piggin
Quoted 4 lines

That's a pretty big ouch. Why does RCU freeing of inodes cause that much regression? The RCU freeing is out of line, so where does the big impact come from?

That comes mostly from inability to reuse the cache-hot inode structure, and the cost to go over the deferred RCU list and free them after they get cache cold.

I do wonder if this isn't a big design bug.

Most of the time with RCU, we don't need to wait to actually do the _freeing_ of the individual data structure, we only need to make sure that the data structure remains of the same _type_. IOW, we can free it (and re-use it), but the backing storage cannot be released to the page cache. That's what SLAB_DESTROY_BY_RCU should give us.

Is that not possible in this situation? Do we really need to keep the inode _identity_ around for RCU?

If you use just SLAB_DESTROY_BY_RCU, then inode re-use remains, and cache behavior would be much improved. The usual requirement for SLAB_DESTROY_BY_RCU is that you only touch a lock (and perhaps re-validate the identity) in the RCU-reader paths. Could that be made to work?

Because that 27% drop really is pretty distressing.

That said, open (of the non-creating kind), close, and stat are certainly more important than creating and freeing files. So as a trade-off, it's probably the right thing to do. But if we can get all the improvement _without_ that big downside, that would obviously be better yet.

Linus

#150 lore raw
Quoted 11 lines · Linus Torvalds
Quoted 8 lines · Nick Piggin
Quoted 4 lines

That's a pretty big ouch. Why does RCU freeing of inodes cause that much regression? The RCU freeing is out of line, so where does the big impact come from?

That comes mostly from inability to reuse the cache-hot inode structure, and the cost to go over the deferred RCU list and free them after they get cache cold.

I do wonder if this isn't a big design bug.

It's possible, yes. Although a lot of that drop does come from hitting RCU and overruning slab allocator queues. It was what, closer to 10% when doing small numbers of creat/unlink loops.

Quoted 6 lines

Most of the time with RCU, we don't need to wait to actually do the _freeing_ of the individual data structure, we only need to make sure that the data structure remains of the same _type_. IOW, we can free it (and re-use it), but the backing storage cannot be released to the page cache. That's what SLAB_DESTROY_BY_RCU should give us.

Quoted 8 lines

Is that not possible in this situation? Do we really need to keep the inode _identity_ around for RCU?

If you use just SLAB_DESTROY_BY_RCU, then inode re-use remains, and cache behavior would be much improved. The usual requirement for SLAB_DESTROY_BY_RCU is that you only touch a lock (and perhaps re-validate the identity) in the RCU-reader paths. Could that be made to work?

I definitely thought of that. I actually thought it would not be possible with the store-free path walk patches though, because we need to check some inode properties (eg. permission). So I was thinking the usual approach of taking a per-entry lock defeats the whole purpose of store-free path walk.

But you've got me to think about it again and it should be possible to do just using the dentry seqlock. IOW, if the inode gets disconnected from the dentry (and then can get possibly freed and reused) then just retry the lookup.

It may be a little tricky. I'll wait until the path-walk code is more polished first.

Quoted 8 lines

Because that 27% drop really is pretty distressing.

That said, open (of the non-creating kind), close, and stat are certainly more important than creating and freeing files. So as a trade-off, it's probably the right thing to do. But if we can get all the improvement _without_ that big downside, that would obviously be better yet.

We have actually bigger regressions than that for other code paths. The RCU freeing for files structs causes similar, about 20-30% regression in open/close.

I actually have a (proper) patch to make that use DESTROY_BY_RCU too. It actually slows down fd lookup by a tiny bit, though (lock, load, branch, increment, unlock versus atomic inc). But same number of atomic ops.

#151 lore raw
In reply to Linus Torvalds
Quoted 12 lines · Linus Torvalds
Quoted 8 lines · Nick Piggin
Quoted 4 lines

That's a pretty big ouch. Why does RCU freeing of inodes cause that much regression? The RCU freeing is out of line, so where does the big impact come from?

That comes mostly from inability to reuse the cache-hot inode structure, and the cost to go over the deferred RCU list and free them after they get cache cold.

I do wonder if this isn't a big design bug.

Quoted 8 lines

Most of the time with RCU, we don't need to wait to actually do the _freeing_ of the individual data structure, we only need to make sure that the data structure remains of the same _type_. IOW, we can free it (and re-use it), but the backing storage cannot be released to the page cache. That's what SLAB_DESTROY_BY_RCU should give us.

Is that not possible in this situation? Do we really need to keep the inode _identity_ around for RCU?

In this case, the workload can be very update-heavy, so this type-safe (vs. identity-safe) approach indeed makes a lot of sense. But if this was a read-heavy situation (think SELinux or many areas in networking), the read-side simplifications and speedups that often come with identity safety would probably more than make up for the occasional grace-period-induced cache miss.

So, as a -very- rough rule of thumb, when less than a few percent of the accesses are updates, you most likely want identity safety. If more than half of the accesses can be updates, you probably want SLAB_DESTROY_BY_RCU-style type safety instead -- or maybe just straight locking. If you are somewhere in between, pick one randomly, if it works, go with it, otherwise try something else. ;-)

In this situation, a create/rename/delete workload would be quite update heavy, so, as you say, SLAB_DESTROY_BY_RCU is well worth looking into.

Thanx, Paul

Quoted 20 lines

If you use just SLAB_DESTROY_BY_RCU, then inode re-use remains, and cache behavior would be much improved. The usual requirement for SLAB_DESTROY_BY_RCU is that you only touch a lock (and perhaps re-validate the identity) in the RCU-reader paths. Could that be made to work?

Because that 27% drop really is pretty distressing.

That said, open (of the non-creating kind), close, and stat are certainly more important than creating and freeing files. So as a trade-off, it's probably the right thing to do. But if we can get all the improvement _without_ that big downside, that would obviously be better yet.

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

#152 lore raw
In reply to Dave Chinner
Quoted 3 lines · Dave Chinner

Sure, but I have to question how much of this is actually necessary? A lot of it looks like scalability for scalabilities sake, not because there is a demonstrated need...

Well, we've repeatedly run into problems with contention on the dcache_lock as well as the inode_lock; changes that improve those paths are extremely interesting to us. I've also seen numbers from systems with large (i.e. 32 to 64) numbers of cores that clearly show serious problems in this area.

Further, while this seems like a bunch of patches, a close look shows that it basically just pushes the dcache and inode locks down as far as possible, making other improvements (such as removal of a few atomics and no longer batching inode reclaims, among other things) based on that work. I would be hard-pressed to find much to cherry-pick from this patch set.

One interesting thing might be to do a set of performance tests for kernels with increasingly more of the patchset, just to see the effect of the earlier patches against a vanilla kernel and to measure the cumulative effect of the later patches. (I'm not volunteering, however:
ENOTIME.)

Signature · 2 lines
Frank Mayhar <[email protected]>
Google, Inc.