-- File: VMIO.mesa
-- Last edited by Wobber: 2-Nov-82 10:38:19
-- Last edited by Gobbel: 18-May-81 12:18:20
DIRECTORY
FileDefs USING [ComparePositions, Completer, FileHandle, PageNumber],
Inline USING [LowHalf],
LogDefs USING [DisplayNumber, Percentage],
VMDefs USING [
CantReadBackingStore, Error, LookAheadCount, Page, PageAddress, Position,
Problem],
VMPrivate USING [
AcquireCache, AcquirePage, AllocateCacheIndex, CacheIndex, EnterInHashTable,
EnterInPageTable, FileHandle, FileObject, IndexToHandle, LookupInHashTable,
loggingEnabled, MDSPageNumber, MDSPageToAddress, nilCacheIndex, PageHandle,
ReleaseCache, ReleasePage, ValidateFile, ValidatePageNumber, WaitUntilStable];
VMIO: PROGRAM
IMPORTS FileDefs, Inline, LogDefs, VMDefs, VMPrivate EXPORTS VMDefs, VMPrivate =
BEGIN OPEN VMDefs, VMPrivate;
-- Statistics Logging --
readPageCalls, readPageCacheHits: LONG CARDINAL;
cacheHitPercent: LogDefs.Percentage;
-- Miscellaneous Declarations --
BadAddress: ERROR = CODE;
WaitUntilStableFailed: ERROR = CODE;
-- Procedures, Signals, and Types Exported to VMDefs --
FileObject: PUBLIC TYPE = VMPrivate.FileObject;
ReadPage: PUBLIC PROCEDURE [addr: PageAddress, lookAhead: LookAheadCount ← 0]
RETURNS [Page] =
BEGIN
page: PageHandle;
FixUseCount: PROCEDURE = INLINE
-- decrements page.useCount after an error.
BEGIN
AcquirePage[page]; -- needn't remap 'addr' because useCount > 0
page.useCount ← page.useCount - 1;
ReleasePage[page];
END;
ValidateAddressAndAcquireCache[addr];
DoReads[addr, lookAhead, TRUE];
ReleaseCache[];
page ← IndexToHandle[LookupInHashTable[addr]]; -- Lookup can't fail; useCount > 0
IF loggingEnabled THEN
BEGIN -- approximately correct, since page.state may be changing
readPageCalls ← readPageCalls + 1;
IF page.state = stable THEN readPageCacheHits ← readPageCacheHits + 1;
cacheHitPercent ← Inline.LowHalf[(readPageCacheHits * 100) / readPageCalls];
END;
IF WaitUntilStable[page, reading ! CantReadBackingStore => FixUseCount[]] #
stable THEN ERROR WaitUntilStableFailed;
RETURN[MDSPageToAddress[page.buffer]];
END;
StartReading: PUBLIC PROCEDURE [
addr: PageAddress, lookAhead: LookAheadCount ← 0] =
BEGIN
ValidateAddressAndAcquireCache[addr];
DoReads[addr, lookAhead, FALSE];
ReleaseCache[];
END;
-- Procedures and Signals Exported to VMPrivate --
-- Page I/O --
WritePageToFS: PUBLIC PROCEDURE [page: PageHandle, wait: BOOLEAN] =
BEGIN
vmFile: FileHandle = page.file;
fFile: FileDefs.FileHandle = vmFile.fh;
DO
-- loops only in the case that the write is never started (which can't
-- happen if extending)
extending: BOOLEAN;
newLength: Position;
[extending, newLength] ← ValidatePageAddress[page];
page.errorStatus ← ok;
IF extending THEN
BEGIN
vmFile.fs.ops.extend[
fFile, newLength, MDSPageToAddress[page.buffer] !
VMDefs.Error => {page.errorStatus ← reason; CONTINUE}];
IF page.errorStatus = ok THEN page.dirty ← FALSE;
ReleasePage[page];
END
ELSE
vmFile.fs.ops.startWrite[
fFile, page.page, MDSPageToAddress[page.buffer], FSWriteComplete, page];
IF ~wait OR WaitUntilStable[page, writing] = stable THEN EXIT;
ENDLOOP;
END;
-- Start/Stop --
InitializeVMIO: PUBLIC PROCEDURE =
BEGIN
IF loggingEnabled THEN
BEGIN
readPageCalls ← readPageCacheHits ← cacheHitPercent ← 0;
LogDefs.DisplayNumber["VM Cache Hits"L, [percent[@cacheHitPercent]]];
END;
END;
FinalizeVMIO: PUBLIC PROCEDURE = {NULL};
-- Miscellaneous --
ValidatePageAddress: PUBLIC PROCEDURE [page: PageHandle]
RETURNS [extending: BOOLEAN, newLength: Position] =
BEGIN
vmFile: FileHandle = page.file;
length: Position = vmFile.fs.ops.getLength[vmFile.fh];
newLength ← [page.page + 1, 0];
IF ~(extending ← FileDefs.ComparePositions[newLength, length] = greater) THEN
RETURN;
IF newLength.page ~= length.page + 1 THEN ERROR BadAddress;
END;
-- Internal Procedures --
-- Page Input --
DoReads: PROCEDURE [
addr: PageAddress, lookAhead: LookAheadCount, bump: BOOLEAN] =
-- does the common part of ReadPage and StartReading. If 'bump' is TRUE, the
-- useCount for the page corresponding to 'addr' will be incremented (this prevents
-- it from being stolen by any look-ahead).
BEGIN
vmFile: FileHandle = addr.file;
fileLength: Position = vmFile.fs.ops.getLength[vmFile.fh];
pg: FileDefs.PageNumber;
actualReads: CARDINAL;
IF (pg ← addr.page + (IF fileLength.byte = 0 THEN 1 ELSE 0)) > fileLength.page
THEN ERROR BadAddress;
actualReads ← MIN[lookAhead, fileLength.page - pg] + 1;
THROUGH [0..actualReads) DO
page: PageHandle;
oldindex, newindex: CacheIndex;
BEGIN
IF (oldindex ← LookupInHashTable[addr]) # nilCacheIndex THEN
GO TO AlreadyIn;
ReleaseCache[];
newindex ← AllocateCacheIndex[];
AcquireCache[];
page ← IndexToHandle[newindex];
IF (oldindex ← LookupInHashTable[addr]) # nilCacheIndex THEN {
page.state ← stable; GO TO AlreadyIn};
page.file ← addr.file;
page.page ← addr.page;
page.dirty ← FALSE;
EnterInPageTable[page.buffer, newindex];
EnterInHashTable[page];
vmFile.fs.ops.startRead[
vmFile.fh, page.page, MDSPageToAddress[page.buffer], FSReadComplete,
page];
EXITS AlreadyIn => {page ← IndexToHandle[oldindex]};
END;
page.age ← new;
IF bump THEN {page.useCount ← page.useCount + 1; bump ← FALSE};
addr.page ← addr.page + 1;
ENDLOOP;
END;
FSReadComplete: FileDefs.Completer =
-- handles termination of remote file read operations initiated by DoReads.
BEGIN
page: PageHandle = LOOPHOLE[arg];
page.errorStatus ← outcome;
ReleasePage[page];
END;
-- Page Output --
FSWriteComplete: FileDefs.Completer =
-- handles termination of remote file write operations initiated by WritePageToFS.
BEGIN
page: PageHandle = LOOPHOLE[arg];
IF (page.errorStatus ← outcome) = ok THEN page.dirty ← FALSE;
ReleasePage[page];
END;
-- Miscellaneous --
ValidateAddressAndAcquireCache: PROCEDURE [addr: PageAddress] =
BEGIN
ValidateFile[addr.file];
ValidatePageNumber[addr.page];
AcquireCache[];
END;
END.