Files
erkyrath.infocom-zcode-terps/unix/page.c
Andrew Plotkin b642da811e Initial commit.
2023-11-16 18:19:54 -05:00

205 lines
7.3 KiB
C

In the interest of having a general ZIP paging scheme that is faster
than the time stamping scheme used in the PDP-11 version, a linked
list LRU scheme was implemented. The LRU chain is a doubly linked
list of blkdesc structures. Each structure contains a previous and
next pointer, a char ptr to the buffer and a virtual page number for
the page currently residing in the buffer. The global variable MRU
is a pointer to the structure corresponding to the most recently used
page; consequently, mru->prev points to the LRU buffer.
The routine Getpag takes a block number and returns a char pointer
to the start of the corresponding block. It also performs all the
necessary pointer manipulation and paging to make that pointer valid.
/* P A G I N G */
short curblk = -1, /* current block (same as last zpc1) */
curpag = -1; /* last page gotten (from getpag) */
char *curblkloc, /* pointer to curblk block */
*curpagloc; /* pointer to curpag block */
struct blkdesc {
struct blkdesc *next, /* next descriptor ptr */
*prev; /* previous descriptor ptr */
char *loc; /* page pointer */
short vpage; /* page number */
}
pagedesc[MAXBLKS]; /* one descriptor for each virtual page */
struct blkdesc *mru, /* most recently used blkdesc */
*pagemap[MAXBLKS]; /* one mapping for each virtual page */
memini()
{ /* This routine compares memreq with ENDLOD and PLENTH. It
determines how much dataspace to allocate, and does so. It determines
how much data to preload, and does so. It also initializes paging.
*/
char buffer[BLKSIZ]; /* temp space for block 0 */
ZIPINT maxlod;
short i;
char *md_alloc();
/* Read the first block into a temporary buffer. We temporarily set
dataspace to point to this buffer, so that getpre() and the GTV macros
work. */
dataspace = buffer;
getpre(0, 1); /* get block 0 */
endlod = GTVWRD(PENDLD); /* get endlod pointer */
if (endlod & BYTEBITS) THEN
endlod += BLKSIZ; /* round up to next block */
endlod >>= CVTBLK; /* convert to blocks */
maxlod = GTVWRD(PLENTH); /* length of program, in words */
if (maxlod & 0xFF) THEN
maxlod += BLKSIZ/2; /* round up to next block */
maxlod >>= CVTBLK-1; /* convert to blocks */
/* Note that our paging scheme normally requires a minimum of 2 pages in
the chain, one for the current code page and a second for roving pointers.
In the freak case where only one page is not preloaded, however, the
"chain" may contain only one page too. When all pages are preloaded,
paging is never called and no chain at all is required. Thus an array
of MAXBLKS paging structures is the most ever needed.
*/
if (memreq < endlod + 2) THEN
fatal("Insufficient memory for preload");
if (memreq >= maxlod) THEN { /* mucho memory, take advantage */
endlod = maxlod; /* hack endlod to force total preload */
memreq = maxlod; /* reduce memreq to max needed */
}
if ((dataspace = md_alloc(memreq * BLKSIZ)) == NULL) THEN {
printf("Unable to allocate %d", memreq, "blocks");
fatal("Memory allocation error");
}
getpre(0, endlod); /* read in preload data */
/* Currently, an array of blkdescs and a pagemap are declared statically
[0..255]. Should allocate space dynamically for [endlod..memreq-1] only
(number of physical buffers), and a pagemap array for [0..maxlod-1] only
(number of actual pages).
IDEA: call getpre(endlod, memreq) to "prime" the page buffers, and
mark each pagedesc and pagemap appropriately.
*/
if (endlod < maxlod) THEN { /* if total preload, just skip */
for (i = 0; i < MAXBLKS; i++)
pagemap[i] = NOT_IN_CORE; /* no paged pages in core, yet */
for (i = endlod; i < memreq; i++) {
pagedesc[i].next = &pagedesc[i+1]; /* setup pointer chain */
pagedesc[i].prev = &pagedesc[i-1];
pagedesc[i].loc = ((char *)(dataspace + (i * BLKSIZ)));
pagedesc[i].vpage = NO_PAGE;
}
i = memreq - 1;
pagedesc[i].next = &pagedesc[endlod]; /* make the list circular */
pagedesc[endlod].prev = &pagedesc[i]; /* excluding pre and extra */
mru = &pagedesc[i]; /* init mru to last page */
}
}
char *getpag(blk) /* return a pointer to the requested page */
short blk;
{
/* This is the heart of the paging scheme. It manages a doubly-linked
list of block descriptors. Preloaded pages are not included in this
list so they cannot be paged out. If the page requested is preloaded,
a valid pointer is returned immediately.
Otherwise, the block is removed from the linked list, spliced into
the front of the list and made mru. There are two subroutines, unlink
and relink, that manage the linked list.
If the block is not in core, the current mru's->previous (or lru
block) buffer is used to page in the requested block. Then the
information in the corresponding block descriptors is filled to
indicate the absence of the lru and the presence of the new. The mru
pointer is then pointed at this block.
*/
struct blkdesc *lru;
short deadpage;
#if _DEBUG
if (blk >= MAXBLKS) THEN /* valid block request? */
fatal("Virtual page number out of range");
#endif
if (blk == curpag) THEN /* same page as last time */
return(curpagloc); /* return immediately */
if (blk < endlod) THEN { /* preloaded, expand the pointer */
curpag = blk;
curpagloc = dataspace + (blk << CVTBLK);
return(curpagloc);
}
if (pagemap[blk] == NOT_IN_CORE) THEN {
/* When choosing the (lru) page to discard, make sure it's not the
current code page (where the zpc is), otherwise the newzpc buffer pointer
becomes invalid! -- DBB */
lru = mru->prev; /* get oldest page */
deadpage = lru->vpage;
if (deadpage == curblk) THEN { /* but avoid using the zpc page */
lru = lru->prev; /* get next oldest page */
deadpage = lru->vpage;
}
getblk(blk, lru->loc); /* read new page over lru page */
if (deadpage != NO_PAGE) THEN /* mark old page as gone */
pagemap[deadpage] = NOT_IN_CORE;
pagemap[blk] = lru; /* update map for new page */
lru->vpage = blk; /* update desc for new page */
mru = lru; /* update mru */
}
else /* page is resident */
if (pagemap[blk] != mru) THEN { /* if already mru, do nothing */
unlinkb(blk); /* unsplice from wherever it is */
relinkb(blk); /* link it in as new mru */
}
curpag = blk; /* update page globals */
curpagloc = mru->loc;
return(curpagloc); /* return pointer */
}
unlinkb(block)
short block;
{ /* Unlink removes a block descriptor from the lru chain.
*/
struct blkdesc *t1, *t2;
t1 = pagemap[block]->prev; /* get pointer to one end */
t2 = pagemap[block]->next; /* and the other */
t1->next = t2; /* swap pointers */
t2->prev = t1;
}
relinkb(block)
short block;
{ /* Splice a block back into the lru chain (becomes the new mru).
*/
struct blkdesc *newblk, *lru;
newblk = pagemap[block]; /* pointer to the splice block */
lru = mru->prev; /* lru pointer */
newblk->next = mru; /* update new desc's prev and next */
newblk->prev = lru;
mru->prev = newblk; /* update mru and lru descs */
lru->next = newblk;
mru = newblk; /* new mru */
}