implementation of dentry child lists

Bill Hawes (whawes@star.net)
Tue, 04 Nov 1997 12:43:36 -0500


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

I've attached a preliminary implementation of dentry child lists along
with a new shrink_dcache_parent(). It seems to be working OK, so
hopefully I haven't forgotten anything important.

The approach in shrink_dcache_parent is to define a select_parent
routine to move all unused child dentries to the end of the unused
list. Select_parent does a complete search of the child lists,
descending each time it finds a non-empty subdirs list. When it
finishes, it reports the number of dentries moved, and prune_dcache()
does the work.

One possible criticism of this approach is that an unused dentry could
go back into use while others are being pruned, and then prune_dcache
would free an extra dentry. It's not incorrect, only inefficient in
that we're freeing an unselected dentry. But the alternative would
require pruning each child dentry as we find it and then restarting the
whole search process.

I'm open to any criticisms or comments, and will continue testing the
code (right now only smbfs is using it here.) I also plan to start
going through the various filesystems and adding calls to
shrink_dcache_parent(), as it should be used before every rmdir
operation.

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

--- linux-2.1.61/include/linux/dcache.h.old Fri Oct 31 00:26:08 1997
+++ linux-2.1.61/include/linux/dcache.h Tue Nov 4 13:06:28 1997
@@ -57,10 +57,13 @@
struct dentry * d_covers;
struct list_head d_hash; /* lookup hash list */
struct list_head d_lru; /* d_count = 0 LRU list */
+ struct list_head d_child; /* child of parent list */
+ struct list_head d_subdirs; /* our children */
struct qstr d_name;
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block * d_sb; /* The root of the dentry tree */
+ unsigned long d_reftime; /* last time referenced */
};

struct dentry_operations {
@@ -115,6 +118,7 @@
extern struct dentry * d_alloc(struct dentry * parent, const struct qstr *name);
extern void prune_dcache(int);
extern void shrink_dcache_sb(struct super_block *);
+extern void shrink_dcache_parent(struct dentry *);
extern int d_invalidate(struct dentry *);

#define shrink_dcache() prune_dcache(0)
--- linux-2.1.61/fs/dcache.c.old Fri Oct 31 00:26:07 1997
+++ linux-2.1.61/fs/dcache.c Tue Nov 4 12:56:11 1997
@@ -97,12 +97,14 @@
goto out;
}

- if (!list_empty(&dentry->d_lru))
+ if (!list_empty(&dentry->d_lru)) {
dentry_stat.nr_unused--;
- list_del(&dentry->d_lru);
+ list_del(&dentry->d_lru);
+ }
if (list_empty(&dentry->d_hash)) {
struct inode *inode = dentry->d_inode;
struct dentry * parent;
+ list_del(&dentry->d_child);
if (inode) {
dentry->d_inode = NULL;
iput(inode);
@@ -247,6 +259,7 @@
struct dentry * parent;

list_del(&dentry->d_hash);
+ list_del(&dentry->d_child);
if (dentry->d_inode) {
struct inode * inode = dentry->d_inode;

@@ -338,6 +351,69 @@
}

/*
+ * Search the dentry child list for the specified parent,
+ * and move any unused dentries to the end of the unused
+ * list for prune_dcache(). We descend to the next level
+ * whenever the d_subdirs list is non-empty and continue
+ * searching.
+ */
+static int select_parent(struct dentry * parent)
+{
+ struct dentry *this_parent = parent;
+ struct list_head *next;
+ int found = 0;
+
+repeat:
+ next = this_parent->d_subdirs.next;
+resume:
+ while (next != &this_parent->d_subdirs) {
+ struct list_head *tmp = next;
+ struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
+ next = tmp->next;
+ if (!dentry->d_count) {
+ list_del(&dentry->d_lru);
+ list_add(&dentry->d_lru, dentry_unused.prev);
+ found++;
+ }
+ /*
+ * Descend a level if the d_subdirs list is non-empty.
+ */
+ if (!list_empty(&dentry->d_subdirs)) {
+ this_parent = dentry;
+#ifdef DCACHE_DEBUG
+printk("select_parent: descending to %s/%s, found=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, found);
+#endif
+ goto repeat;
+ }
+ }
+ /*
+ * All done at this level ... ascend and resume the search.
+ */
+ if (this_parent != parent) {
+ next = this_parent->d_child.next;
+ this_parent = this_parent->d_parent;
+#ifdef DCACHE_DEBUG
+printk("select_parent: ascending to %s/%s, found=%d\n",
+this_parent->d_parent->d_name.name, this_parent->d_name.name, found);
+#endif
+ goto resume;
+ }
+ return found;
+}
+
+/*
+ * Prune the dcache to remove unused children of the parent dentry.
+ */
+void shrink_dcache_parent(struct dentry * parent)
+{
+ int found;
+
+ while ((found = select_parent(parent)) != 0)
+ prune_dcache(found);
+}
+
+/*
* This is called from do_try_to_free_page() to indicate
* that we should reduce the dcache and inode cache memory.
*/
@@ -415,11 +491,15 @@
if (parent) {
dentry->d_parent = dget(parent);
dentry->d_sb = parent->d_sb;
- }
+ list_add(&dentry->d_child, &parent->d_subdirs);
+ } else
+ INIT_LIST_HEAD(&dentry->d_child);
+
dentry->d_mounts = dentry;
dentry->d_covers = dentry;
INIT_LIST_HEAD(&dentry->d_hash);
INIT_LIST_HEAD(&dentry->d_lru);
+ INIT_LIST_HEAD(&dentry->d_subdirs);

dentry->d_name.name = str;
dentry->d_name.len = name->len;
@@ -608,11 +688,16 @@
list_del(&target->d_hash);
INIT_LIST_HEAD(&target->d_hash);

+ list_del(&dentry->d_child);
+ list_del(&target->d_child);
+
/* Switch the parents and the names.. */
switch(dentry->d_parent, target->d_parent);
switch(dentry->d_name.name, target->d_name.name);
switch(dentry->d_name.len, target->d_name.len);
switch(dentry->d_name.hash, target->d_name.hash);
+ list_add(&target->d_child, &target->d_parent->d_subdirs);
+ list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
}

/*

--------------DE45FB8B8C092E93BA46323A--