--- linux-2.6.19-rc4/fs/dcache.c.vshud1 2006-11-02 14:23:03.000000000 +0300 +++ linux-2.6.19-rc4/fs/dcache.c 2006-11-03 14:57:58.000000000 +0300 @@ -171,8 +171,14 @@ repeat: if (d_unhashed(dentry)) goto kill_it; if (list_empty(&dentry->d_lru)) { - dentry->d_flags |= DCACHE_REFERENCED; - list_add(&dentry->d_lru, &dentry_unused); + struct list_head *list; + + list = &dentry->d_sb->s_shrink_list; + if (list_empty(list)) { + list = &dentry_unused; + dentry->d_flags |= DCACHE_REFERENCED; + } + list_add(&dentry->d_lru, list); dentry_stat.nr_unused++; } spin_unlock(&dentry->d_lock); @@ -394,10 +400,6 @@ static void prune_one_dentry(struct dent * * This function may fail to free any resources if * all the dentries are in use. - * - * Any dentries that were not removed due to the @count - * limit will be splice on to the end of dentry_unused, - * so they should already be founded in dentry_stat.nr_unused. */ static void prune_dcache(int count, struct list_head *list) @@ -409,7 +411,7 @@ static void prune_dcache(int count, stru /* use the dentry_unused list */ list = &dentry_unused; - for (; count ; count--) { + for (; have_list || count-- ;) { struct dentry *dentry; struct list_head *tmp; struct rw_semaphore *s_umount; @@ -434,6 +436,17 @@ static void prune_dcache(int count, stru spin_unlock(&dentry->d_lock); continue; } + /* + * If this dentry is for "my" filesystem, then I can prune it + * without taking the s_umount lock: either I already hold it + * (called from shrink_dcache_sb) or are called from some + * filesystem opernations and therefore cannot race with + * generic_shutdown_super(). + */ + if (have_list) { + prune_one_dentry(dentry); + continue; + } /* If the dentry was recently referenced, don't free it. */ if (dentry->d_flags & DCACHE_REFERENCED) { dentry->d_flags &= ~DCACHE_REFERENCED; @@ -443,21 +456,6 @@ static void prune_dcache(int count, stru continue; } /* - * If the dentry is not DCACHED_REFERENCED, it is time - * to remove it from the dcache, provided the super block is - * NULL (which means we are trying to reclaim memory) - * or this dentry belongs to the same super block that - * we want to shrink. - */ - /* - * If this dentry is for "my" filesystem, then I can prune it - * without taking the s_umount lock (I already hold it). - */ - if (have_list) { - prune_one_dentry(dentry); - continue; - } - /* * ...otherwise we need to be sure this filesystem isn't being * unmounted, otherwise we could race with * generic_shutdown_super(), and end up holding a reference to @@ -483,27 +481,9 @@ static void prune_dcache(int count, stru list_add(&dentry->d_lru, &dentry_unused); dentry_stat.nr_unused++; } - /* split any remaining entries back onto dentry_unused */ - if (have_list) - list_splice(list, dentry_unused.prev); spin_unlock(&dcache_lock); } -/** - * shrink_dcache_sb - shrink dcache for a superblock - * @sb: superblock - * - * Shrink the dcache for the specified super block. This - * is used to reduce the dcache presence of a file system - * before re-mounting, and when invalidating the device - * holding a file system. - */ - -void shrink_dcache_sb(struct super_block * sb) -{ - shrink_dcache_parent(sb->s_root); -} - /* * destroy a single subtree of dentries for unmount * - see the comments on shrink_dcache_for_umount() for a description of the @@ -706,11 +686,10 @@ positive: * drop the lock and return early due to latency * constraints. */ -static int select_parent(struct dentry * parent, struct list_head *new) +static void select_parent(struct dentry * parent, struct list_head *new) { struct dentry *this_parent = parent; struct list_head *next; - int found = 0; spin_lock(&dcache_lock); repeat: @@ -732,7 +711,6 @@ resume: if (!atomic_read(&dentry->d_count)) { list_add_tail(&dentry->d_lru, new); dentry_stat.nr_unused++; - found++; } /* @@ -740,7 +718,7 @@ resume: * ensures forward progress). We'll be coming back to find * the rest. */ - if (found && need_resched()) + if (!list_empty(new) && need_resched()) goto out; /* @@ -761,7 +739,37 @@ resume: } out: spin_unlock(&dcache_lock); - return found; +} + +/** + * select_anon - further prune the cache + * @sb: superblock + * + * Prune the dentries that are anonymous + */ + +static void select_anon(struct super_block *sb) +{ + struct hlist_node *lp; + struct hlist_head *head = &sb->s_anon; + + spin_lock(&dcache_lock); + hlist_for_each(lp, head) { + struct dentry *this = hlist_entry(lp, struct dentry, d_hash); + if (!list_empty(&this->d_lru)) { + dentry_stat.nr_unused--; + list_del_init(&this->d_lru); + } + /* + * move only zero ref count dentries to the end + * of list for prune_dcache + */ + if (!atomic_read(&this->d_count)) { + list_add(&this->d_lru, &sb->s_shrink_list); + dentry_stat.nr_unused++; + } + } + spin_unlock(&dcache_lock); } /** @@ -773,11 +781,38 @@ out: void shrink_dcache_parent(struct dentry * parent) { - int found; LIST_HEAD(list); - while ((found = select_parent(parent, &list)) != 0) - prune_dcache(found, &list); + for (;;) { + select_parent(parent, &list); + if (list_empty(&list)) + break; + prune_dcache(0, &list); + } +} + +/** + * shrink_dcache_sb - shrink dcache for a superblock + * @sb: superblock + * + * Shrink the dcache for the specified super block. This + * is used to reduce the dcache presence of a file system + * before re-mounting, and when invalidating the device + * holding a file system. + */ + +void shrink_dcache_sb(struct super_block * sb) +{ + struct list_head *list; + + list = &sb->s_shrink_list; + for (;;) { + select_parent(sb->s_root, list); + select_anon(sb); + if (list_empty(list)) + break; + prune_dcache(0, list); + } } /* --- linux-2.6.19-rc4/include/linux/fs.h.vshud1 2006-11-03 15:28:04.000000000 +0300 +++ linux-2.6.19-rc4/include/linux/fs.h 2006-11-03 14:52:16.000000000 +0300 @@ -941,6 +941,7 @@ struct super_block { struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */ struct list_head s_files; + struct list_head s_shrink_list; struct block_device *s_bdev; struct list_head s_instances; struct quota_info s_dquot; /* Diskquota specific options */