patch-2.1.58 linux/fs/dcache.c

Next file: linux/fs/file_table.c
Previous file: linux/fs/binfmt_misc.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.57/linux/fs/dcache.c linux/fs/dcache.c
@@ -61,37 +61,54 @@
  */
 void dput(struct dentry *dentry)
 {
-	if (dentry) {
-		int count;
+	int count;
+
+	if (!dentry)
+		return;
+
 repeat:
-		count = dentry->d_count-1;
-		if (count < 0) {
-			printk("Negative d_count (%d) for %s/%s\n",
-				count,
-				dentry->d_parent->d_name.name,
-				dentry->d_name.name);
-			*(int *)0 = 0;
-		}
+	count = dentry->d_count - 1;
+	if (count != 0)
+		goto out;
+
+	/*
+	 * 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)
+			goto out;
+	}
+
+	list_del(&dentry->d_lru);
+	if (list_empty(&dentry->d_hash)) {
+		struct inode *inode = dentry->d_inode;
+		struct dentry * parent;
+		if (inode)
+			iput(inode);
+		parent = dentry->d_parent;
+		d_free(dentry);
+		if (dentry == parent)
+			return;
+		dentry = parent;
+		goto repeat;
+	}
+	list_add(&dentry->d_lru, &dentry_unused);
+out:
+	if (count >= 0) {
 		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;
-				if (inode)
-					iput(inode);
-				parent = dentry->d_parent;
-				d_free(dentry);
-				if (dentry == parent)
-					return;
-				dentry = parent;
-				goto repeat;
-			}
-			list_add(&dentry->d_lru, &dentry_unused);
-		}
+		return;
 	}
+
+	printk("Negative d_count (%d) for %s/%s\n",
+		count,
+		dentry->d_parent->d_name.name,
+		dentry->d_name.name);
+	*(int *)0 = 0;	
 }
 
 /*
@@ -99,13 +116,14 @@
  * 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.
+ * We should probably try to see if we can invalidate
+ * any unused children - right now we refuse to invalidate
+ * too much. That would require a better child list
+ * data structure, though.
  */
 int d_invalidate(struct dentry * dentry)
 {
-	/* We should do a partial shrink_dcache here */
+	/* We might want to do a partial shrink_dcache here */
 	if (dentry->d_count != 1)
 		return -EBUSY;
 
@@ -114,6 +132,27 @@
 }
 
 /*
+ * Throw away a dentry - free the inode, dput the parent.
+ * This requires that the LRU list has already been
+ * removed.
+ */
+static inline void prune_one_dentry(struct dentry * dentry)
+{
+	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);
+}
+
+/*
  * 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,21 +170,62 @@
 		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;
 		}
+	}
+}
+
+/*
+ * Shrink the dcache for the specified super block.
+ * This allows us to unmount a device without disturbing
+ * the dcache for the other devices.
+ *
+ * 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.
+ */
+void shrink_dcache_sb(struct super_block * sb)
+{
+	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;
+		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;
+		list_del(tmp);
+		INIT_LIST_HEAD(tmp);
+		prune_one_dentry(dentry);
+		goto repeat;
 	}
 }
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov