updated dcache/inode patch

Bill Hawes (whawes@star.net)
Sun, 16 Nov 1997 10:33:57 -0500


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

The attached patch for dcache.c and inode.c updates the dcache and inode
management code. The patch is against 2.1.63 but should go cleanly into
2.1.64.

The main change to the dcache management in select_dcache is to use an
explicit dentry timestamp rather than the inode i_atime to detect when
recently-unused dentries are being cannibalized. This improves
operation in cases where i_atime may not have been changed (e.g. file
find) or when i_atime isn't being updated.

I've also added a call to shrink_dcache_parent in d_invalidate,
contingent on d_count > 1 and the subdirs list not empty. This should
improve operation for filesystems needing dentry invalidation (nfs,
smbfs, more to come?).

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

--- linux-2.1.63/fs/dcache.c.old Wed Nov 12 17:34:12 1997
+++ linux-2.1.63/fs/dcache.c Thu Nov 13 14:55:53 1997
@@ -118,6 +118,11 @@
}
list_add(&dentry->d_lru, &dentry_unused);
dentry_stat.nr_unused++;
+ /*
+ * Update the timestamp
+ */
+ dentry->d_reftime = jiffies;
+
out:
if (count >= 0) {
dentry->d_count = count;
@@ -135,15 +140,12 @@
* 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.
- *
- * 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 might want to do a partial shrink_dcache here */
+ /* Check whether to do a partial shrink_dcache */
+ if (dentry->d_count > 1 && !list_empty(&dentry->d_subdirs))
+ shrink_dcache_parent(dentry);
if (dentry->d_count != 1)
return -EBUSY;

@@ -152,27 +154,31 @@
}

