-- Transport Mechanism Filestore - object directory -- -- ObjectDir.mesa -- Andrew Birrell 10-Jun-81 10:46:42 -- -- M. D. Schroeder 7-Feb-83 15:34:06 -- -- Hankins 26-Jul-84 16:31:35 Klamath update (make some proc var's so HeapRestart can access private procs. DIRECTORY HeapFileDefs USING [ObjectAbandoned], ObjectDirXDefs USING [DOPC, ObjectNumber, ObjectState], ObjectDirDefs USING [noObject, ObjectType], PolicyDefs USING [GapExists], VMDefs USING [ Page, pageSize, PageNumber, FileHandle, PageIndex, ReadPage, UsePage, Mark, MarkStart, Release, FullAddress]; ObjectDir: MONITOR IMPORTS HeapFileDefs, PolicyDefs, VMDefs EXPORTS ObjectDirDefs, ObjectDirXDefs SHARES ObjectDirDefs = BEGIN -- proc var's for HeapRestart: Find: TYPE = PROC [obj: ObjectDirXDefs.ObjectNumber] RETURNS [data: POINTER TO DirData]; Get: TYPE = PROCEDURE [reqd: ObjDirPageNumber]; CountTooBig: TYPE = ERROR; NotInUse: TYPE = ERROR; Release: TYPE = PROCEDURE [d: CacheState] ← ReleaseData; findData: Find ← FindData; getDirPage: Get ← GetDirPage; objectCountTooBig: CountTooBig ← ObjectCountTooBig; objectNotInUse: NotInUse ← ObjectNotInUse; releaseData: Release ← ReleaseData; -- Reference counts -- ObjectCount: TYPE = CARDINAL; zeroCount: ObjectCount = 0; oneCount: ObjectCount = zeroCount + 1; -- Structure of object directory pages -- handle: VMDefs.FileHandle; -- set by HeapRestart -- -- Object directory is sequence of pages, each of type "DirPage" -- Pages containing unused indexes are on "firstFreePage" chain -- Unused indexes within page are in header.nextFreeIndex chain -- Unused indexes are not usable until lastDofpc is high enough -- See ObjectDirDefs comments about DOPC. -- There may be pages on "firstFreePage" chain having no free indexes -- Object directory page numbers (and markers) fit into 8 bits! ObjDirPageSpace: TYPE = [0..255]; unchained: ObjDirPageSpace = LAST[ObjDirPageSpace]; -- "not on free chain" endOfChain: ObjDirPageSpace = PRED[unchained]; -- "end of free chain" ObjDirPageNumber: TYPE = [FIRST[ObjDirPageSpace]..PRED[endOfChain]]; DirData: TYPE = RECORD [ SELECT freedom: * --w0,b15-- FROM free => [ next: VMDefs.PageIndex, --w0,b[7..0]-- dopc: ObjectDirXDefs.DOPC --w[1..2]-- ], used => [ type: ObjectDirDefs.ObjectType, --w0,b[14..11]-- word: VMDefs.PageIndex, --w0,b[7..0]-- page: VMDefs.PageNumber, --w1-- count: ObjectCount --w2-- ], ENDCASE]; DirPageHeader: TYPE = RECORD [ nextFreePage: ObjDirPageSpace, nextFreeIndex: VMDefs.PageIndex]; entriesPerPage: CARDINAL = (VMDefs.pageSize - SIZE[DirPageHeader]) / SIZE[DirData]; DirIndex: TYPE = [0..entriesPerPage); -- NOTE: DirIndex values must fit in the "index" field of an object number -- DirPage: TYPE = RECORD [header: DirPageHeader, data: ARRAY DirIndex OF DirData]; noFreeIndex: [0..LAST[VMDefs.PageIndex]] = SUCC[LAST[DirIndex]]; firstFreePage: ObjDirPageSpace ← endOfChain; -- first page containing free numbers -- nextVirginPage: ObjDirPageSpace; -- current size of file -- dpNumber: ObjDirPageNumber; dpPage: POINTER TO DirPage ← NIL; GetDirPage: INTERNAL PROCEDURE [reqd: ObjDirPageNumber] = BEGIN IF dpPage = NIL OR dpNumber # reqd THEN BEGIN IF dpPage # NIL THEN VMDefs.Release[LOOPHOLE[dpPage, VMDefs.Page]]; dpPage ← LOOPHOLE[VMDefs.ReadPage[ [handle, dpNumber ← reqd], 0 --lookAhead-- ], POINTER TO DirPage]; END; END; -- Allocation of object numbers -- InitPage: PROCEDURE = BEGIN -- initialize current page as empty and chain onto firstFreePage -- index: DirIndex; dpPage.header ← [nextFreePage: firstFreePage, nextFreeIndex: FIRST[DirIndex]]; firstFreePage ← dpNumber; FOR index IN DirIndex DO dpPage.data[index] ← [free[next: SUCC[index], dopc: lastDofpc]] ENDLOOP END; BadFreeObjChain: ERROR = CODE; ObjectDirectoryFull: ERROR = CODE; NewObject: PUBLIC ENTRY PROCEDURE [ at: VMDefs.FullAddress, type: ObjectDirDefs.ObjectType] RETURNS [obj: ObjectDirXDefs.ObjectNumber] = BEGIN obj.page ← firstFreePage; DO --until we find a suitable page -- IF obj.page = endOfChain THEN BEGIN -- allocate a new page by extending object dir file -- obj.page ← nextVirginPage; nextVirginPage ← nextVirginPage + 1; IF dpPage # NIL THEN VMDefs.Release[LOOPHOLE[dpPage, VMDefs.Page]]; dpPage ← LOOPHOLE[VMDefs.UsePage[[handle, dpNumber ← obj.page]], POINTER TO DirPage]; InitPage[] -- puts it on firstFreePage list -- ; VMDefs.MarkStart[LOOPHOLE[dpPage, VMDefs.Page]]; --to cause file extension-- END ELSE GetDirPage[obj.page]; IF obj.page > LAST[ObjDirPageNumber] THEN ERROR ObjectDirectoryFull[]; IF dpPage.header.nextFreePage = unchained THEN ERROR BadFreeObjChain[]; -- if page is empty and is start of free page list IF dpPage.header.nextFreeIndex = noFreeIndex AND obj.page = firstFreePage THEN BEGIN firstFreePage ← obj.page ← dpPage.header.nextFreePage; dpPage.header.nextFreePage ← unchained; ReleaseData[dirty]; END ELSE BEGIN -- scan free chain on this page -- prev: POINTER TO DirData ← NIL; possible: VMDefs.PageIndex ← dpPage.header.nextFreeIndex; UNTIL possible = noFreeIndex DO -- find re-usable object number on this page's free list -- WITH data: dpPage.data[possible] SELECT FROM free => IF data.dopc <= lastDofpc THEN BEGIN -- take it! -- obj.index ← possible; IF prev = NIL THEN dpPage.header.nextFreeIndex ← data.next ELSE WITH prevData: prev SELECT FROM free => prevData.next ← data.next; ENDCASE => ERROR; GOTO found END ELSE BEGIN prev ← @(dpPage.data[possible]); possible ← data.next; END; ENDCASE => ERROR BadFreeObjChain[]; ENDLOOP; obj.page ← dpPage.header.nextFreePage; ReleaseData[clean]; END; REPEAT found => NULL; ENDLOOP; dpPage.data[obj.index] ← [ used[page: at.page.page, word: at.word, type: type, count: oneCount]]; ReleaseData[dirty]; obj.fill ← 0; --to allow for expansion of type field-- obj.type ← type; END; CacheState: TYPE = {clean, dirty}; IllegalObjectNumber: ERROR = CODE; FindData: INTERNAL PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] RETURNS [data: POINTER TO DirData] = BEGIN IF obj.index > LAST[DirIndex] THEN ERROR IllegalObjectNumber[]; GetDirPage[obj.page]; RETURN[@(dpPage.data[obj.index])] END; ReleaseData: INTERNAL PROCEDURE [d: CacheState] = BEGIN IF d = dirty THEN VMDefs.Mark[LOOPHOLE[dpPage, VMDefs.Page]]; END; -- Operations on individual object numbers -- ObjectWithZeroCount: ERROR = CODE; IllegalDestroyObject: ERROR = CODE; ObjectNotInUse: ERROR = CODE; ObjectCountTooBig: ERROR = CODE; heapHandle: VMDefs.FileHandle; -- set by HeapRestart -- ObjectBase: PUBLIC ENTRY PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] RETURNS [addr: VMDefs.FullAddress] = BEGIN either: POINTER TO DirData = FindData[obj]; WITH data: either SELECT FROM used => BEGIN IF data.count < oneCount THEN ERROR ObjectWithZeroCount; addr ← [page: [heapHandle, data.page], word: data.word]; END; ENDCASE => ERROR ObjectNotInUse[]; ReleaseData[clean]; END; UseObject: PUBLIC ENTRY PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] = BEGIN either: POINTER TO DirData = FindData[obj]; WITH data: either SELECT FROM used => BEGIN IF data.count < oneCount THEN ERROR ObjectWithZeroCount; IF data.count = LAST[ObjectCount] THEN ERROR ObjectCountTooBig; data.count ← data.count + 1; END; ENDCASE => ERROR ObjectNotInUse[]; ReleaseData[dirty]; END; FreeObject: PUBLIC ENTRY PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] = BEGIN either: POINTER TO DirData = FindData[obj]; WITH data: either SELECT FROM used => BEGIN IF data.count < oneCount THEN ERROR ObjectWithZeroCount; IF (data.count ← data.count - 1) = zeroCount THEN BEGIN IF obj.type = temp THEN BEGIN HeapFileDefs.ObjectAbandoned[[heapHandle, data.page]]; AddToFreeList[obj, either, 0]; END ELSE PolicyDefs.GapExists[]; END; END; ENDCASE => ERROR ObjectNotInUse[]; ReleaseData[dirty]; END; IllegalRelease: ERROR = CODE; ReleaseObject: PUBLIC ENTRY PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] = BEGIN either: POINTER TO DirData = FindData[obj]; IF obj.type = temp THEN ERROR IllegalRelease[]; WITH data: either SELECT FROM used => IF data.count # zeroCount THEN ERROR IllegalRelease[] ELSE {AddToFreeList[obj, either, 0]; ReleaseData[dirty]}; ENDCASE => ERROR IllegalRelease[]; END; -- ReleaseObject -- IllegalEnumerate: ERROR = CODE; Enumerate: PUBLIC PROCEDURE [ type: ObjectDirDefs.ObjectType, proc: PROCEDURE [ObjectDirXDefs.ObjectNumber] RETURNS [BOOLEAN]] RETURNS [ObjectDirXDefs.ObjectNumber] = BEGIN Check: ENTRY PROCEDURE [obj: ObjectDirXDefs.ObjectNumber] RETURNS [wanted: BOOLEAN] = INLINE BEGIN either: POINTER TO DirData = FindData[obj]; WITH data: either SELECT FROM used => BEGIN wanted ← data.type = obj.type; IF wanted THEN data.count ← data.count + 1; END; ENDCASE => wanted ← FALSE; ReleaseData[IF wanted THEN dirty ELSE clean]; END; IF type = temp THEN ERROR IllegalEnumerate[]; FOR page: VMDefs.PageNumber ← 0, page + 1 DO Last: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE BEGIN RETURN[page = nextVirginPage] END; IF Last[] THEN EXIT; FOR index: DirIndex IN [FIRST[DirIndex]..LAST[DirIndex]] DO BEGIN obj: ObjectDirXDefs.ObjectNumber = [ page: page, fill: 0, type: type, index: index]; IF Check[obj] THEN BEGIN result: BOOLEAN ← proc[obj ! UNWIND => FreeObject[obj]]; FreeObject[obj]; IF result THEN RETURN[obj]; END; END; ENDLOOP; ENDLOOP; RETURN[ObjectDirDefs.noObject]; END; -- Compactor interface -- lastDofpc: ObjectDirXDefs.DOPC ← 0; ReportDofpc: PUBLIC ENTRY PROCEDURE [dofpc: ObjectDirXDefs.DOPC] = { lastDofpc ← dofpc}; MoveObject: PUBLIC ENTRY PROCEDURE [ obj: ObjectDirXDefs.ObjectNumber, where: VMDefs.FullAddress] = BEGIN either: POINTER TO DirData = FindData[obj]; -- the compactor may occasionally move objects whose count has -- -- decreased to zero after the compactor called 'ObjectInUse' -- WITH data: either SELECT FROM used => BEGIN data.page ← where.page.page; data.word ← where.word; END; ENDCASE => ERROR ObjectNotInUse[]; ReleaseData[dirty]; END; GetObjectState: PUBLIC ENTRY PROCEDURE [ obj: ObjectDirXDefs.ObjectNumber, where: VMDefs.FullAddress, dorpc: ObjectDirXDefs.DOPC] RETURNS [state: ObjectDirXDefs.ObjectState] = BEGIN either: POINTER TO DirData = FindData[obj]; -- TRUE iff non-zero count and on appropriate page -- -- Word position within page is not relevant: position is considered -- -- only to eliminate duplicates left behind by compactor crashes -- WITH data: either SELECT FROM used => BEGIN IF data.page # where.page.page THEN { state ← duplicate; ReleaseData[clean]} ELSE IF data.count = zeroCount THEN BEGIN AddToFreeList[obj, either, dorpc]; state ← unused; ReleaseData[dirty]; END ELSE {state ← inUse; ReleaseData[clean]}; END; ENDCASE => ERROR ObjectNotInUse[]; END; AddToFreeList: PROC [ obj: ObjectDirXDefs.ObjectNumber, either: POINTER TO DirData, dorpc: ObjectDirXDefs.DOPC] = BEGIN IF dpPage.header.nextFreePage = unchained THEN { dpPage.header.nextFreePage ← firstFreePage; firstFreePage ← dpNumber}; either↑ ← [free[dpPage.header.nextFreeIndex, dorpc]]; dpPage.header.nextFreeIndex ← obj.index; END; END.