-- 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.