/*
- * Selects less valuable dentries to be pruned when
- * we need inodes or memory. The selected dentries
- * are moved to the old end of the list where
- * prune_dcache() can find them.
- */
-int select_dcache(int count, int page_count)
-{
- struct list_head *tail = &dentry_unused;
- struct list_head *next = dentry_unused.prev;
- int forward = 0, young = 0, depth = dentry_stat.nr_unused >> 1;
- int found = 0, pages = 0;
+ * Select less valuable dentries to be pruned when we need
+ * inodes or memory. The selected dentries are moved to the
+ * old end of the list where prune_dcache() can find them.
+ *
+ * Negative dentries are included in the selection so that
+ * they don't accumulate at the end of the list. The count
+ * returned is the total number of dentries selected, which
+ * may be much larger than the requested number of inodes.
+ */
+int select_dcache(int inode_count, int page_count)
+{
+ struct list_head *next, *tail = &dentry_unused;
+ int found = 0, forward = 0, young = 8;
+ int depth = dentry_stat.nr_unused >> 1;
+ unsigned long min_value = 0, max_value = 4;

-#ifdef DCACHE_DEBUG
-printk("select_dcache: %d unused, count=%d, pages=%d\n",
-dentry_stat.nr_unused, count, page_count);
-#endif
+ if (page_count)
+ max_value = -1;
+
+ next = tail->prev;
while (next != &dentry_unused && depth--) {
struct list_head *tmp = next;
struct dentry *dentry = list_entry(tmp, struct dentry, d_lru);
struct inode *inode = dentry->d_inode;
- unsigned long value = 0;
+ unsigned long value = 0;

next = tmp->prev;
if (forward)
@@ -184,56 +190,57 @@
continue;
}
/*
- * Select dentries based on the page cache count ...
- * should factor in number of uses as well.
+ * Check the dentry's age to see whether to change direction.
*/
- if (inode) {
- if (inode->i_state)
- continue;
- value = inode->i_nrpages;
- }
- /*
- * Consider various exemptions ...
- */
- if (!page_count) {
- if (!inode)
- continue;
- if (value >= 3)
- continue;
- } else if (!forward) {
- if (inode) {
- int age = CURRENT_TIME - inode->i_atime;
- if (age < dentry_stat.age_limit) {
- if (++young > 8) {
- forward = 1;
- next = dentry_unused.next;
+ if (!forward) {
+ int age = (jiffies - dentry->d_reftime) / HZ;
+ if (age < dentry_stat.age_limit) {
+ if (!--young) {
+ forward = 1;
+ next = dentry_unused.next;
+ /*
+ * Update the limits -- we don't want
+ * files with too few or too many pages.
+ */
+ if (page_count) {
+ min_value = 3;
+ max_value = 15;
+ }
#ifdef DCACHE_DEBUG
-printk("select_dcache: age=%d, pages=%d, scanning forward\n", age, pages);
+printk("select_dcache: %s/%s age=%d, scanning forward\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, age);
#endif
- }
- continue;
}
+ continue;
}
- } else {
- /*
- * If we're scanning from the front, don't take
- * files with only a trivial amount of memory.
- */
- if (value < 3 || value > 15)
+ }
+
+ /*
+ * Select dentries based on the page cache count ...
+ * should factor in number of uses as well. We take
+ * all negative dentries so that they don't accumulate.
+ * (We skip inodes that aren't immediately available.)
+ */
+ if (inode) {
+ value = inode->i_nrpages;
+ if (value >= max_value || value < min_value)
+ continue;
+ if (inode->i_state || inode->i_count > 1)
continue;
}
+
/*
- * Move the dentry behind the tail
+ * Move the selected dentries behind the tail.
*/
if (tmp != tail->prev) {
list_del(tmp);
list_add(tmp, tail->prev);
}
tail = tmp;
- pages += value;
- if (++found >= count)
+ found++;
+ if (inode && --inode_count <= 0)
break;
- if (page_count && pages >= page_count)
+ if (page_count && (page_count -= value) <= 0)
break;
}
return found;
@@ -430,7 +437,7 @@
if (goal) {
if (goal > 50)
goal = 50;
- count = select_dcache(128, goal);
+ count = select_dcache(32, goal);
#ifdef DCACHE_DEBUG
printk("check_dcache_memory: goal=%d, count=%d\n", goal, count);
#endif
@@ -453,7 +460,7 @@
* Prune the dcache if there are too many unused dentries.
*/
if (dentry_stat.nr_unused > 3*(nr_inodes >> 1)) {
-#ifdef DCACHE_PARANOIA
+#ifdef DCACHE_DEBUG
printk("d_alloc: %d unused, pruning dcache\n", dentry_stat.nr_unused);
#endif
prune_dcache(8);
--- linux-2.1.63/fs/inode.c.old Wed Nov 12 17:32:13 1997
+++ linux-2.1.63/fs/inode.c Wed Nov 12 18:12:22 1997
@@ -358,33 +384,30 @@
*/
static void try_to_free_inodes(int goal)
{
- int retried = 0, found;
+ int retry = 1, found;

/*
* Check whether to preshrink the dcache ...
*/
- if (inodes_stat.preshrink) {
- spin_unlock(&inode_lock);
- select_dcache(goal, 0);
- prune_dcache(goal);
- spin_lock(&inode_lock);
- }
+ if (inodes_stat.preshrink)
+ goto preshrink;

-repeat:
- found = free_inodes(goal);
-
- /*
- * If we didn't free any inodes, do a limited
- * pruning of the dcache to help the next time.
- */
- if (!found) {
+ retry = 0;
+ do {
+ if (free_inodes(goal))
+ break;
+ /*
+ * If we didn't free any inodes, do a limited
+ * pruning of the dcache to help the next time.
+ */
+ preshrink:
spin_unlock(&inode_lock);
- select_dcache(goal, 0);
- prune_dcache(goal);
+ found = select_dcache(goal, 0);
+ if (found < goal)
+ found = goal;
+ prune_dcache(found);
spin_lock(&inode_lock);
- if (inodes_stat.preshrink && !retried++)
- goto repeat;
- }
+ } while (retry--);
}

/*
@@ -440,11 +463,11 @@
* If the allocation failed, do an extensive pruning of
* the dcache and then try again to free some inodes.
*/
- prune_dcache(128);
+ prune_dcache(inodes_stat.nr_inodes >> 2);
inodes_stat.preshrink = 1;

spin_lock(&inode_lock);
- free_inodes(128);
+ free_inodes(inodes_stat.nr_inodes >> 2);
{
struct list_head *tmp = inode_unused.next;
if (tmp != &inode_unused) {

--------------98E1E89DCDC63FF68A7C2F38--