[PATCH] dcache: separate slab for directory dentries

David Chinner
2005-10-06T06:27:39Z

Separate out directory dentries into a separate slab so that
(potentially) longer lived dentries are clustered together rather
than sparsely distributed around the dentry slab cache.

Originally written for 2.6.5 by Greg Banks.

Signed-off-by: Greg Banks (gnb@sgi.com)
Signed-off-by: Dave Chinner (dgc@sgi.com)
---

Reference: http://marc.theaimsgroup.com/?l=linux-mm&m=112664896809759&w=2

fs/dcache.c | 100 ++++++++++++++++++++++++++++++++++++++++++++-----
include/linux/dcache.h | 1
2 files changed, 91 insertions(+), 10 deletions(-)

--- 2.6.x-xfs.orig/include/linux/dcache.h 2005-10-06 12:41:12.000000000 +1000
+++ 2.6.x-xfs/include/linux/dcache.h 2005-10-06 10:51:31.825645594 +1000
@@ -155,6 +155,7 @@

#define DCACHE_REFERENCED 0x0008 /* Recently used, don't discard. */
#define DCACHE_UNHASHED 0x0010
+#define DCACHE_DIRSLAB 0x0040 /* allocated in the dir memcache */

extern spinlock_t dcache_lock;

--- 2.6.x-xfs.orig/fs/dcache.c 2005-10-06 12:41:12.000000000 +1000
+++ 2.6.x-xfs/fs/dcache.c 2005-10-06 11:02:12.986649700 +1000
@@ -45,6 +45,7 @@
EXPORT_SYMBOL(dcache_lock);

static kmem_cache_t *dentry_cache;
+static kmem_cache_t *dentry_dir_cache;

#define DNAME_INLINE_LEN (sizeof(struct dentry)-offsetof(struct dentry,d_iname))

@@ -75,7 +76,8 @@

if (dname_external(dentry))
kfree(dentry->d_name.name);
- kmem_cache_free(dentry_cache, dentry);
+ kmem_cache_free((dentry->d_flags & DCACHE_DIRSLAB) ?
+ dentry_dir_cache : dentry_cache, dentry);
}

/*
@@ -707,7 +709,7 @@
}

/**
- * d_alloc - allocate a dcache entry
+ * __d_alloc - allocate a dcache entry
* @parent: parent of entry to allocate
* @name: qstr of the name
*
@@ -716,19 +718,22 @@
* copied and the copy passed in may be reused after this call.
*/

-struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
+static struct dentry * __d_alloc(struct dentry * parent,
+ const struct qstr *name, int flags)
{
struct dentry *dentry;
char *dname;
+ kmem_cache_t *cache;

- dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
+ cache = (flags & DCACHE_DIRSLAB) ? dentry_dir_cache : dentry_cache;
+ dentry = kmem_cache_alloc(cache, GFP_KERNEL);
if (!dentry)
return NULL;

if (name->len > DNAME_INLINE_LEN-1) {
dname = kmalloc(name->len + 1, GFP_KERNEL);
if (!dname) {
- kmem_cache_free(dentry_cache, dentry);
+ kmem_cache_free(cache, dentry);
return NULL;
}
} else {
@@ -742,7 +747,7 @@
dname[name->len] = 0;

atomic_set(&dentry->d_count, 1);
- dentry->d_flags = DCACHE_UNHASHED;
+ dentry->d_flags = (DCACHE_UNHASHED | flags);
spin_lock_init(&dentry->d_lock);
dentry->d_inode = NULL;
dentry->d_parent = NULL;
@@ -782,6 +787,69 @@
return d_alloc(parent, &q);
}

+struct dentry * d_alloc(struct dentry * parent, const struct qstr *name)
+{
+ return __d_alloc(parent, name, 0);
+}
+
+/*
+ * Allocate a new dentry which will be suitable for the given inode
+ */
+static struct dentry * d_alloc_for_inode(struct dentry * parent,
+ const struct qstr *name,
+ struct inode *inode)
+{
+ int flags = 0;
+
+ if (inode && S_ISDIR(inode->i_mode))
+ flags |= DCACHE_DIRSLAB;
+
+ return __d_alloc(parent, name, flags);
+}
+
+/*
+ * If the given dentry is not suitable for the inode, reallocate
+ * it, copy across the dentry's data and return the new one. Only
+ * useful when the dentry has not yet been attached to inode or
+ * hashed, which is why it's a lot simpler than d_move(). Returns
+ * NULL if the dentry is suitable, Called with dcache_lock, drops
+ * and regains.
+ */
+static struct dentry * d_realloc_for_inode(struct dentry * dentry,
+ struct inode *inode)
+{
+ int flags = 0;
+ struct dentry *new;
+ struct dentry *parent;
+
+ BUG_ON(dentry == NULL);
+ BUG_ON(dentry->d_inode != NULL);
+ BUG_ON(inode == NULL);
+ BUG_ON(dentry->d_parent == NULL || dentry->d_parent == dentry);
+
+ if (S_ISDIR(inode->i_mode))
+ flags |= DCACHE_DIRSLAB;
+ if ((flags & DCACHE_DIRSLAB) == (dentry->d_flags & DCACHE_DIRSLAB))
+ return NULL; /* dentry is suitable */
+
+ parent = dentry->d_parent;
+ list_del_init(&dentry->d_child);
+
+ spin_unlock(&dcache_lock);
+
+ new = __d_alloc(parent, &dentry->d_name, dentry->d_flags | flags);
+
+ spin_lock(&dcache_lock);
+
+ BUG_ON(new == NULL); /* TODO */
+ if (new) {
+// new->d_op = dentry->d_op;
+// new->d_fsdata = dentry->d_fsdata;
+ }
+
+ return new;
+}
+
/**
* d_instantiate - fill in inode information for a dentry
* @entry: dentry to complete
@@ -872,7 +940,7 @@
if (root_inode) {
static const struct qstr name = { .name = "/", .len = 1 };

- res = d_alloc(NULL, &name);
+ res = d_alloc_for_inode(NULL, &name, root_inode);
if (res) {
res->d_sb = root_inode->i_sb;
res->d_parent = res;
@@ -921,7 +989,7 @@
return res;
}

- tmp = d_alloc(NULL, &anonstring);
+ tmp = d_alloc_for_inode(NULL, &anonstring, inode);
if (!tmp)
return NULL;

@@ -987,6 +1055,8 @@
iput(inode);
} else {
/* d_instantiate takes dcache_lock, so we do it by hand */
+ if ((new = d_realloc_for_inode(dentry, inode)))
+ dentry = new;
list_add(&dentry->d_alias, &inode->i_dentry);
dentry->d_inode = inode;
spin_unlock(&dcache_lock);
@@ -1014,7 +1084,7 @@
* To avoid races with d_move while rename is happening, d_lock is used.
*
* Overflows in memcmp(), while d_move, are avoided by keeping the length
- * and name pointer in one structure pointed by d_qstr.
+ * and name pointer in one structure pointed by d_name.
*
* rcu_read_lock() and rcu_read_unlock() are used to disable preemption while
* lookup is going on.
@@ -1121,7 +1191,8 @@
struct hlist_node *lhp;

/* Check whether the ptr might be valid at all.. */
- if (!kmem_ptr_validate(dentry_cache, dentry))
+ if (!kmem_ptr_validate(dentry_cache, dentry) &&
+ !kmem_ptr_validate(dentry_dir_cache, dentry))
goto out;

if (dentry->d_parent != dparent)
@@ -1687,6 +1758,15 @@
SLAB_RECLAIM_ACCOUNT|SLAB_PANIC,
NULL, NULL);

+ dentry_dir_cache = kmem_cache_create("dentry_dir_cache",
+ sizeof(struct dentry),
+ 0,
+ SLAB_RECLAIM_ACCOUNT,
+ NULL, NULL);
+ if (!dentry_dir_cache)
+ panic("Cannot create dentry dir cache");
+
+
set_shrinker(DEFAULT_SEEKS, shrink_dcache_memory);

/* Hash may have been set up in dcache_init_early */

--
Dave Chinner
R&D Software Enginner
SGI Australian Software Group