-- File: VMIO.mesa
-- Last edited by Levin: 12-Jul-82 16:16:46
-- Last edited by Brotz: January 28, 1983 1:46 PM
DIRECTORY
AltoFile USING [
EnterDiskAddress, FileToAltoPageNumber, GetFileID, MapPageToDiskAddress,
PageNumber],
DiskIODefs USING [
CompletionStatus, DiskRequest, fillInvDA, InitiateDiskIO, vDA,
VerboseCompletionProcedure, XferSpec],
FileDefs USING [Completer, FileHandle, PageNumber],
Inline USING [LowHalf],
LogDefs USING [DisplayNumber, Percentage],
SegmentDefs USING [memConfig],
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, ObjectHandle,
PageHandle, ReleaseCache, ReleasePage, ValidateFile, ValidatePageNumber,
WaitUntilStable, WriteEnable];
VMIO: PROGRAM
IMPORTS AltoFile, DiskIODefs, Inline, LogDefs, SegmentDefs, VMDefs, VMPrivate
EXPORTS VMDefs, VMPrivate =
BEGIN OPEN VMDefs, VMPrivate;
-- Statistics Logging --
readPageCalls, readPageCacheHits: LONG CARDINAL;
cacheHitPercent: LogDefs.Percentage;
-- Miscellaneous Declarations --
BadAddress: ERROR = CODE;
maxLookAhead: LookAheadCount = 11; -- sectors/track-1
-- 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;
IF SegmentDefs.memConfig.AltoType = D0 THEN lookAhead ← 0; --DKB D0 fix.
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;
UNTIL WaitUntilStable[page, reading ! CantReadBackingStore => FixUseCount[]] = stable
DO
-- The read was never started, a situation that can only occur if the page in
-- in question is on the local disk. The page is in the hash and page tables and
-- the use count is non-zero, but the buffer has invalid content and the page
-- object is unstable. If multiple readers attempt to access this page, mutual
-- exclusion is assured by WaitUntilStable if it returns FALSE, since it leaves
-- 'page' unstable.
request: DiskIODefs.DiskRequest;
xferSpec: ARRAY [0..1) OF DiskIODefs.XferSpec;
vmFile: FileHandle = page.file;
SetUpDiskRequest[@request, vmFile];
PrepareXfer[@xferSpec[0], page];
request.xfers ← DESCRIPTOR[@xferSpec, 1];
IssueDiskRequest[@request, vmFile];
ENDLOOP;
RETURN[MDSPageToAddress[page.buffer]]
END;
StartReading: PUBLIC PROCEDURE [addr: PageAddress, lookAhead: LookAheadCount ← 0] =
BEGIN
IF SegmentDefs.memConfig.AltoType = D0 THEN RETURN; --DKB D0 fix.
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;
IF SegmentDefs.memConfig.AltoType = D0 THEN wait ← TRUE; -- DKB D0 fix.
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
IF vmFile.altoFile THEN
BEGIN
nearPage: AltoFile.PageNumber;
nearvDA: DiskIODefs.vDA;
xferSpec: ARRAY [0..1) OF DiskIODefs.XferSpec;
knownPages: CARDINAL;
request: DiskIODefs.DiskRequest ←
[firstPage:, fileID: AltoFile.GetFileID[fFile], firstPagevDA:,
pagesToSkip:, nonXferID: vmFile, xfers: DESCRIPTOR[@xferSpec, 1],
proc: [verbose[AltoWriteComplete]], noRestore: FALSE, command: WriteD[]];
xferSpec[0] ← [MDSPageToAddress[page.buffer], DiskIODefs.fillInvDA, page];
[nearPage, nearvDA, knownPages] ←
AltoFile.MapPageToDiskAddress[fFile, page.page, 1];
request.firstPage ← AltoFile.FileToAltoPageNumber[nearPage];
request.firstPagevDA ← nearvDA;
IF (request.pagesToSkip ← page.page - nearPage) = 0 THEN
{xferSpec[0].diskAddress ← nearvDA; page.recordNextVda ← (knownPages = 1)};
DiskIODefs.InitiateDiskIO[@request];
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];
SELECT TRUE FROM
page.page > length.page => ERROR BadAddress;
page.page = length.page AND length.byte = 0 => RETURN[TRUE, [page.page + 1, 0]];
ENDCASE => RETURN[FALSE, length];
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 OPEN DiskIODefs;
vmFile: FileHandle = addr.file;
fileLength: Position = vmFile.fs.ops.getLength[vmFile.fh];
pg: FileDefs.PageNumber;
actualReads: CARDINAL;
xferSpecs: ARRAY [0..maxLookAhead] OF XferSpec;
slot: [0..maxLookAhead];
request: DiskRequest;
FlushReads: PROCEDURE =
-- initiates accumulated read requests.
BEGIN
IF vmFile.altoFile AND slot > 0 THEN
BEGIN
request.xfers ← DESCRIPTOR[@xferSpecs, slot];
IssueDiskRequest[@request, vmFile];
slot ← 0;
END;
END;
IF (pg ← addr.page + (IF fileLength.byte = 0 THEN 1 ELSE 0)) > fileLength.page
THEN ERROR BadAddress;
actualReads ← MIN[lookAhead, fileLength.page - pg, maxLookAhead] + 1;
IF vmFile.altoFile THEN {slot ← 0; SetUpDiskRequest[@request, vmFile]};
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];
IF vmFile.altoFile THEN {PrepareXfer[@xferSpecs[slot], page]; slot ← slot + 1}
ELSE
vmFile.fs.ops.startRead[vmFile.fh, page.page, MDSPageToAddress[page.buffer],
FSReadComplete, page];
EXITS AlreadyIn => {page ← IndexToHandle[oldindex]; FlushReads[]};
END;
page.age ← new;
IF bump THEN
BEGIN
IF page.useCount = 0 THEN WriteEnable[page.buffer];
page.useCount ← page.useCount + 1;
bump ← FALSE;
END;
addr.page ← addr.page + 1;
ENDLOOP;
FlushReads[];
END;
SetUpDiskRequest: PROCEDURE [
request: POINTER TO DiskIODefs.DiskRequest, vmFile: FileHandle] =
-- initializes a disk request block for the given file.
BEGIN
request↑ ← DiskIODefs.DiskRequest[
firstPage:, fileID: AltoFile.GetFileID[vmFile.fh], firstPagevDA:,
pagesToSkip:, nonXferID: vmFile, xfers:, proc: [verbose[AltoReadComplete]],
noRestore: FALSE, command: ReadD[]];
END;
PrepareXfer: PROCEDURE [xfer: POINTER TO DiskIODefs.XferSpec, page: PageHandle] =
-- builds an XferSpec record for the given page and prepares the page object for
-- the transfer.
BEGIN OPEN DiskIODefs;
page.errorStatus ← ok;
page.recordNextVda ← FALSE;
xfer↑ ← XferSpec[MDSPageToAddress[page.buffer], fillInvDA, page];
END;
IssueDiskRequest: PROCEDURE [
request: POINTER TO DiskIODefs.DiskRequest, vmFile: FileHandle] =
-- completes the disk request block and initiates the I/O.
BEGIN OPEN AltoFile, DiskIODefs;
nearPage: PageNumber;
nearvDA: vDA;
knownPages: CARDINAL;
firstRead: PageNumber = LOOPHOLE[request.xfers[0].id, PageHandle].page;
[nearPage, nearvDA, knownPages] ←
MapPageToDiskAddress[vmFile.fh, firstRead, LENGTH[request.xfers]];
request.firstPage ← FileToAltoPageNumber[nearPage];
request.firstPagevDA ← nearvDA;
IF (request.pagesToSkip ← firstRead - nearPage) = 0 THEN
BEGIN
request.xfers[0].diskAddress ← nearvDA;
FOR i: CARDINAL IN [knownPages-1..LENGTH[request.xfers]) DO
LOOPHOLE[request.xfers[i].id, PageHandle].recordNextVda ← TRUE;
ENDLOOP;
END;
InitiateDiskIO[request];
END;
AltoReadComplete: DiskIODefs.VerboseCompletionProcedure =
-- handles termination of local file read operations initiated by DoReads.
BEGIN OPEN AltoFile;
WITH obj: LOOPHOLE[id, ObjectHandle] SELECT FROM
file => -- i.e., a page we passed over on the way
IF status = ok THEN
EnterDiskAddress[obj.fh, label.page + 1, label.next ! ANY => CONTINUE];
page => -- i.e., a page we read
BEGIN
IF (obj.errorStatus ← MapAltoStatusToProblem[status]) = ok THEN
IF obj.recordNextVda THEN
BEGIN
EnterDiskAddress[obj.file.fh, label.page+1, label.next ! ANY => CONTINUE];
obj.recordNextVda ← FALSE;
END;
ReleasePage[@obj];
END;
ENDCASE;
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 --
AltoWriteComplete: DiskIODefs.VerboseCompletionProcedure =
-- handles termination of Alto file write operations initiated by WritePageToFS.
BEGIN OPEN AltoFile;
WITH obj: LOOPHOLE[id, ObjectHandle] SELECT FROM
file => -- i.e., a page we passed over on the way
IF status = ok THEN
EnterDiskAddress[obj.fh, label.page + 1, label.next ! ANY => CONTINUE];
page => -- i.e., the page we wrote
BEGIN
IF (obj.errorStatus ← MapAltoStatusToProblem[status]) = ok THEN
BEGIN
obj.dirty ← FALSE;
IF obj.recordNextVda THEN
BEGIN
EnterDiskAddress[obj.file.fh, label.page+1, label.next ! ANY => CONTINUE];
obj.recordNextVda ← FALSE;
END;
END;
ReleasePage[@obj];
END;
ENDCASE;
END;
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;
MapAltoStatusToProblem: PROCEDURE [status: DiskIODefs.CompletionStatus]
RETURNS [Problem] = INLINE
{RETURN[SELECT status FROM ok => ok, neverStarted => other, ENDCASE => io]};
-- Miscellaneous --
ValidateAddressAndAcquireCache: PROCEDURE [addr: PageAddress] =
BEGIN
ValidateFile[addr.file];
ValidatePageNumber[addr.page];
AcquireCache[];
END;
END.