tunable refill_freelist patch for 2.1.42

Bill Hawes (whawes@star.net)
Tue, 08 Jul 1997 06:58:21 -0400


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

The attached patch against 2.1.42 contains the accumulated patches I've
been doing for 2.0.30, and refines the flexible refill_freelist code.
Using the new refill code speeds up simultaneous compiles in low memory
by almost 10 percent, so it seems to be working pretty well. (And I
have yet to see it have to wake up bdflush ...)

The flexible refill is on by default, but can be turned off using one of
the unused bdflush parameters. The update -4 0 command will turn it
off, and update -4 1 turns it back on again.

The patch also incorporates a more fair buffer selection algorithm that
compares lru times at each each step to select the next buffer to be
freed, and now searches the full locked list rather than just up to the
first locked buffer. Using some debugging code I observed on a number
of occasions unlocked buffers occurring after the first locked one.

Please check this out and let me know of any problems ...

-Bill
--------------F4FE4169BCB25549B034779E
Content-Type: text/plain; charset=us-ascii; name="buffer-patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="buffer-patch"

--- fs/buffer.c.old Thu Jun 26 14:03:37 1997
+++ fs/buffer.c Mon Jul 7 18:18:35 1997
@@ -98,7 +98,7 @@
each time we call refill */
int nref_dirt; /* Dirty buffer threshold for activating bdflush
when trying to refill buffers. */
- int dummy1; /* unused */
+ int flex_refill; /* Flexible refill policy switch */
int age_buffer; /* Time for normal buffer to age before
we flush it */
int age_super; /* Time for superblock to age before we
@@ -107,11 +107,11 @@
int dummy3; /* unused */
} b_un;
unsigned int data[N_PARAM];
-} bdf_prm = {{60, 500, 64, 256, 15, 30*HZ, 5*HZ, 1884, 2}};
+} bdf_prm = {{60, 500, 64, 256, 1, 30*HZ, 5*HZ, 1884, 2}};

/* These are the min and max parameter values that we will allow to be assigned */
-int bdflush_min[N_PARAM] = { 0, 10, 5, 25, 0, 100, 100, 1, 1};
-int bdflush_max[N_PARAM] = {100,5000, 2000, 2000,100, 60000, 60000, 2047, 5};
+int bdflush_min[N_PARAM] = { 0, 10, 5, 25, 0, 100, 100, 1, 1};
+int bdflush_max[N_PARAM] = {100,5000, 2000, 2000, 1, 60000, 60000, 2047, 5};

