DIRECTORY PrincOps USING [flagsNone, flagsVacant, PageFlags, PageState, PageValue, RealPageNumber], PrincOpsUtils USING [DisableInterrupts, EnableInterrupts], ProcessorFace USING [firstSpecialRealPage, specialRealPages], VMBacking USING [BriefPageState], VMInternal USING [AddToFreeList, AllocCount, Crash, DataState, freeList, freePages, GetPageValue, GetVMMap, InitializeTables, InOut, Interval, IsVacant, Outcome, PageCount, PageNumber, PageState, PageStateFromFlags, RMEntryPointer, rmMap, RMMapEntries, RMMapEntry, SetPageFlags, SetPageValue, SetVMMap, SpecialMemoryOutcome, VMMapEntry, VMPartition], VMSideDoor USING [vmPages], VMStatistics USING []; VMStateImpl: MONITOR LOCKS vmStateLock IMPORTS PrincOpsUtils, ProcessorFace, VMInternal, VMSideDoor EXPORTS VMBacking, VMInternal, VMStatistics SHARES VMInternal = BEGIN OPEN PrincOps, VMInternal; vmStateLock: PUBLIC MONITORLOCK; freeList: PUBLIC RealPageNumber; freePages: PUBLIC INT _ 0; rmMap: PUBLIC LONG POINTER TO RMMapEntries; lastRealPage: PUBLIC RealPageNumber; cleaningRover: PUBLIC RealPageNumber _ RealPageNumber.FIRST; checkIn: PUBLIC CONDITION _ [timeout: 0]; vacantEntry: VMMapEntry = [ state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[ checkedOut: FALSE, readOnly: FALSE, dataState: undefined ] ]; rmCleanPasses: PUBLIC INT _ 0; readOnlyPages: PUBLIC INT _ 0; pinnedPages, trappedPages: PUBLIC INT _ 0; checkoutConflicts: PUBLIC INT _ 0; VirtualAllocation: PUBLIC PROC [partition: VMPartition] RETURNS [pagesAllocated, pagesFreed, pagesInPartition: PageCount] = { RETURN[ pagesAllocated: allocCounts[partition].pagesAllocated, pagesFreed: allocCounts[partition].pagesFreed, pagesInPartition: partitions[partition].count ] }; StateFromPageValue: PUBLIC SAFE PROC [map: PrincOps.PageValue] RETURNS [VMBacking.BriefPageState] = TRUSTED { vmEntry: VMMapEntry _ [ state: map.state, body: in[real: map.real] ]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => RETURN[ SELECT vmE.dataState FROM none => free, undefined => killed, ENDCASE => active ]; in => RETURN[active]; ENDCASE => ERROR; }; RecoverRealMemory: PUBLIC PROC = { vmPage: PageNumber _ 0; firstSpecialReal: RealPageNumber = ProcessorFace.firstSpecialRealPage; countSpecialReal: PageCount = ProcessorFace.specialRealPages; THROUGH [0..freePages) DO realPage: RealPageNumber = freeList; UNTIL VMInternal.IsVacant[vmPage] DO vmPage _ vmPage.SUCC; ENDLOOP; VMInternal.SetPageValue[ vmPage, [VMInternal.PageStateFromFlags[PrincOps.flagsNone], realPage]]; vmPage _ vmPage.SUCC; WITH rmMap[realPage] SELECT FROM rmE: free RMMapEntry => freeList _ rmE.next; ENDCASE => Crash[]; ENDLOOP; IF countSpecialReal > 0 THEN FOR vmPage IN [0..VMSideDoor.vmPages) DO state: PrincOps.PageState; real: RealPageNumber; [state, real] _ VMInternal.GetPageValue[vmPage].pv; IF state.flags ~= PrincOps.flagsVacant AND real IN [firstSpecialReal..firstSpecialReal+countSpecialReal) THEN VMInternal.SetPageFlags[vmPage, 0, PrincOps.flagsVacant]; ENDLOOP; }; partitions: PUBLIC ARRAY VMPartition OF Interval; --*stats*-- allocCounts: PUBLIC ARRAY VMPartition OF AllocCount _ ALL[[0, 0, 0, 0]]; Unpin: PUBLIC ENTRY PROC [vmPage: PageNumber] RETURNS [outcome: Outcome _ ok] = { vmEntry: VMMapEntry = GetCheckedInVMMap[vmPage].vmEntry; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM in => { rmE: RMEntryPointer = @rmMap[vmE.real]; WITH rmE: rmE SELECT FROM free => Crash[]; reclaimable => NULL; pinned => IF rmE.pinReason = normal THEN SELECT rmE.pinCount FROM 0 => Crash[]; 1 => { rmMap[vmE.real].body _ reclaimable[virtual: vmPage]; --*stats*-- pinnedPages _ pinnedPages.PRED; }; ENDCASE => rmE.pinCount _ rmE.pinCount - 1; ENDCASE; }; out => IF vmE.dataState = none THEN outcome _ addressFault; ENDCASE; }; PrepareToAllocateSpecialRealMemory: PUBLIC ENTRY PROC [ vmPage: PageNumber, buffer: PageNumber, special: PageNumber] RETURNS [outcome: SpecialMemoryOutcome _ needsCopy] = { vmEntry: VMMapEntry _ GetCheckedInVMMap[vmPage].vmEntry; specialFlags: PrincOps.PageFlags _ PrincOps.flagsNone; readOnly: BOOL _ FALSE; specialEntry: in VMMapEntry; newRME: RMMapEntry _ [ dataState: NULL, needsBackingStoreWrite: FALSE, body: pinned[pinReason: specialRealPageInUse, pinCount: 0] ]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => { SELECT (newRME.dataState _ vmE.dataState) FROM none => RETURN[addressFault]; undefined => outcome _ noTransfer; ENDCASE => outcome _ needsIO; readOnly _ vmE.readOnly; }; in => { WITH rmE: rmMap[vmE.real] SELECT FROM free => Crash[]; reclaimable => { SELECT (newRME.dataState _ rmE.dataState) FROM none => Crash[]; undefined => outcome _ IF vmE.state.flags.dirty THEN needsCopy ELSE noTransfer; ENDCASE => outcome _ needsCopy; newRME.needsBackingStoreWrite _ rmE.needsBackingStoreWrite; }; pinned => RETURN[badParameter]; -- either already special or pinned ENDCASE; specialFlags _ vmE.state.flags; readOnly _ specialFlags.readonly; specialFlags.readonly _ FALSE; }; ENDCASE; specialEntry _ [ state: VMInternal.PageStateFromFlags[specialFlags], body: in[real: NULL] ]; IF ~([realPage: specialEntry.real] _ AllocateSpecialPage[newRME]).ok THEN RETURN[noMemory]; SetVMMap[vmPage, [ state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[checkedOut: TRUE, readOnly: readOnly, dataState: newRME.dataState] ]]; SetVMMap[buffer, vmEntry]; SetVMMap[special, specialEntry]; }; FinishAllocateSpecialRealMemory: PUBLIC ENTRY PROC [ vmPage: PageNumber, buffer: PageNumber, special: PageNumber, worked: BOOL] = { specialEntry: VMMapEntry _ GetVMMap[special]; vmEntry: VMMapEntry = GetVMMap[vmPage]; readOnly: BOOL; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => readOnly _ vmE.readOnly; ENDCASE => Crash[]; WITH specialE: specialEntry SELECT InOut[specialEntry] FROM in => IF worked THEN { bufferEntry: VMMapEntry = GetVMMap[buffer]; WITH bE: bufferEntry SELECT InOut[bufferEntry] FROM in => AddToFreeList[bE.real]; ENDCASE; specialE.state.flags.readonly _ readOnly; SetVMMap[vmPage, specialE]; } ELSE { bufferEntry: VMMapEntry = GetVMMap[buffer]; SetVMMap[vmPage, bufferEntry]; FreeSpecialPage[specialE.real]; }; out => Crash[]; ENDCASE; BROADCAST checkIn; SetVMMap[buffer, vacantEntry]; SetVMMap[special, vacantEntry]; }; PrepareToReleaseSpecialRealMemory: PUBLIC ENTRY PROC [ vmPage: PageNumber, special: PageNumber] RETURNS [outcome: SpecialMemoryOutcome] = { vmEntry: VMMapEntry _ GetCheckedInVMMap[vmPage].vmEntry; checkedEntry: out VMMapEntry _ [ state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[checkedOut: TRUE, readOnly: vmEntry.state.flags.readonly, dataState: NULL] ]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => RETURN[IF vmE.dataState = none THEN addressFault ELSE badParameter]; in => WITH rmE: rmMap[vmE.real] SELECT FROM pinned => { IF rmE.pinCount > 0 THEN RETURN[badParameter]; checkedEntry.dataState _ rmE.dataState; outcome _ IF rmE.dataState = undefined AND ~vmE.state.flags.dirty THEN noTransfer ELSE needsIO; }; ENDCASE => RETURN[badParameter]; ENDCASE; SetVMMap[vmPage, checkedEntry]; SetVMMap[special, vmEntry]; }; FinishReleaseSpecialRealMemory: PUBLIC ENTRY PROC [ vmPage: PageNumber, special: PageNumber, worked: BOOL] = { specialEntry: VMMapEntry = GetVMMap[special]; WITH specialE: specialEntry SELECT InOut[specialEntry] FROM in => IF worked THEN WITH rmE: rmMap[specialE.real] SELECT FROM pinned => { IF rmE.pinReason ~= specialRealPageInUse THEN Crash[]; SetVMMap[vmPage, [ state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[ checkedOut: FALSE, readOnly: specialE.state.flags.readonly, dataState: rmE.dataState ] ]]; FreeSpecialPage[specialE.real]; }; ENDCASE => Crash[] ELSE SetVMMap[vmPage, specialEntry]; out => Crash[]; ENDCASE; BROADCAST checkIn; SetVMMap[special, vacantEntry]; }; State: PUBLIC ENTRY PROC [vmPage: PageNumber] RETURNS [state: PageState] = { vmEntry: VMMapEntry = GetVMMap[vmPage]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => state _ [ dataState: vmE.dataState, readOnly: vmE.readOnly, hasRealMemory: FALSE, needsCleaning: FALSE, pinCount: 0 ]; in => { rmE: RMEntryPointer = @rmMap[vmE.real]; state _ [ dataState: IF vmE.state.flags.dirty THEN changed ELSE rmE.dataState, readOnly: vmE.state.flags.readonly, hasRealMemory: TRUE, needsCleaning: vmE.state.flags.dirty OR rmE.needsBackingStoreWrite, pinCount: WITH rmE^ SELECT FROM pinned => pinCount, ENDCASE => 0 ]; }; ENDCASE; }; SetDataState: PUBLIC ENTRY PROC [vmPage: PageNumber, dataState: DataState] RETURNS [outcome: Outcome _ ok] = { vmEntry: VMMapEntry _ GetCheckedInVMMap[vmPage].vmEntry; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => IF vmE.dataState = none THEN outcome _ addressFault ELSE { IF ~(dataState = unchanged AND vmE.dataState = undefined) THEN { -- pages that are "undefined" are not modified to "unchanged". This is necessary to run without a backing file and is a slight optimization. vmE.dataState _ dataState; SetVMMap[vmPage, vmE]; }; }; in => { rmE: RMEntryPointer = @rmMap[vmE.real]; SELECT dataState FROM none => { SetVMMap[vmPage, [state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[checkedOut: FALSE, readOnly: FALSE, dataState: dataState]] ]; WITH rmE: rmE SELECT FROM free => Crash[]; reclaimable => AddToFreeList[vmE.real]; pinned => IF rmE.pinReason = specialRealPageInUse THEN FreeSpecialPage[vmE.real] ELSE AddToFreeList[vmE.real]; ENDCASE; }; undefined => { WITH rmE: rmE SELECT FROM free => Crash[]; reclaimable => { SetVMMap[vmPage, [state: VMInternal.PageStateFromFlags[PrincOps.flagsVacant], body: out[checkedOut: FALSE, readOnly: FALSE, dataState: dataState]] ]; AddToFreeList[vmE.real]; }; pinned => { vmE.state.flags.dirty _ rmE.needsBackingStoreWrite _ FALSE; SetVMMap[vmPage, vmE]; rmE.dataState _ dataState; }; ENDCASE; }; unchanged => { newEntry: VMMapEntry; PrincOpsUtils.DisableInterrupts[]; newEntry _ GetVMMap[vmPage]; WITH newE: newEntry SELECT InOut[newEntry] FROM in => IF newE.state.flags.dirty THEN { rmE.needsBackingStoreWrite _ TRUE; newE.state.flags.dirty _ FALSE; SetVMMap[vmPage, newE]; }; out => Crash[]; ENDCASE; PrincOpsUtils.EnableInterrupts[]; rmE.dataState _ dataState; }; changed => rmE.dataState _ dataState; ENDCASE; }; ENDCASE; }; MakeReadOnly: PUBLIC ENTRY PROC [vmPage: PageNumber] RETURNS [outcome: Outcome _ ok] = { vmEntry: VMMapEntry _ GetCheckedInVMMap[vmPage].vmEntry; PrincOpsUtils.DisableInterrupts[]; vmEntry _ GetVMMap[vmPage]; -- reread map to ensure atomicity WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => IF vmE.dataState = none THEN outcome _ addressFault ELSE IF ~vmE.readOnly THEN { vmE.readOnly _ TRUE; SetVMMap[vmPage, vmE]; --*stats*-- readOnlyPages _ readOnlyPages.SUCC; }; in => IF ~vmE.state.flags.readonly THEN { IF vmE.state.flags.dirty THEN { rmMap[vmE.real].needsBackingStoreWrite _ TRUE; vmE.state.flags.dirty _ FALSE; rmMap[vmE.real].dataState _ changed; }; vmE.state.flags.readonly _ TRUE; SetVMMap[vmPage, vmE]; --*stats*-- readOnlyPages _ readOnlyPages.SUCC; }; ENDCASE; PrincOpsUtils.EnableInterrupts[]; }; MakeReadWrite: PUBLIC ENTRY PROC [vmPage: PageNumber] RETURNS [outcome: Outcome _ ok] = { vmEntry: VMMapEntry _ GetCheckedInVMMap[vmPage].vmEntry; PrincOpsUtils.DisableInterrupts[]; vmEntry _ GetVMMap[vmPage]; -- reread map to ensure atomicity WITH vmE: vmEntry SELECT InOut[vmEntry] FROM out => IF vmE.dataState = none THEN outcome _ addressFault ELSE IF vmE.readOnly THEN { vmE.readOnly _ FALSE; SetVMMap[vmPage, vmE]; --*stats*-- readOnlyPages _ readOnlyPages.PRED; }; in => IF vmE.state.flags.readonly THEN { vmE.state.flags.readonly _ FALSE; SetVMMap[vmPage, vmE]; --*stats*-- readOnlyPages _ readOnlyPages.PRED; }; ENDCASE; PrincOpsUtils.EnableInterrupts[]; }; AllocateSpecialPage: PROC [rmE: RMMapEntry] RETURNS [ok: BOOL _ FALSE, realPage: RealPageNumber] = INLINE { count: CARDINAL = ProcessorFace.specialRealPages; realPage _ ProcessorFace.firstSpecialRealPage; THROUGH [0..count) DO WITH rmE: rmMap[realPage] SELECT FROM pinned => IF rmE.pinReason = specialRealPageAvailable THEN EXIT; ENDCASE; realPage _ realPage + 1; REPEAT FINISHED => RETURN; ENDLOOP; rmMap[realPage] _ rmE; ok _ TRUE; }; FreeSpecialPage: PROC [realPage: RealPageNumber] = INLINE { WITH rmE: rmMap[realPage] SELECT FROM pinned => IF rmE.pinReason = specialRealPageInUse THEN rmE.pinReason _ specialRealPageAvailable ELSE Crash[]; ENDCASE => Crash[]; }; GetCheckedInVMMap: INTERNAL PROC [vmPage: PageNumber, dontWait: BOOL _ FALSE] RETURNS [vmEntry: VMMapEntry, success: BOOL _ TRUE] = INLINE { firstTime: BOOL _ TRUE; DO vmEntry _ GetVMMap[vmPage]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM in => EXIT; out => IF ~vmE.checkedOut OR vmE.dataState = none THEN EXIT; ENDCASE; IF dontWait THEN {success_ FALSE; EXIT}; --*stats*-- IF firstTime THEN {checkoutConflicts _ checkoutConflicts.SUCC; firstTime _ FALSE}; WAIT checkIn; ENDLOOP; }; VMPageInfo: TYPE = RECORD [ body: SELECT inOut: * FROM in => [inEntry: VMMapEntry[in], entry: RMMapEntry], out => [outEntry: VMMapEntry[out]], none => NULL, ENDCASE]; ShowVirtual: PROC [vmPage: PageNumber] RETURNS [VMPageInfo] = { IF vmPage IN [0..VMSideDoor.vmPages) THEN { vmEntry: VMMapEntry _ GetVMMap[vmPage]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM in => RETURN [[in[vmE, rmMap[vmE.real]]]]; out => RETURN [[out[vmE]]]; ENDCASE; }; RETURN [[none[]]]; }; ShowReal: PROC [rmPage: RealPageNumber] RETURNS [ok: BOOL _ FALSE, info: RMMapEntry] = { info _ rmMap[0]; IF rmPage IN [0..lastRealPage] THEN {ok _ TRUE; info _ rmMap[rmPage]}; }; FindVirtual: PROC [rmPage: RealPageNumber, last: PageNumber _ 0] RETURNS [vmPage: PageNumber _ 0, info: VMPageInfo] = { FOR vmPage IN (last..VMSideDoor.vmPages) DO vmEntry: VMMapEntry _ GetVMMap[vmPage]; WITH vmE: vmEntry SELECT InOut[vmEntry] FROM in => IF vmE.real = rmPage THEN {info _ [in[vmE, rmMap[vmE.real]]]; RETURN}; ENDCASE; ENDLOOP; info _ [none[]]; }; InitializeTables[]; END. ‚VMStateImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Russ Atkinson on January 30, 1985 10:35:27 pm PST Bob Hagmann on May 17, 1985 1:36:58 pm PDT Notes on the implementation: The representation of the mapping from virtual page number to its associated PageState is partitioned into two pieces. A portion called the VM map is used for virtual memory pages that do not have an associated real memory page. A portion called the RM map is used for virtual memory pages that have an associated real memory page. (The RM map also holds other information, as will be discussed shortly.) Thus, to locate the PageState for a given (virtual) page, one first consults the hardware PageMap. If the flags say "vacant", the PageState is stored in the VM map; otherwise, it is stored in the RM map. The representation of information in the VM map can be considerably optimized. First, PageState.hasRealMemory is implicitly FALSE and PageState.pinCount isn't meaningful. Thus, only PageState.readOnly and PageState.dataState need to be explicitly represented, requiring 3 bits per page. Second, we can use a trick that relies on the hardware implementation of the PageMap on all of the machines of interest (Dolphin, Dorado, Dandelion). Although the PrincOps specifies that if both dirty and write-protected are TRUE (indicating "vacant"), the real page number is undefined, the hardware does not enforce this restriction and is capable of retaining a few bits of information (<= 12) in what would otherwise be the real page number. This is simply because the map is implemented as a table lookup in a memory at least 16 bits wide of which at most 4 are used for flags. The VM map can therefore be comfortably stored in the available memory of the PageMap. If RMEntry.rmState is "reclaimable", the real memory is available for reclamation by the real memory allocator (if it does so, it uses the "virtual" field to set the PageMap entry to "vacant"). If RMEntry.rmState is "pinned", the real memory allocator is prohibited from reclaiming this page. Note that the RMMap does not encode the virtual memory location of pinned real memory. This is not necessary, since the operation that changes rmState from "pinned" to "reclaimable" (Unpin) supplies the necessary PageNumber. Furthermore, this representation permits the reservation of specific real memory without necessarily associating it with virtual memory, a convenience in dealing with the Dandelion's display memory. If the needsBackingStoreWrite Boolean is TRUE, the bits in this page of real memory are known not to be present on backing storage. The "or" of needsBackingStoreWrite and the PageMap "dirty" bit is used to decide if the page must be written out as part of reclaiming it (see the AllocateForSwapIn and ConsiderCleaning procedures, below). The dataState field in an RMMap entry should never be "none", but may be "undefined" (implying "killed"). Finally, if RMEntry.rmState is "free", the "next" field is used to chain unallocated real memory pages together. This provides the real memory allocator with a list of prime candidates for allocation. All of the procedures in this interface share a monitor that protects the RM map, VM map, and PageMap. AllocateRealMemoryInternal needs to be available to the frame fault handler (via AllocateForLocalFrames) and therefore must be a coroutine (AllocateRealMemoryInternal) with its two callers, AllocateForLocalFrames and AllocateForSwapIn, entering the monitor to ensure mutual exclusion. To avoid possible recursion in frame fault handling, AllocateRealMemoryInternal must not call any procedures. To avoid deadlock, therefore, all other procedures that enter the monitor must not call procedures either. A note on checkout/checkin: This is not a general purpose facility. It is intended to permit parellelism between the VM operations that do not require I/O and those that do (SwapIn, Clean). Thus, a page can only be checked out when its hardware map entry says "vacant" (the map is always in this state during swapper I/O). The procedures below that implement non-I/O VM operations all test if the page in question is checked out and, if so, wait until it is checked in (releasing the monitor while they wait). Checkout occurs only as a part of AllocateForSwapIn and ConsiderCleaning, and checkin occurs only as a part of RelocateAfterSwapIn and MarkClean. The following monitor lock is used to protect the data structures of this module. It is exported so that the inline procedures in the VMInternal interface (which logically belong to this implementation module) can use it. The following global variables are protected by vmStateLock. index of last entry in rmMap rover for GetCleaningCandidate The following constant is useful in various places Exports to VMStatistics Exports to VMBackingChanges The following works even if we are presented with a non-vacant map entry, but Andrew assures me that we won't! Exports to VMBacking See comment in the interface for a description of this crock. It completely trashes the virtual memory abstraction, except for those pages that already have real memory. Exports to VMInternal Virtual Memory Allocation Real Memory Allocation Perhaps we should complain if rmE.pinReason ~= normal? The following implicitly checks in vmPage. The following implicitly checks in vmPage. Now we make the swap buffer pages vacant. Need to set this to keep from having none in the data state (sigh). The following implicitly checks in vmPage. The following implicitly checks in vmPage. State Examination and Modification It's OK to call GetVMMap instead of GetCheckedInVMMap, since we have the monitor lock and the data structures won't change under us. Note: If this procedure were made a coroutine (with an appropriate INLINE entry procedure), it could be invoked directly from the frame fault handler's "FlushLargeFrames" logic. Otherwise, that logic must reside in a separate process, which is allowed to call procedures and therefore take frame faults. Strictly speaking, we should disable interrupts, reread the map, clear the dirty bit, and set the map before enabling interrupts. This will avoid losing the referenced bit. However, since the page is being killed, the referenced bit isn't very interesting now anyway. We must do the following atomically to avoid losing the referenced bit. ReadOnly <==> ReadWrite Interrupts must be disabled if the map entry says present, so that the test for "dirty" and subsequent resetting of "dirty" are atomic. Interrupts need not be disabled if the map entry says "vacant", but it doesn't hurt, since we spend minimal time in that state and it simplifies the code. Interrupts must be disabled if the map entry says present, so that the resetting of "readonly" is atomic (otherwise, the "referenced" bit might be lost). Interrupts need not be disabled if the map entry says "vacant", but it doesn't hurt, since we spend minimal time in that state and it simplifies the code. INTERNAL procedures A note in VMInternal.VMMapEntry explains the following non-intuitive test. Debugging procedures (Russ Atkinson, February 9, 1984) Given a virtual page, returns VMPageInfo which will contain the virtual page info and real page info for a legal page that is mapped in, or virtual page info for a legal page that is mapped out, or an indication of illegal page number. Given a valid real page, show its state (ok = TRUE). If not valid, ok = FALSE. Given a real page number, find the corresponding virtual page > last and report on its contents. If no such virtual page, a null result is returned. Note that we are reasonably sure that virtual page 0 is not mapped. Initialization Bob Hagmann May 17, 1985 1:35:31 pm PDT changes to: SetDataState Κœ˜codešœ™Kšœ Οmœ1™˜Ešžœ˜Kšœ6˜6Kšœ.˜.Kšœ-˜-K˜—K˜K˜—Kšœ™™šŸœžœžœžœ˜>Kšžœžœ˜.Kšœn™n˜K˜K˜K˜—šžœžœž˜,šœžœ˜šžœž˜Kšœ ˜ Kšœ˜Kšžœ ˜—Kšœ˜—Kšœžœ ˜Kšžœžœ˜—K˜—K˜—šœ™K™šŸœžœžœ˜"Kšœͺ™ͺK˜KšœF˜FKšœ=˜=šžœž˜K˜$Kšžœžœžœžœ˜Cšœ˜KšœG˜G—Kšœžœ˜šžœžœž˜ Kšœ,˜,Kšžœ ˜—Kšžœ˜—šžœž˜šžœžœž˜(Kšœ˜Kšœ˜Kšœ3˜3šžœ%ž˜*Kšœžœ7ž˜BKšœ9˜9—Kšžœ˜——K˜—K˜—K™Kšœ™™Kšœ™K™Kšœ ž œ žœ ˜1K™Kš Οc œžœžœ žœžœ˜TK˜Kšœ™K˜šŸœž œžœžœ˜QK˜8šžœžœž˜,šœ˜Kšœ'˜'šžœ žœž˜K˜Kšœžœ˜˜ Kšœ6™6šžœž˜šžœž˜K˜ šœ˜Kšœ4˜4Kš  œžœ˜+K˜—šžœ˜ Kšœ ˜ ————Kšžœ˜—K˜—Kšœžœžœ˜;Kšžœ˜—K˜—K˜šŸ"œžœžœžœ˜7Kšœ<˜šžœžœž˜,šœ˜Kšžœžœ˜3šž˜šžœžœ˜Kšœžœ˜Kšœ˜Kš  œžœ˜/Kšœ˜———šœ˜šžœžœ˜#šžœžœ˜Kšœ)žœ˜.Kšœžœ˜Kšœ$˜$Kšœ˜—Kšœžœ˜ Kšœ˜Kš  œžœ˜/K˜——Kšžœ˜—Kšœ!˜!K˜—K˜šŸ œžœ˜5Kšžœ˜#Kšœ΅™΅K˜8Kšœ"˜"Kšœ !˜>šžœžœž˜,šœ˜Kšžœžœ˜3šž˜šžœžœ˜Kšœžœ˜Kšœ˜Kš  œžœ˜/Kšœ˜———šœ˜šžœžœ˜"Kšœžœ˜!Kšœ˜Kš  œžœ˜/Kšœ˜——Kšžœ˜—Kšœ!˜!K˜—K˜—K™K™K˜šŸœžœ˜+Kšžœžœžœžœ˜?Kšœžœ"˜1Kšœ.˜.šžœ ž˜šžœžœž˜%Kšœ žœ*žœžœ˜@Kšžœ˜—Kšœ˜šž˜Kšžœžœ˜—Kšžœ˜—Kšœ˜Kšœžœ˜ K˜—K˜šŸœžœžœ˜;šžœžœž˜%šœ ˜ Kšžœ&žœ)˜UKšžœ ˜ —Kšžœ ˜—K˜K˜—šŸœžœžœ ž œ˜MKšžœ žœžœžœ˜>Kšœ žœžœ˜šž˜Kšœ˜šžœžœž˜,Kšœžœ˜ šœ˜KšœJ™JKšžœžœžœžœ˜5—Kšžœ˜—Kšžœ žœ žœžœ˜(˜ Kšžœ žœ(žœžœ˜R—Kšžœ ˜ Kšžœ˜—Kšœ˜K˜—šœ6™6K˜šœ žœžœ˜šœžœ ž˜Kšœ3˜3Kšœ#˜#Kšœžœ˜ Kšžœ˜ ——K˜šŸ œžœžœ˜?Kšœλ™λšžœžœžœ˜+Kšœ'˜'šžœžœž˜,Kšœžœ˜*Kšœžœ˜Kšžœ˜—K˜—Kšžœ ˜K˜K˜—šŸœžœ˜'Kšžœžœžœ˜0KšœO™OKšœ˜Kšžœžœžœžœ˜FK˜K˜—šŸ œž˜Kšœ.˜.Kšžœ/˜6KšœΪ™Ϊšžœžœž˜+Kšœ'˜'šžœžœž˜,Kšœžœžœ%žœ˜LKšžœ˜—Kšžœ˜—Kšœ˜K˜K˜——K™Kšœ™K™Kšœ˜K™Kšžœ˜K˜K˜™'Kšœ Οr ™—K™—…—62gP