vfs scalability patches updated
Subsystems (70)
Peter Zijlstra
maintainerRe: [patch 30/52] fs: icache lock lru/writeback lists
In reply to Nick PigginQuoted 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_lockedcould be changed over.
Probably, I just haven't felt like actually visiting all sites to check ;-)
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
@@ -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_bllong 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
@@ -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_headestroy_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 *ilist_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 *ispin_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 *ispin_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
@@ -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
@@ -1283,11 +1283,11 @@ static void pohmelfs_put_super(struct sudprintk ("%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
@@ -1964,8 +1964,13 @@ void btrfs_add_delayed_iput(struct inodestruct 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 *__unlireturn 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
@@ -1028,7 +1028,7 @@ static int trim_caps_cb(struct inode *inspin_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
@@ -566,7 +566,9 @@ static int logfs_link(struct dentry *oldreturn -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
@@ -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 )
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
@@ -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_heaclear_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_blostruct 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 *ispin_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
@@ -382,6 +382,7 @@ static void hugetlbfs_forget_inode(strucif (generic_detach_inode (inode)) {truncate_hugepages (inode,0 );clear_inode (inode); +/* XXX: why no wake_up_inode? */ destroy_inode (inode); } }
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
@@ -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_blospin_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;
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
@@ -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
@@ -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
@@ -312,7 +312,7 @@ static void requeue_io(struct inode *inostatic 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(strwhile (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 bint 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 *inif (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
@@ -377,7 +377,7 @@ static void hugetlbfs_delete_inode(strucclear_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
@@ -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
@@ -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
@@ -883,7 +883,6 @@ static void add_dquot_ref(struct super_bint 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 superemove_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
@@ -368,13 +368,16 @@ void fsnotify_unmount_inodes(struct listlist_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 listiput (inode); -spin_lock (&inode_lock);spin_lock (&sb_inode_list_lock); } }
mm/backing-dev.c +0 -4
@@ -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_infoif (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);
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
@@ -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_heaspin_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 *iEXPORT_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 *ispin_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
@@ -2181,6 +2181,7 @@ extern int should_remove_suid(struct denextern 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
@@ -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;
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
@@ -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_blockspin_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(strucstruct 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, inif (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 sstruct 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
@@ -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
@@ -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
@@ -2122,7 +2122,7 @@ static int shmem_encode_fh(struct dentryif (*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
@@ -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
@@ -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
@@ -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
@@ -500,7 +500,7 @@ static struct dentry *hfs_file_lookup(stHFS_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
@@ -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
@@ -178,7 +178,7 @@ static struct dentry *hfsplus_file_lookuHFSPLUS_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
@@ -187,13 +187,13 @@ int nilfs_init_gccache(struct the_nilfsINIT_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
@@ -2447,7 +2447,7 @@ nilfs_remove_written_gcinodes(struct thelist_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
@@ -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;
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
@@ -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 *inodeINIT_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_heastruct 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_hinvalidate_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_blockatomic_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 *istruct 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 *iWARN_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
@@ -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 iextern 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 sextern 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
@@ -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
@@ -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;/*
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.
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 :)
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]
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_REFERENCEDreclaim 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_REFERENCEDflag 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.
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_REFERENCEDreclaim 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_REFERENCEDflag 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]
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_REFERENCEDreclaim 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_REFERENCEDflag 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_REFERENCEDflag 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.
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
@@ -161,11 +161,18 @@ static struct inode *ext2_alloc_inode(streturn &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
@@ -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
@@ -397,13 +397,20 @@ static struct inode *bdev_alloc_inode(streturn &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
@@ -485,6 +485,13 @@ static struct inode *ext3_alloc_inode(streturn &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
@@ -665,11 +665,18 @@ static struct inode *hugetlbfs_alloc_inoreturn &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
@@ -66,11 +66,18 @@ static struct inode *proc_alloc_inode(streturn 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
@@ -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
@@ -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
@@ -520,11 +520,18 @@ static struct inode *fat_alloc_inode(strreturn &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
@@ -1365,11 +1365,18 @@ struct inode *nfs_alloc_inode(struct supreturn &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
@@ -2389,13 +2389,20 @@ static struct inode *shmem_alloc_inode(sreturn &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
@@ -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
@@ -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 ;/**
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]
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.
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
@@ -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
@@ -412,14 +412,6 @@ void fsnotify_unmount_inodes(struct listspin_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 listfsnotify_inode_delete (inode);iput (inode); - -spin_lock (&sb_inode_list_lock); } }
fs/notify/inotify/inotify.c +0 -10
@@ -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
@@ -883,8 +883,8 @@ static void add_dquot_ref(struct super_bint 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 supeif (!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 */
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
@@ -371,7 +371,7 @@ static int sock_alloc_file(struct socketif (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
@@ -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
@@ -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
@@ -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
@@ -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 *);
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
@@ -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_headestroy_inode (inode); nr_disposed++; } -atomic_sub (nr_disposed, &inodes_stat.nr_inodes); }/*
@@ -399,7 +398,7 @@ static void dispose_list(struct list_heastatic 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_hif (!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 *iif (!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
@@ -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
@@ -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_bllong 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); }
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
@@ -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);
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_cpuvariable, feeded by the sharedlast_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.
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_cpuvariable, feeded by the sharedlast_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.
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_inodeto be exported, adding more branches innew_inodewould 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.
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_inodeto be exported, adding more branches innew_inodewould 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.
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
@@ -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_bllong 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
@@ -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
@@ -406,6 +406,8 @@ extern struct files_stat_struct files_stextern 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 *fstruct 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
@@ -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",
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
@@ -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_heaspin_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_breturn 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
@@ -68,12 +68,26 @@ static struct super_block *alloc_super(sfor_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
@@ -17,7 +17,7 @@ static void drop_pagecache_sb(struct supstruct 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 supiput (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
@@ -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
@@ -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 listfsnotify_inode_delete (inode);iput (inode); - } + } while_inode_list_for_each_entry_safe }
fs/notify/inotify/inotify.c +4 -4
@@ -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
@@ -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 supestruct 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
@@ -344,7 +344,7 @@ extern void fsnotify_destroy_mark_by_entextern 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
@@ -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
@@ -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;
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:
Signed-off-by Nick Piggin <[email protected]>
.....
Quoted 43 lines
include/linux/fs.h +39
-0@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(strextern 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]
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:
Signed-off-by Nick Piggin <[email protected]>.....
Quoted 50 lines
include/linux/fs.h +46
-0@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(strextern 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.
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:
Signed-off-by Nick Piggin <[email protected]>.....
Quoted 50 lines
include/linux/fs.h +46
-0@@ -2194,6 +2198,58 @@ static inline void insert_inode_hash(strextern 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]
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.
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
@@ -603,27 +603,27 @@ static struct inode *find_inode(struct sstruct 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(strstruct 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 ; }
PATCH 46/52 fs: icache reduce locking
In reply to [email protected]fs/fs-writeback.c +5 -3
@@ -484,15 +484,17 @@ writeback_single_inode(struct inode *inospin_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
@@ -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; }
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
@@ -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(sstatic 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
@@ -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
@@ -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
@@ -109,7 +109,7 @@ static int raw_release(struct inode *inoif (--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
@@ -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_devif (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_devdisk_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_devdisk_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
@@ -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
@@ -2480,7 +2480,7 @@ static void btrfs_read_locked_inode(struswitch (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(strucase 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
@@ -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
@@ -136,7 +136,8 @@ struct inode * configfs_new_inode(mode_tstruct 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
@@ -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
@@ -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
@@ -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_blockif (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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -60,7 +60,7 @@ struct inode *ramfs_get_inode(struct supif (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
@@ -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
@@ -109,7 +109,7 @@ struct inode *ubifs_new_inode(struct ubiubifs_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
@@ -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
@@ -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
@@ -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
@@ -1561,7 +1561,7 @@ static struct inode *shmem_get_inode(strif (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
@@ -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(structstruct 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(structint 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
@@ -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
@@ -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
@@ -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
@@ -72,7 +72,7 @@ SYSCALL_DEFINE(fadvise64_64)(int fd, lofelse 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, lofcase 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
@@ -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(structvfs_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
@@ -409,7 +409,7 @@ xip_file_write(struct file *filp, constvfs_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
@@ -491,7 +491,7 @@ static void balance_dirty_pages(struct aunsigned 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 pagint 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 pageint 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -75,8 +75,7 @@ void cancel_dirty_page(struct page *pagestruct 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
@@ -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
@@ -177,7 +177,7 @@ static void ext2_preread_inode(struct instruct 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
@@ -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
@@ -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
@@ -108,8 +108,7 @@ static int ceph_set_page_dirty(struct paif (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
@@ -1365,7 +1365,7 @@ static int cifs_partialpagewrite(structstatic 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
@@ -945,7 +945,7 @@ static ssize_t fuse_file_aio_write(strucvfs_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
@@ -254,7 +254,7 @@ struct inode *fuse_iget(struct super_bloif ((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
@@ -280,7 +280,7 @@ nfs_fhget(struct super_block *sb, structif (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
@@ -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_pageif (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, strnfs_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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
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
@@ -291,11 +291,11 @@ static void redirty_tail(struct inode *iif (!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 *inostruct 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 lassert_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
@@ -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
@@ -80,11 +80,11 @@ static int bdi_debug_stats_show(struct sspin_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
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
@@ -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 lstruct 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 *inospin_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
@@ -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
@@ -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
@@ -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_spacif (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_infostruct 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_infolist_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
@@ -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
@@ -2076,6 +2076,8 @@ extern int check_disk_change(struct blocextern 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);
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
@@ -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 aint 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -205,7 +205,8 @@ extern struct rpc_procinfo nfs4_procedurvoid 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
@@ -655,7 +655,7 @@ int dquot_quota_sync(struct super_blockEXPORT_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
@@ -38,11 +38,7 @@ static void drop_pagecache_sb(struct supstatic 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
@@ -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
@@ -2924,14 +2924,29 @@ static int kvm_mmu_remove_some_alloc_mmureturn 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, gfspin_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
@@ -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
@@ -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(intlist_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
@@ -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
@@ -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
@@ -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
@@ -350,7 +350,6 @@ static const struct super_operations nfsstatic struct shrinker acl_shrinker = { .shrink = nfs_access_cache_shrinker, - .seeks =DEFAULT_SEEKS , };/*
fs/ubifs/shrinker.c +18 -11
@@ -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_masreturn 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
@@ -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
@@ -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
@@ -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 intxfsbufd_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
@@ -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
@@ -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 *tastatic 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>
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>
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>
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>
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>
Re: [patch 50/52] mm: implement per-zone shrinker
In reply to Andi KleenQuoted 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>
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.
Signed-off-by Nick Piggin <[email protected]>
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 longreturn 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, stif (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
-2list_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_mmureturn 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_lisstatic 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
-1list_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>
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_scanand 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 privatenr_to_scan? Shouldn't they all use the same method? The static variable method looks to me to be full of races - concurrent callers toshrinker_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, stif (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_scanloop inshrink_dcache_memory()and leavingprune_dcacheuntouched would have been a better separation. I note that this is what you did withprune_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_FScheck 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_scanrather than nr(1) =nr_to_scanand 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>
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
@@ -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_strucextern 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
@@ -50,6 +50,8 @@ static struct super_block *alloc_super(sstatic 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
@@ -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
@@ -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 *);
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.
Signed-off-by Nick Piggin <[email protected]>
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]
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.
Signed-off-by Nick Piggin <[email protected]>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
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
@@ -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_heaif (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); }
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_FREEINGstate and then continues to gather more, during which it may iput,invalidate_mapping_pages, be preempted, etc. Holding these inodes inI_FREEINGcan 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]
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_FREEINGstate and then continues to gather more, during which it may iput,invalidate_mapping_pages, be preempted, etc. Holding these inodes inI_FREEINGcan cause pauses.What sort of pauses? I can't see how holding a few inodes in
I_FREEINGstate 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.
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_FREEINGstate and then continues to gather more, during which it may iput,invalidate_mapping_pages, be preempted, etc. Holding these inodes inI_FREEINGcan cause pauses.
Quoted 6 lines
Quoted 2 lines
What sort of pauses? I can't see how holding a few inodes in
I_FREEINGstate 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]
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_FREEINGstate and then continues to gather more, during which it may iput,invalidate_mapping_pages, be preempted, etc. Holding these inodes inI_FREEINGcan cause pauses.What sort of pauses? I can't see how holding a few inodes in
I_FREEINGstate 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_FREEINGbehaviour 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.
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.
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_lockandvfsmount_lockseem like rather easy targets to start with. But forfiles_lockI 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.
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.770sAfter 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]
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.770sAfter 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
Quoted 7 lines · Nick Piggin
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.
Please! I was going to ask you this myself, in fact.
Signature · 2 lines
Frank Mayhar <[email protected]> Google, Inc.
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.770sAfter 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_lockis currently basically global (except ford_flagsand some cases ofd_countmanipulations).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]
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_lockandinode_lockprotect 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.770sAfter 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_lockis currently basically global (except ford_flagsand some cases ofd_countmanipulations).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.
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_lockandinode_lockprotect 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.
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).
Quoted 4 lines · Nick Piggin
What's that good for? A single threaded, cached
git diffon 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
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 diffon 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
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
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_RCUshould 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 forSLAB_DESTROY_BY_RCUis 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.
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_RCUshould 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 forSLAB_DESTROY_BY_RCUis 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/
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.