/*
* Rewrote the wait-routines to use the "new" wait-queue functionality,
@@ -605,34 +605,20 @@
}
}

-/* Check if a buffer is OK to be reclaimed. */
-static inline int can_reclaim(struct buffer_head *bh, int size)
-{
- if (bh->b_count ||
- buffer_protected(bh) ||
- buffer_locked(bh))
- return 0;
-
- if (buffer_dirty(bh)) {
- refile_buffer(bh);
- return 0;
- }
-
- if (bh->b_size != size)
- return 0;
-
- return 1;
-}
-
-/* Find a candidate buffer to be reclaimed. */
-static struct buffer_head *find_candidate(struct buffer_head *list,
+/*
+ * Find a candidate buffer to be reclaimed.
+ * WSH 07/07/97: Changed to fully search the BUF_LOCKED list rather than
+ * terminating when a locked buffer is found. Buffers are unlocked at
+ * completion of IO, and under some conditions there may be unlocked
+ * buffers after the first locked one.
+ */
+static struct buffer_head *find_candidate(struct buffer_head *bh,
int *list_len, int size)
{
- struct buffer_head *bh;
-
- for (bh = list;
- bh && (*list_len) > 0;
- bh = bh->b_next_free, (*list_len)--) {
+ if (!bh)
+ return NULL;
+
+ for (; (*list_len) > 0; bh = bh->b_next_free, (*list_len)--) {
if (size != bh->b_size) {
/* This provides a mechanism for freeing blocks
* of other sizes, this is necessary now that we
@@ -643,54 +629,61 @@
break;
continue;
}
-
- if (buffer_locked(bh) && bh->b_list == BUF_LOCKED) {
- /* Buffers are written in the order they are placed
- * on the locked list. If we encounter a locked
- * buffer here, this means that the rest of them
- * are also locked.
- */
- (*list_len) = 0;
- return NULL;
- }
-
- if (can_reclaim(bh,size))
- return bh;
+ else if (!bh->b_count &&
+ !buffer_locked(bh) &&
+ !buffer_protected(bh) &&
+ !buffer_dirty(bh))
+ return bh;
}

return NULL;
}

+
static void refill_freelist(int size)
{
- struct buffer_head * bh;
+ struct buffer_head * bh, * next;
struct buffer_head * candidate[BUF_DIRTY];
- unsigned int best_time, winner;
int buffers[BUF_DIRTY];
int i;
- int needed;
+ int needed, obtained=0;

refilled = 1;
- /* If there are too many dirty buffers, we wake up the update process
- * now so as to ensure that there are still clean buffers available
- * for user processes to use (and dirty).
- */

/* We are going to try to locate this much memory. */
needed = bdf_prm.b_un.nrefill * size;

- while ((nr_free_pages > min_free_pages*2) &&
- (needed > 0) &&
- grow_buffers(GFP_BUFFER, size))
- needed -= PAGE_SIZE;
+ while ((nr_free_pages > min_free_pages*2) &&
+ grow_buffers(GFP_BUFFER, size)) {
+ obtained += PAGE_SIZE;
+ if (obtained >= needed)
+ return;
+ }

+ /*
+ * Check whether the flexible refill policy is in effect, and if
+ * so update the needed amount. We don't want to free more than
+ * one quarter of the (potentially) available buffers.
+ */
+ if (bdf_prm.b_un.flex_refill) {
+ i = nr_buffers_type[BUF_CLEAN] + nr_buffers_type[BUF_LOCKED];
+ i = i >> 2;
+ if (i < bdf_prm.b_un.nrefill) {
+ needed = i * size;
+ if (needed < PAGE_SIZE)
+ needed = PAGE_SIZE;
+ }
+ }
+
+ /*
+ * OK, we cannot grow the buffer cache, now try to get some
+ * from the lru list.
+ */
repeat:
- if(needed <= 0)
+ if (obtained >= needed)
return;

- /* OK, we cannot grow the buffer cache, now try to get some
- * from the lru list.
- *
+ /*
* First set the candidate pointers to usable buffers. This
* should be quick nearly all of the time.
*/
@@ -700,53 +693,72 @@
candidate[i] = find_candidate(lru_list[i], &buffers[i], size);
}

- /* Now see which candidate wins the election. */
-
- winner = best_time = UINT_MAX;
- for(i=0; i<BUF_DIRTY; i++) {
- if(!candidate[i])
- continue;
- if(candidate[i]->b_lru_time < best_time) {
- best_time = candidate[i]->b_lru_time;
- winner = i;
- }
- }
-
- /* If we have a winner, use it, and then get a new candidate from that list. */
- if(winner != UINT_MAX) {
- i = winner;
- while (needed>0 && (bh=candidate[i])) {
- candidate[i] = bh->b_next_free;
- if(candidate[i] == bh)
- candidate[i] = NULL; /* Got last one */
- remove_from_queues(bh);
- bh->b_dev = B_FREE;
- put_last_free(bh);
- needed -= bh->b_size;
- buffers[i]--;
- if(buffers[i] == 0)
- candidate[i] = NULL;
-
- if (candidate[i] && !can_reclaim(candidate[i],size))
- candidate[i] = find_candidate(candidate[i],
- &buffers[i], size);
- }
- goto repeat;
- }
+ /*
+ * Select the older of the available buffers until we reach our goal.
+ */
+ for (;;) {
+ i = BUF_CLEAN;
+ if (!candidate[BUF_CLEAN]) {
+ if (!candidate[BUF_LOCKED])
+ goto next_try;
+ i = BUF_LOCKED;
+ }
+ else if (candidate[BUF_LOCKED] &&
+ (candidate[BUF_LOCKED]->b_lru_time <
+ candidate[BUF_CLEAN ]->b_lru_time))
+ i = BUF_LOCKED;
+ /*
+ * Free the selected buffer and get the next candidate.
+ */
+ bh = candidate[i];
+ next = bh->b_next_free;
+
+ obtained += bh->b_size;
+ remove_from_queues(bh);
+ put_last_free(bh);
+ if (obtained >= needed)
+ return;
+
+ if (--buffers[i] == 0 || bh == next)
+ candidate[i] = NULL;
+ else
+ candidate[i] = find_candidate(next, &buffers[i], size);
+ }
+
+next_try:
+ /*
+ * If we achieved at least half of our goal, return now.
+ */
+ if (bdf_prm.b_un.flex_refill && obtained >= (needed >> 1))
+ return;

/* Too bad, that was not enough. Try a little harder to grow some. */
if (nr_free_pages > min_free_pages + 5) {
if (grow_buffers(GFP_BUFFER, size)) {
- needed -= PAGE_SIZE;
+ obtained += PAGE_SIZE;
goto repeat;
}
}
-
- /* And repeat until we find something good. */
+
+ /*
+ * Make one last attempt to allocate some buffers ...
+ */
if (grow_buffers(GFP_ATOMIC, size))
- needed -= PAGE_SIZE;
- else
- wakeup_bdflush(1);
+ obtained += PAGE_SIZE;
+
+ /*
+ * If we got any buffers, return now to let this task proceed.
+ */
+ if (bdf_prm.b_un.flex_refill && obtained) {
+printk("refill_freelist: returning some obtained=%d\n", obtained);
+ return;
+ }
+
+ /*
+ * System is _very_ low on memory ... wake up bdflush and wait.
+ */
+printk("refill_freelist: waking bdflush\n");
+ wakeup_bdflush(1);
goto repeat;
}

@@ -873,13 +893,11 @@

/* If dirty, mark the time this buffer should be written back. */
set_writetime(buf, 0);
- refile_buffer(buf);
-
- if (buf->b_count) {
+ if (buf->b_count)
buf->b_count--;
- return;
- }
- printk("VFS: brelse: Trying to free free buffer\n");
+ else
+ printk("VFS: brelse: Trying to free free buffer\n");
+ refile_buffer(buf);
}

