-- Copyright (C) 1981, 1982, 1984, 1985 by Xerox Corporation. All rights reserved. -- VMCacheOps.mesa, HGM, 17-Sep-85 2:20:57 -- Last edited by Wobber: 2-Nov-82 10:30:47 -- Last edited by Levin: 30-Apr-81 14:27:42 DIRECTORY Heap USING [systemZone], Inline USING [BITXOR, HighByte, LowByte, LowHalf], VMDefs USING [ CantReadBackingStore, CantWriteBackingStore, Page, PageAddress, PageNumber, Start], VMPrivate USING [ AcquireCache, AcquirePage, AddressToPageIndex, AllocateCacheIndex, CacheIndex, FileHandle, FileObject, FinalizeVMCacheMgr, HandleTable, InitializeVMCacheMgr, PageIndex, nilCacheIndex, PageHandle, ReleaseCache, ReleasePage, ValidateFile, ValidatePageNumber, WaitUntilStable]; VMCacheOps: PROGRAM IMPORTS Heap, Inline, VMDefs, VMPrivate EXPORTS VMDefs, VMPrivate = BEGIN OPEN VMDefs, VMPrivate; -- Types and Related Constants -- handles: LONG POINTER TO HandleTable; hashTable: PACKED ARRAY HashIndex OF CacheIndex; PageMap: TYPE = PACKED ARRAY PageIndex OF CacheIndex; pageMap: PageMap; hashBins: CARDINAL = 97; HashIndex: TYPE = [0..hashBins); -- Miscellaneous Declarations -- AttemptToReusePageMapSlot: ERROR = CODE; BadAddress: ERROR = CODE; IllegalCacheIndex: ERROR = CODE; IllegalPageState: ERROR = CODE; InconsistentHandleMap: ERROR = CODE; InconsistentHashTable: ERROR = CODE; InconsistentPageMap: ERROR = CODE; -- Procedures and Signals Exported to VMDefs -- FileObject: PUBLIC TYPE = VMPrivate.FileObject; -- Page Object Allocation -- AllocatePage: PUBLIC PROCEDURE RETURNS [Page] = BEGIN index: CacheIndex = AllocateCacheIndex[]; page: PageHandle = IndexToHandle[index]; page.useCount ← 1; page.file ← NIL; page.age ← new; page.dirty ← FALSE; page.state ← stable; AcquireCache[]; EnterInPageTable[page, index]; ReleaseCache[]; RETURN[page.pointer] END; Release: PUBLIC PROCEDURE [page: Page] = BEGIN p: PageHandle; AcquireCache[]; p ← AddressToHandle[page]; IF p.useCount = 0 THEN ERROR IllegalPageState; IF (p.useCount ← p.useCount - 1) = 0 AND p.file = NIL THEN RemoveFromPageTable[p]; ReleaseCache[]; END; Deactivate: PUBLIC PROCEDURE [page: Page] = BEGIN p: PageHandle; AcquireCache[]; p ← AddressToHandle[page]; SELECT p.useCount FROM 0 => ERROR IllegalPageState; 1 => IF ~p.dirty THEN RemovePageFromTables[p] ELSE {p.age ← old; ReleaseCache[]; VMDefs.Start[page]; AcquireCache[]}; ENDCASE; p.useCount ← p.useCount - 1; ReleaseCache[]; END; -- Core/Disk Mapping -- UsePage: PUBLIC PROCEDURE [addr: PageAddress] RETURNS [Page] = BEGIN oldindex, newindex: CacheIndex; page: PageHandle; ValidateAddress[addr]; AcquireCache[]; IF (oldindex ← LookupInHashTable[addr]) = nilCacheIndex THEN BEGIN ReleaseCache[]; page ← IndexToHandle[newindex ← AllocateCacheIndex[]]; page.file ← addr.file; page.page ← addr.page; AcquireCache[]; page.state ← stable; IF (oldindex ← LookupInHashTable[addr]) = nilCacheIndex THEN { EnterInPageTable[page, newindex]; EnterInHashTable[page]} ELSE -- someone slipped the requested page in ahead us; abandon our page {page.file ← NIL; page ← IndexToHandle[oldindex]}; END ELSE page ← IndexToHandle[oldindex]; AcquirePage[page]; page.dirty ← FALSE; page.useCount ← page.useCount + 1; MarkPageUsableAndRelease[page]; RETURN[page.pointer] END; RemapPage: PUBLIC PROCEDURE [page: Page, addr: PageAddress] = BEGIN p: PageHandle; index: CacheIndex; ValidateAddress[addr]; AcquireCache[]; p ← AddressToHandle[page]; AcquirePage[p]; IF p.useCount # 1 THEN ERROR IllegalPageState; index ← LookupInHashTable[addr]; IF index # nilCacheIndex THEN BEGIN -- previous contents of 'addr' is in cache oldPage: PageHandle = IndexToHandle[index]; IF oldPage = p THEN {MarkPageUsableAndRelease[p]; RETURN}; oldPage.useCount ← oldPage.useCount + 1; ReleaseCache[]; IF WaitUntilStable[ oldPage, writing ! CantReadBackingStore, CantWriteBackingStore => CONTINUE] = unstable THEN ReleasePage[oldPage]; AcquireCache[]; -- throw away old contents, even if dirty IF (oldPage.useCount ← oldPage.useCount - 1) = 0 THEN { IF ~oldPage.beingTaken THEN RemovePageFromTables[oldPage]} ELSE ERROR BadAddress; END; RemoveFromHashTable[p]; p.file ← addr.file; p.page ← addr.page; p.dirty ← TRUE; EnterInHashTable[p]; MarkPageUsableAndRelease[p]; END; UnmapPage: PUBLIC PROCEDURE [page: Page] = BEGIN p: PageHandle; AcquireCache[]; p ← AddressToHandle[page]; AcquirePage[p]; IF p.useCount # 1 THEN ERROR IllegalPageState; RemoveFromHashTable[p]; p.file ← NIL; MarkPageUsableAndRelease[p]; END; PageToPageAddress: PUBLIC PROCEDURE [page: Page] RETURNS [PageAddress] = BEGIN p: PageHandle; addr: PageAddress; AcquireCache[]; p ← AddressToHandle[page]; AcquirePage[p]; IF p.useCount = 0 OR p.file = NIL THEN ERROR IllegalPageState; addr ← [p.file, p.page]; ReleasePage[p]; ReleaseCache[]; RETURN[addr] END; -- Procedures and Signals Exported to VMPrivate -- -- Start/Stop -- InitializeVMCache: PUBLIC PROCEDURE [min, max: CacheIndex, first: LONG POINTER] = BEGIN handles ← Heap.systemZone.NEW[HandleTable [max]]; FOR index: CacheIndex IN [0..max) DO handles[index] ← NIL; ENDLOOP; FOR vmpage: PageIndex IN PageIndex DO pageMap[vmpage] ← nilCacheIndex; ENDLOOP; FOR bin: HashIndex IN HashIndex DO hashTable[bin] ← nilCacheIndex; ENDLOOP; InitializeVMCacheMgr[handles, min, max, first]; END; FinalizeVMCache: PUBLIC PROCEDURE = { FinalizeVMCacheMgr[]; Heap.systemZone.FREE[@handles]}; -- Handle Table Management -- AddressToHandle: PUBLIC PROCEDURE [address: VMDefs.Page] RETURNS [PageHandle] = BEGIN RETURN[IndexToHandle[pageMap[AddressToPageIndex[address]]]]; END; IndexToHandle: PUBLIC PROCEDURE [index: CacheIndex] RETURNS [handle: PageHandle] = BEGIN IF index = nilCacheIndex THEN ERROR IllegalCacheIndex; IF (handle ← handles[index]) = NIL THEN ERROR InconsistentHandleMap; RETURN END; -- Hash Table Management -- LookupInHashTable: PUBLIC PROCEDURE [addr: PageAddress] RETURNS [index: CacheIndex] = BEGIN index ← hashTable[Hash[addr]]; UNTIL index = nilCacheIndex DO page: PageHandle ← IndexToHandle[index]; IF page.file = addr.file AND page.page = addr.page THEN RETURN; index ← page.hashLink; ENDLOOP; RETURN END; EnterInHashTable: PUBLIC PROCEDURE [page: PageHandle] = BEGIN bin: HashIndex = Hash[PageAddress[page.file, page.page]]; page.hashLink ← hashTable[bin]; hashTable[bin] ← pageMap[page.index]; IncrementFileUseCount[page.file]; END; -- Page Table Management -- BogusPageIndex: ERROR = CODE; EnterInPageTable: PUBLIC PROCEDURE [page: PageHandle, index: CacheIndex] = BEGIN IF index > PageIndex.LAST THEN ERROR BogusPageIndex; IF pageMap[page.index] # nilCacheIndex THEN ERROR AttemptToReusePageMapSlot; pageMap[page.index] ← index; END; -- Miscellaneous Procedures -- RemovePageFromTables: PUBLIC PROCEDURE [page: PageHandle] = BEGIN RemoveFromHashTable[page]; RemoveFromPageTable[page]; page.file ← NIL; END; -- Internal Procedures -- -- Hash Table Management -- RemoveFromHashTable: PROCEDURE [page: PageHandle] = -- delinks the argument page from the hash table. BEGIN ci: CacheIndex = pageMap[page.index]; bin: HashIndex; IF page.file = NIL THEN RETURN; bin ← Hash[PageAddress[page.file, page.page]]; IF hashTable[bin] = nilCacheIndex THEN GO TO badTable; IF ci = hashTable[bin] THEN hashTable[bin] ← page.hashLink ELSE BEGIN prev: PageHandle ← IndexToHandle[hashTable[bin]]; UNTIL prev.hashLink = nilCacheIndex DO IF prev.hashLink = ci THEN {prev.hashLink ← page.hashLink; EXIT}; prev ← IndexToHandle[prev.hashLink]; REPEAT FINISHED => GO TO badTable; ENDLOOP; END; DecrementFileUseCount[page.file]; EXITS badTable => ERROR InconsistentHashTable; END; Hash: PROCEDURE [addr: PageAddress] RETURNS [HashIndex] = -- produces HashIndex for 'addr' into hashTable. BEGIN XORBytes: PROCEDURE [word: UNSPECIFIED] RETURNS [UNSPECIFIED] = INLINE -- XORs the two bytes of 'word'. BEGIN RETURN[Inline.BITXOR[Inline.HighByte[word], Inline.LowByte[word]]]; END; RETURN[XORBytes[Inline.BITXOR[Inline.LowHalf[addr.file], addr.page]] MOD hashBins] END; -- Page Table Management -- RemoveFromPageTable: PROCEDURE [page: PageHandle] = -- destroys the correspondance between page 'page.pointer' and the page object. BEGIN IF IndexToHandle[pageMap[page.index]] # page THEN ERROR InconsistentPageMap; pageMap[page.index] ← nilCacheIndex; END; -- Miscellaneous Procedures -- ValidateAddress: PROCEDURE [addr: PageAddress] = BEGIN ValidateFile[addr.file]; ValidatePageNumber[addr.page]; END; MarkPageUsableAndRelease: PROCEDURE [page: PageHandle] = BEGIN page.age ← new; page.errorStatus ← ok; ReleasePage[page]; ReleaseCache[]; END; IncrementFileUseCount: PROCEDURE [file: FileHandle] = INLINE { file.useCount ← file.useCount + 1}; DecrementFileUseCount: PROCEDURE [file: FileHandle] = INLINE { file.useCount ← file.useCount - 1}; END.