[PATCH 21/41] union-mount: Drive the union cache via dcache

From: Valerie Aurora
Date: Wed Oct 21 2009 - 15:27:09 EST


From: Jan Blunck <jblunck@xxxxxxx>

If a dentry is removed from dentry cache because its usage count drops to
zero, the references to the underlying layer of the unions the dentry is in
are droped too. Therefore the union cache is driven by the dentry cache.

Signed-off-by: Jan Blunck <jblunck@xxxxxxx>
Signed-off-by: Valerie Aurora <vaurora@xxxxxxxxxx>
---
fs/dcache.c | 10 ++++++-
fs/union.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/dcache.h | 8 +++++
include/linux/union.h | 6 ++++
4 files changed, 97 insertions(+), 1 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 56bd05f..d80a3bb 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -18,6 +18,7 @@
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fs.h>
+#include <linux/union.h>
#include <linux/fsnotify.h>
#include <linux/slab.h>
#include <linux/init.h>
@@ -188,11 +189,14 @@ static struct dentry *__d_kill(struct dentry *dentry, struct list_head *list,
list_add(&dentry->d_lru, list);
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
+ __shrink_d_unions(dentry, list);
return NULL;
}

- /*drops the locks, at that point nobody can reach this dentry */
+ /* drops the locks, at that point nobody can reach this dentry */
dentry_iput(dentry);
+ /* If the dentry was in an union delete them */
+ __shrink_d_unions(dentry, list);
if (IS_ROOT(dentry))
parent = NULL;
else
@@ -784,6 +788,7 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
iput(inode);
}

+ shrink_d_unions(dentry);
d_free(dentry);

/* finished when we fall off the top of the tree,
@@ -1614,7 +1619,9 @@ void d_delete(struct dentry * dentry)
spin_lock(&dentry->d_lock);
isdir = S_ISDIR(dentry->d_inode->i_mode);
if (atomic_read(&dentry->d_count) == 1) {
+ __d_drop_unions(dentry);
dentry_iput(dentry);
+ shrink_d_unions(dentry);
fsnotify_nameremove(dentry, isdir);
return;
}
@@ -1625,6 +1632,7 @@ void d_delete(struct dentry * dentry)
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);

+ shrink_d_unions(dentry);
fsnotify_nameremove(dentry, isdir);
}

diff --git a/fs/union.c b/fs/union.c
index d1950c2..6b99393 100644
--- a/fs/union.c
+++ b/fs/union.c
@@ -14,6 +14,7 @@

#include <linux/bootmem.h>
#include <linux/init.h>
+#include <linux/module.h>
#include <linux/types.h>
#include <linux/hash.h>
#include <linux/fs.h>
@@ -255,6 +256,8 @@ int append_to_union(struct vfsmount *mnt, struct dentry *dentry,
union_put(this);
return 0;
}
+ list_add(&this->u_unions, &dentry->d_unions);
+ dest_dentry->d_unionized++;
__union_hash(this);
spin_unlock(&union_lock);
return 0;
@@ -330,3 +333,74 @@ int follow_union_mount(struct vfsmount **mnt, struct dentry **dentry)

return res;
}
+
+/*
+ * This must be called when unhashing a dentry. This is called with dcache_lock
+ * and unhashes all unions this dentry is in.
+ */
+void __d_drop_unions(struct dentry *dentry)
+{
+ struct union_mount *this, *next;
+
+ spin_lock(&union_lock);
+ list_for_each_entry_safe(this, next, &dentry->d_unions, u_unions)
+ __union_unhash(this);
+ spin_unlock(&union_lock);
+}
+EXPORT_SYMBOL_GPL(__d_drop_unions);
+
+/*
+ * This must be called after __d_drop_unions() without holding any locks.
+ * Note: The dentry might still be reachable via a lookup but at that time it
+ * already a negative dentry. Otherwise it would be unhashed. The union_mount
+ * structure itself is still reachable through mnt->mnt_unions (which we
+ * protect against with union_lock).
+ */
+void shrink_d_unions(struct dentry *dentry)
+{
+ struct union_mount *this, *next;
+
+repeat:
+ spin_lock(&union_lock);
+ list_for_each_entry_safe(this, next, &dentry->d_unions, u_unions) {
+ BUG_ON(!hlist_unhashed(&this->u_hash));
+ BUG_ON(!hlist_unhashed(&this->u_rhash));
+ list_del(&this->u_unions);
+ this->u_next.dentry->d_unionized--;
+ spin_unlock(&union_lock);
+ union_put(this);
+ goto repeat;
+ }
+ spin_unlock(&union_lock);
+}
+
+extern void __dput(struct dentry *, struct list_head *, int);
+
+/*
+ * This is the special variant for use in dput() only.
+ */
+void __shrink_d_unions(struct dentry *dentry, struct list_head *list)
+{
+ struct union_mount *this, *next;
+
+ BUG_ON(!d_unhashed(dentry));
+
+repeat:
+ spin_lock(&union_lock);
+ list_for_each_entry_safe(this, next, &dentry->d_unions, u_unions) {
+ struct dentry *n_dentry = this->u_next.dentry;
+ struct vfsmount *n_mnt = this->u_next.mnt;
+
+ BUG_ON(!hlist_unhashed(&this->u_hash));
+ BUG_ON(!hlist_unhashed(&this->u_rhash));
+ list_del(&this->u_unions);
+ this->u_next.dentry->d_unionized--;
+ spin_unlock(&union_lock);
+ if (__union_put(this)) {
+ __dput(n_dentry, list, 0);
+ mntput(n_mnt);
+ }
+ goto repeat;
+ }
+ spin_unlock(&union_lock);
+}
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 4d48c20..730c432 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -215,12 +215,20 @@ extern seqlock_t rename_lock;
* __d_drop requires dentry->d_lock.
*/

+#ifdef CONFIG_UNION_MOUNT
+extern void __d_drop_unions(struct dentry *);
+#endif
+
static inline void __d_drop(struct dentry *dentry)
{
if (!(dentry->d_flags & DCACHE_UNHASHED)) {
dentry->d_flags |= DCACHE_UNHASHED;
hlist_del_rcu(&dentry->d_hash);
}
+#ifdef CONFIG_UNION_MOUNT
+ /* remove dentry from the union hashtable */
+ __d_drop_unions(dentry);
+#endif
}

static inline void d_drop(struct dentry *dentry)
diff --git a/include/linux/union.h b/include/linux/union.h
index 0c85312..b035a82 100644
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -46,6 +46,9 @@ extern int append_to_union(struct vfsmount *, struct dentry *,
struct vfsmount *, struct dentry *);
extern int follow_union_down(struct vfsmount **, struct dentry **);
extern int follow_union_mount(struct vfsmount **, struct dentry **);
+extern void __d_drop_unions(struct dentry *);
+extern void shrink_d_unions(struct dentry *);
+extern void __shrink_d_unions(struct dentry *, struct list_head *);

#else /* CONFIG_UNION_MOUNT */

@@ -55,6 +58,9 @@ extern int follow_union_mount(struct vfsmount **, struct dentry **);
#define append_to_union(x1, y1, x2, y2) ({ BUG(); (0); })
#define follow_union_down(x, y) ({ (0); })
#define follow_union_mount(x, y) ({ (0); })
+#define __d_drop_unions(x) do { } while (0)
+#define shrink_d_unions(x) do { } while (0)
+#define __shrink_d_unions(x,y) do { } while (0)

#endif /* CONFIG_UNION_MOUNT */
#endif /* __KERNEL__ */
--
1.6.3.3

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