updated 2.1.57 dcache patch

Bill Hawes (whawes@star.net)
Sat, 27 Sep 1997 16:04:33 -0400


This is a multi-part message in MIME format.
--------------2163E485DB4402BE97AD2CD6
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

The attached patch is an update for some of the dcache extensions I've
been working on. The changes are:

(1) A shrink_dcache_sb routine for more efficient unmounting and inode
invalidation. This is a generalization of a previously-posted patch.

(2) A shrink_dcache_parent routine to remove child dentries of the
specified parent. This is used when a dentry is being invalidated,
currently done only for NFS and smbfs.

(3) A change in dput() to make it safe to block in the d_op->d_delete
operation. This is done by calling d_delete before decrementing
d_count, so that no harm is done if the dentry goes back into use.

The patch also includes a change to fs/super.c to call the new
shrink_dcache_sb routine at unmount time.

As the shrink_dcache_parent code is currently exercised only by
invalidating an inode, I'd appreciate some testing from people using NFS
and smbfs. I've left some debugging printks enabled; just turn them off
if there are too many messages.

Regards,
Bill
--------------2163E485DB4402BE97AD2CD6
Content-Type: text/plain; charset=us-ascii; name="dcache_57-patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="dcache_57-patch"

--- linux-2.1.57/fs/dcache.c.old Sat Sep 20 08:16:14 1997
+++ linux-2.1.57/fs/dcache.c Sat Sep 27 15:10:39 1997
@@ -64,6 +64,15 @@
if (dentry) {
int count;
repeat:
+ if (dentry->d_count == 1) {
+ /*
+ * Note that if d_op->d_delete blocks,
+ * the dentry could go back in use.
+ * Each fs will have to watch for this.
+ */
+ if (dentry->d_op && dentry->d_op->d_delete)
+ dentry->d_op->d_delete(dentry);
+ }
count = dentry->d_count-1;
if (count < 0) {
printk("Negative d_count (%d) for %s/%s\n",
@@ -75,8 +84,6 @@
dentry->d_count = count;
if (!count) {
list_del(&dentry->d_lru);
- if (dentry->d_op && dentry->d_op->d_delete)
- dentry->d_op->d_delete(dentry);
if (list_empty(&dentry->d_hash)) {
struct inode *inode = dentry->d_inode;
struct dentry * parent;
@@ -98,14 +105,20 @@
* Try to invalidate the dentry if it turns out to be
* possible. If there are other users of the dentry we
* can't invalidate it.
- *
- * This is currently incorrect. We should try to see if
- * we can invalidate any unused children - right now we
- * refuse to invalidate way too much.
*/
int d_invalidate(struct dentry * dentry)
{
- /* We should do a partial shrink_dcache here */
+ /*
+ * Check whether to do a partial shrink of the dcache.
+ */
+ if (dentry->d_count != 1 &&
+ dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
+#if 1
+printk("d_invalidate: %s/%s count=%d, pruning children\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
+#endif
+ shrink_dcache_parent(dentry);
+ }
if (dentry->d_count != 1)
return -EBUSY;

@@ -114,6 +127,45 @@
}

/*
+ * Check whether a dentry is a child of the parent.
+ */
+static int d_is_child(struct dentry * dentry, struct dentry * parent)
+{
+ int is_child = 1;
+#if 1
+printk("d_is_child: %s/%s is ", dentry->d_parent->d_name.name,
+dentry->d_name.name);
+#endif
+ for ( ;; ) {
+ if (dentry == parent)
+ break;
+ if (dentry != dentry->d_parent) {
+ dentry = dentry->d_parent;
+ continue;
+ }
+ is_child = 0;
+ break;
+ }
+#if 1
+printk("%s child of %s/%s\n", (is_child ? "a" : "not a"),
+parent->d_parent->d_name.name, parent->d_name.name);
+#endif
+ return is_child;
+}
+
+/*
+ * This does the dirty work. Since dentries are put on
+ * the unused list because they're hashed at dput() time,
+ * we just unhash it and dput() again.
+ */
+static inline void prune_one_dentry(struct dentry * dentry)
+{
+ dentry->d_count++;
+ d_drop(dentry);
+ dput(dentry);
+}
+
+/*
* Shrink the dcache. This is done when we need
* more memory, or simply when we need to unmount
* something (at which point we need to unuse
@@ -131,22 +183,84 @@
INIT_LIST_HEAD(tmp);
dentry = list_entry(tmp, struct dentry, d_lru);
if (!dentry->d_count) {
- struct dentry * parent;
-
- list_del(&dentry->d_hash);
- if (dentry->d_inode) {
- struct inode * inode = dentry->d_inode;
-
- dentry->d_inode = NULL;
- iput(inode);
- }
- parent = dentry->d_parent;
- d_free(dentry);
- dput(parent);
+ prune_one_dentry(dentry);
if (!--count)
break;
}
}
+}
+
+/*
+ * Prune the dcache for the super block and parent dentry.
+ * The super block provides a quick no-go parenthood test.
+ *
+ * This implementation makes just two traversals of the
+ * unused list. On the first pass we move the selected
+ * dentries to the most recent end, and on the second
+ * pass we free them. The second pass must restart after
+ * each dput(), but since the target dentries are all at
+ * the end, it's really just a single traversal.
+ */
+static void __prune_dcache_sb(struct super_block * sb, struct dentry * parent)
+{
+ struct list_head *tmp, *next;
+ struct dentry *dentry;
+
+ /*
+ * Pass one ... move the dentries for the specified
+ * superblock to the most recent end of the unused list.
+ */
+ next = dentry_unused.next;
+ while (next != &dentry_unused) {
+ tmp = next;
+ next = tmp->next;
+ dentry = list_entry(tmp, struct dentry, d_lru);
+ if (dentry->d_sb != sb)
+ continue;
+ if (parent && !d_is_child(dentry, parent))
+ continue;
+ list_del(tmp);
+ list_add(tmp, &dentry_unused);
+ }
+
+ /*
+ * Pass two ... free the dentries for this superblock.
+ */
+repeat:
+ next = dentry_unused.next;
+ while (next != &dentry_unused) {
+ tmp = next;
+ next = tmp->next;
+ dentry = list_entry(tmp, struct dentry, d_lru);
+ if (dentry->d_sb != sb)
+ continue;
+ if (dentry->d_count)
+ continue;
+ if (parent && !d_is_child(dentry, parent))
+ continue;
+ prune_one_dentry(dentry);
+ goto repeat;
+ }
+}
+
+/*
+ * Shrink the dcache for the specified super block.
+ * This allows us to unmount a device without disturbing
+ * the dcache for the other devices.
+ */
+void shrink_dcache_sb(struct super_block *sb)
+{
+ __prune_dcache_sb(sb, NULL);
+}
+
+/*
+ * Shrink the dcache for the specified parent dentry.
+ * This is used to remove children of a renamed or
+ * invalidated dentry.
+ */
+void shrink_dcache_parent(struct dentry * parent)
+{
+ __prune_dcache_sb(parent->d_sb, parent);
}

#define NAME_ALLOC_LEN(len) ((len+16) & ~15)
--- linux-2.1.57/fs/super.c.old Fri Sep 26 08:10:51 1997
+++ linux-2.1.57/fs/super.c Fri Sep 26 08:48:53 1997
@@ -635,7 +635,7 @@
* root entry should be in use and (b) that root entry is
* clean.
*/
- shrink_dcache();
+ shrink_dcache_sb(sb);
fsync_dev(dev);

retval = d_umount(sb);
--- linux-2.1.57/include/linux/dcache.h.old Sat Sep 20 08:16:16 1997
+++ linux-2.1.57/include/linux/dcache.h Sat Sep 27 14:59:15 1997
@@ -104,6 +104,8 @@
/* allocate/de-allocate */
extern struct dentry * d_alloc(struct dentry * parent, const struct qstr *name);
extern void prune_dcache(int);
+extern void shrink_dcache_parent(struct dentry *);
+extern void shrink_dcache_sb(struct super_block *);
extern int d_invalidate(struct dentry *);

#define shrink_dcache() prune_dcache(0)

--------------2163E485DB4402BE97AD2CD6--