summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.bin/more/ch.c100
1 files changed, 65 insertions, 35 deletions
diff --git a/usr.bin/more/ch.c b/usr.bin/more/ch.c
index 711bac8..8bfc009 100644
--- a/usr.bin/more/ch.c
+++ b/usr.bin/more/ch.c
@@ -67,8 +67,10 @@ struct buf {
int nbufs;
/*
- * The buffer pool is kept as a doubly-linked circular list, in order from
- * most- to least-recently used. The circular list is anchored by buf_anchor.
+ * The buffer pool is kept as a doubly-linked circular list. For the ispipe
+ * case, this list will always be ordered from highest-numbered block downto
+ * lowest-numbered block, skipping no blocks. For the !ispipe case,
+ * it may become disordered. It is not clear that this is a feature.
*/
#define END_OF_CHAIN ((struct buf *)&buf_anchor)
#define buf_head buf_anchor.next
@@ -76,7 +78,14 @@ int nbufs;
static struct {
struct buf *next, *prev;
-} buf_anchor = { END_OF_CHAIN, END_OF_CHAIN };
+ long block; /* this is never changed from -1 */
+} buf_anchor = { END_OF_CHAIN, END_OF_CHAIN, (long)-1 };
+
+/*
+ * The last buffer in the circular list that was accessed, and correspondingly
+ * the most likely to be accessed in the future.
+ */
+static struct buf *buf_lastacc = END_OF_CHAIN;
extern int ispipe, cbufs, sigs;
@@ -99,48 +108,46 @@ static off_t last_piped_pos;
* case that the block desired is at the head of the chain.
*/
#define ch_get() \
- ((buf_head->block == ch_block && \
- ch_offset < buf_head->datasize) ? \
- (unsigned char)buf_head->data[ch_offset] : fch_get())
+ ((buf_lastacc->block == ch_block && \
+ ch_offset < buf_lastacc->datasize) ? \
+ (unsigned char)buf_lastacc->data[ch_offset] : fch_get())
static
fch_get()
{
register struct buf *bp;
- register int n, ch;
register char *p, *t;
+ int n, gofor;
off_t pos, lseek();
- /* look for a buffer holding the desired block. */
- for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
+ /*
+ * look for a buffer holding the desired block.
+ */
+ if (abs(buf_lastacc->next->block - ch_block) <
+ abs(buf_lastacc->prev->block - ch_block))
+ gofor = 1; /* Look forwards through the buffer queue */
+ else
+ gofor = 0; /* Look backwards through the buffer queue */
+
+ bp = buf_lastacc;
+ do {
if (bp->block == ch_block) {
+ buf_lastacc = bp;
if (ch_offset >= bp->datasize)
- /*
- * Need more data in this buffer.
- */
goto read_more;
- /*
- * On a pipe, we don't sort the buffers LRU
- * because this can cause gaps in the buffers.
- * For example, suppose we've got 12 1K buffers,
- * and a 15K input stream. If we read the first 12K
- * sequentially, then jump to line 1, then jump to
- * the end, the buffers have blocks 0,4,5,6,..,14.
- * If we then jump to line 1 again and try to
- * read sequentially, we're out of luck when we
- * get to block 1 (we'd get the "pipe error" below).
- * To avoid this, we only sort buffers on a pipe
- * when we actually READ the data, not when we
- * find it already buffered.
- */
- if (ispipe)
- return((unsigned char)bp->data[ch_offset]);
- goto found;
+ return((unsigned char)bp->data[ch_offset]);
}
+ if (gofor)
+ bp = bp->next;
+ else
+ bp = bp->prev;
+ } while (bp != buf_lastacc);
+
/*
- * Block is not in a buffer. Take the least recently used buffer
- * and read the desired block into it. If the LRU buffer has data
- * in it, and input is a pipe, then try to allocate a new buffer first.
+ * Block is not in a buffer. Take the buffer from the tail and
+ * read the desired block into it. If the input is a pipe, we try
+ * to buffer as much input as possible since the input will be
+ * permanently lost if we throw it from the buffer queue.
*/
if (ispipe && buf_tail->block != (long)(-1))
(void)ch_addbuf(1);
@@ -164,6 +171,7 @@ read_more:
/*
* Read the block.
+ *
* If we read less than a full block, we just return the
* partial block and pick up the rest next time.
*/
@@ -185,7 +193,8 @@ read_more:
}
/*
- * Turn EOI (nul) characters into 0200 since EOI has special meaning. */
+ * Turn other EOI (nul) chars into 0200 since EOI has special meaning.
+ */
for (p = &bp->data[bp->datasize]; --n >= 0;) {
--p;
if (*p == EOI)
@@ -195,8 +204,12 @@ read_more:
found:
if (buf_head != bp) {
/*
- * Move the buffer to the head of the buffer chain.
- * This orders the buffer chain, most- to least-recently used.
+ * Move the buffer to the head of the buffer chain. This
+ * ensures correct order for the ispipe case and prevents
+ * needless buffer thrashing for the !ispipe case. It's not
+ * clear that buffer thrashing isn't desirable in this latter
+ * case, since the VM should probably be handling the file
+ * buffer...
*/
bp->next->prev = bp->prev;
bp->prev->next = bp->next;
@@ -219,6 +232,10 @@ found:
/*
* Determine if a specific block is currently in one of the buffers.
+ *
+ * In general, this function is only called for the ispipe case. For the
+ * !ispipe case, ch.c generally assumes that any given block is accessible
+ * through ch_get(), even though ch_get() may not have it buffered.
*/
static
buffered(block)
@@ -226,6 +243,14 @@ buffered(block)
{
register struct buf *bp;
+ /* For the ispipe case, we know that the buffer queue is sequentially
+ * ordered from tail to head. */
+ if (ispipe && (block <= buf_head->block && block >= buf_tail->block))
+ return(1);
+
+ /*
+ * XXX This is dead code.
+ */
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == block)
return(1);
@@ -290,6 +315,11 @@ ch_beg_seek()
/*
* Can't get to position 0.
* Look thru the buffers for the one closest to position 0.
+ *
+ * This should use the obvious optimization that applies for the
+ * ispipe case (which is also the only case under which this
+ * code will be executed, ie. the only case under which ch_seek()
+ * will fail).
*/
firstbp = bp = buf_head;
if (bp == END_OF_CHAIN)
OpenPOWER on IntegriCloud