patch for 2.1.50 refill_freelist

Bill Hawes (whawes@star.net)
Fri, 15 Aug 1997 11:53:28 -0400


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

With the ongoing discussion of 2.0.31 buffers, this seems like a good
time to post one further refinement to 2.1.50 refill_freelist. This
corrects the main remaining deficiency by preventing a short-term buffer
shortage from depleting the system's reserved pages.

The problem with the current implementation is that it doesn't wait for
locked buffers to unlock before resorting to atomic allocation to create
new buffers. If the system has many locked (or dirty) buffers and
suddenly needs more buffers, the reserved pages can be quickly depleted,
when all that was needed was to write out the dirty buffers and wait for
them to unlock. (The call to wakeup_bdflush(1) doesn't mean "wait for
them to unlock", it just means "wait for them to be written out".)

This patch corrects this with the following changes:

(1) If the attempts to free or allocate buffers without using reserved
pages have failed (free list still empty), check whether any buffers are
dirty, and wake bdflush before attempting to use reserved pages.

(2) After waking bdflush, any dirty buffers will have become locked.
Before using reserved pages, find an unused locked buffer and wait for
it to unlock. After it unlocks, loop back to harvest it.

(3) If for some reason we still can't get a buffer, use just one
reserved page and then return.

(4) Finally (and most unlikely), do a schedule() and keep trying.

I've been testing this since 2.1.47 by doing multiple make -j3 compiles
in 6M, and rarely have to use even one reserved page. By protecting the
reserved pages, we can ensure that other parts of the system don't
become unstable because of low memory.

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

--- fs/buffer.c.old Thu Aug 14 18:44:05 1997
+++ fs/buffer.c Thu Aug 14 20:23:07 1997
@@ -687,6 +691,7 @@
int buffers[BUF_DIRTY];
int i;
int needed, obtained=0;
+ int locked_wait=0;

refilled = 1;

@@ -777,36 +782,81 @@
}

/*
- * Make one more attempt to allocate some buffers.
+ * After this point, we only care about getting this task
+ * out of here ... return if there are any free buffers.
*/
- if (grow_buffers(GFP_ATOMIC, size))
- obtained += PAGE_SIZE;
+ if (free_list[BUFSIZE_INDEX(size)])
+ return;

/*
- * If we got any buffers, or another task freed some,
- * return now to let this task proceed.
+ * If there are dirty buffers, write them out now before trying
+ * to convert reserved pages into buffers. The dirty buffers
+ * become locked, and so still aren't immediately available,
+ * but this should decrease the chance of future calls having
+ * to use the reserved pages.
*/
- if (obtained || free_list[BUFSIZE_INDEX(size)]) {
+ if (nr_buffers_type[BUF_DIRTY])
+ wakeup_bdflush(1);
+
+ /*
+ * In order to prevent a buffer shortage from quickly exhausting
+ * the system's reserved pages, we force tasks to wait before
+ * using reserved pages for buffers. This is easily accomplished
+ * by finding an unused locked buffer and waiting on it.
+ */
+ if (!locked_wait && (bh = lru_list[BUF_LOCKED]) != NULL) {
+ locked_wait = 1;
+ for (i = nr_buffers_type[BUF_LOCKED]; i--; bh = bh->b_next_free)
+ {
+ if (bh->b_size != size)
+ continue;
+ if (bh->b_count)
+ continue;
+ if (!buffer_locked(bh))
+ continue;
+ if (buffer_dirty(bh) || buffer_protected(bh))
+ continue;
+ /*
+ * We've found an unused, locked, non-dirty buffer of
+ * the correct size. Claim it so no one else can,
+ * then wait for it to unlock.
+ */
+ bh->b_count++;
+ wait_on_buffer(bh);
+ bh->b_count--;
+ /*
+ * Loop back to harvest this (and maybe other) buffers.
+ */
+ goto repeat;
+ }
#ifdef BUFFER_DEBUG
-printk("refill_freelist: obtained %d of %d, free list=%d\n",
-obtained, needed, free_list[BUFSIZE_INDEX(size)] != NULL);
+printk("refill_freelist: no available locked buffers\n");
#endif
- return;
}
+ /*
+ * The locked_wait flag is toggled on alternate passes, so that
+ * if for some reason the existing buffers are still unavailable,
+ * eventually the reserved pages can be used for buffers.
+ */
+ locked_wait = 0;

/*
- * System is _very_ low on memory ... wake up bdflush and wait.
+ * Convert a reserved page into buffers ... should happen only rarely.
*/
+ if (grow_buffers(GFP_ATOMIC, size)) {
#ifdef BUFFER_DEBUG
-printk("refill_freelist: waking bdflush\n");
+printk("refill_freelist: used reserve page\n");
#endif
- wakeup_bdflush(1);
+ return;
+ }
+
/*
- * While we slept, other tasks may have needed buffers and entered
- * refill_freelist. This could be a big problem ... reset the
- * needed amount to the absolute minimum.
+ * System is _very_ low on memory ... sleep and try later.
*/
- needed = size;
+#ifdef BUFFER_DEBUG
+printk("refill_freelist: waiting for buffers\n");
+#endif
+ schedule();
goto repeat;
}

--------------9F975334DFD27E709CBB0D0B--