/*
@@ -1000,6 +1018,27 @@
wake_up(&buffer_wait);
}

+/*
+ * We can't put completed temporary IO buffer_heads directly onto the
+ * unused_list when they become unlocked, since the device driver
+ * end_request routines still expect access to the buffer_head's
+ * fields after the final unlock. So, the device driver puts them on
+ * the reuse_list instead once IO completes, and we recover these to
+ * the unused_list here.
+ */
+static inline void recover_reusable_buffer_heads(void)
+{
+ struct buffer_head *head;
+
+ head = xchg(&reuse_list, NULL);
+
+ while (head) {
+ struct buffer_head *bh = head;
+ head = head->b_next_free;
+ put_unused_buffer_head(bh);
+ }
+}
+
static void get_more_buffer_heads(void)
{
struct buffer_head * bh;
@@ -1021,31 +1060,11 @@
*/
run_task_queue(&tq_disk);
sleep_on(&buffer_wait);
+ recover_reusable_buffer_heads();
}

}

-/*
- * We can't put completed temporary IO buffer_heads directly onto the
- * unused_list when they become unlocked, since the device driver
- * end_request routines still expect access to the buffer_head's
- * fields after the final unlock. So, the device driver puts them on
- * the reuse_list instead once IO completes, and we recover these to
- * the unused_list here.
- */
-static inline void recover_reusable_buffer_heads(void)
-{
- struct buffer_head *head;
-
- head = xchg(&reuse_list, NULL);
-
- while (head) {
- struct buffer_head *bh = head;
- head = head->b_next_free;
- put_unused_buffer_head(bh);
- }
-}
-
static struct buffer_head * get_unused_buffer_head(void)
{
struct buffer_head * bh;
@@ -1226,6 +1245,7 @@
free_async_buffers(bh);
restore_flags(flags);
after_unlock_page(page);
+ wake_up(&buffer_wait);
}
++current->maj_flt;
return 0;
@@ -1462,8 +1487,7 @@
void show_buffers(void)
{
struct buffer_head * bh;
- int found = 0, locked = 0, dirty = 0, used = 0, lastused = 0;
- int protected = 0;
+ int found, locked, dirty, used, lastused, lastlocked, protected;
int nlist;
static char *buf_types[NR_LIST] = {"CLEAN","LOCKED","DIRTY"};

@@ -1472,14 +1496,14 @@
printk("Buffer blocks: %6d\n",nr_buffers);

for(nlist = 0; nlist < NR_LIST; nlist++) {
- found = locked = dirty = used = lastused = protected = 0;
+ found = locked = dirty = used = lastused = lastlocked = protected = 0;
bh = lru_list[nlist];
if(!bh) continue;

do {
found++;
if (buffer_locked(bh))
- locked++;
+ locked++, lastlocked = found;
if (buffer_protected(bh))
protected++;
if (buffer_dirty(bh))
@@ -1489,9 +1513,9 @@
bh = bh->b_next_free;
} while (bh != lru_list[nlist]);
printk("%8s: %d buffers, %d used (last=%d), "
- "%d locked, %d protected, %d dirty\n",
+ "%d locked (last=%d), %d protected, %d dirty\n",
buf_types[nlist], found, used, lastused,
- locked, protected, dirty);
+ locked, lastlocked, protected, dirty);
};
}

--------------F4FE4169BCB25549B034779E--