mirror of
https://github.com/erkyrath/infocom-zcode-terps.git
synced 2026-02-11 10:35:10 +00:00
205 lines
7.3 KiB
C
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 */
|
|
}
|
|
|