Support removal of unused dentry entries via SLUB defrag interface This patch allows the removal of unused dentry entries in a partial populated slab page. Very limited in what it can do for reclaim but this catches frequent cases in which we have a long list of partial slabs. We can free up the slabs that just have a few unused dentry entries in them. get_dentry_reference uses the dcache lock and then works with dget_locked to obtaina reference to the dentry. An additional complication is that the dentry may be in process of being freed or it may just have been allocated. In that case d_inode is NULL. If we discover this then we simply stay away from the object. Otherwise we increment the refcount and return success. kick_dentry_object() is called after get_dentry_reference() has been used and after the slab has dropped all of its own locks. The dentry pruning for unused entries works in a straighforward way. Signed-off-by: Christoph Lameter --- fs/dcache.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 10 deletions(-) Index: slub/fs/dcache.c =================================================================== --- slub.orig/fs/dcache.c 2007-05-11 19:18:35.000000000 -0700 +++ slub/fs/dcache.c 2007-05-11 19:31:20.000000000 -0700 @@ -2114,18 +2114,86 @@ static void __init dcache_init_early(voi INIT_HLIST_HEAD(&dentry_hashtable[loop]); } +/* + * The slab is holding off frees. Thus we can safely examine + * the object without the danger of it vanishing from under us. + */ +static int get_dentry(struct kmem_cache *s, void *private) +{ + struct dentry *dentry = private; + int result = 0; + + spin_lock(&dcache_lock); + /* + * dentry->d_inode is set to NULL when the dentry + * is freed. Use that as an indicator that we should + * not interfere with freeing process. + */ + if (dentry->d_inode) { + dget_locked(dentry); + if (atomic_read(&dentry->d_count) > 2) + result = -EINVAL; + } else + result = 1; + spin_unlock(&dcache_lock); + return result; +} + +static void put_dentry(struct kmem_cache *s, void *private) +{ + struct dentry *dentry = private; + + dput(dentry); +} + +/* + * Slab has dropped all the locks. Get rid of the + * refcount we obtained earlier and also rid of the + * object. + */ +static int kick_dentry(struct kmem_cache *s, void *private) +{ + struct dentry *dentry = private; + + spin_lock(&dcache_lock); + spin_lock(&dentry->d_lock); + if (atomic_read(&dentry->d_count) > 1) { + /* + * References still exist. So we cannot + * do anything right now. + */ + spin_unlock(&dentry->d_lock); + spin_unlock(&dcache_lock); + dput(dentry); + return -EBUSY; + } + + /* Remove from LRU */ + if (!list_empty(&dentry->d_lru)) { + dentry_stat.nr_unused--; + list_del_init(&dentry->d_lru); + } + /* Drop the entry */ + prune_one_dentry(dentry, 1); + spin_unlock(&dcache_lock); + return 0; +} + +static struct kmem_cache_ops dentry_kmem_cache_ops = { + .get = get_dentry, + .put = put_dentry, + .kick = kick_dentry, + .sync = synchronize_rcu +}; + static void __init dcache_init(unsigned long mempages) { int loop; - /* - * A constructor could be added for stable state like the lists, - * but it is probably not worth it because of the cache nature - * of the dcache. - */ - dentry_cache = KMEM_CACHE(dentry, - SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD); - + dentry_cache = KMEM_CACHE_OPS(dentry, + SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD, + &dentry_kmem_cache_ops); + register_shrinker(&dcache_shrinker); /* Hash may have been set up in dcache_init_early */ @@ -2173,8 +2241,7 @@ void __init vfs_caches_init(unsigned lon names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL); - filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0, - SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL); + filp_cachep = KMEM_CACHE(file, SLAB_HWCACHE_ALIGN|SLAB_PANIC); dcache_init(mempages); inode_init(mempages);