-- File: CedarSnapshotMain.mesa -- last edited by Levin: 3-Dec-81 11:21:40 DIRECTORY BcdDefs USING [GFTIndex, MTIndex], BcdOps USING [BcdBase, MTHandle, ProcessModules], Boot USING [BootFileType, Location, LVBootFiles], BootFile USING [MemorySizeToFileSize], BootSwap USING [Mon, OutLoad], BootSwapCross USING [pMon], CachedSpace USING [Desc, Handle, Level], CedarSnapshot USING [Outcome], DeviceCleanup USING [Perform], -- DiskChannel USING [Idle, Restart], Directory USING [CreateFile], -- used only to find GF of DirectoryFilesImpl DirectoryFiles USING [DCEntry, --directoryCache,-- maxDCache], DirectoryFilesImpl USING [directoryCache], DiskChannelImpl USING [ChannelHandle, ChannelObject], Environment USING [Base, first64K, wordsPerPage], File USING [ Capability, Create, Delete, delete, GetAttributes, GetSize, ID, LimitPermissions, nullID, PageCount, PageNumber, Permissions, read, Unknown, write], FileTypes USING [tUntypedFile], Frame USING [MyGlobalFrame], -- Hierarchy USING [GetDescriptor], HierarchyImpl USING [GetDescriptor], Inline USING [BITOR, BITXOR], KernelFile USING [GetBootLocation], -- LogicalVolume USING [CloseLogicalVolume, OpenLogicalVolume], MapLogImpl USING [WriteLog1], -- should be VMMapLog.WriteLog PilotFileTypes USING [tVMBackingFile], -- PilotLoaderOps USING [ -- CloseLinkSpace, LinkSegmentLength, OpenLinkSpace, ReadLink, WriteLink], PilotLoaderSupport USING [ CloseLinkSpace, LinkSegmentLength, OpenLinkSpace, ReadLink, WriteLink], PilotLoadStateOps USING [ AcquireBcd, ConfigIndex, EnumerateBcds, GetMap, GetModule, InputLoadState, Map, ReleaseBcd, ReleaseLoadState, ReleaseMap], PilotMP USING [cClient], PrincOps USING [ControlLink, GFTIndex, GlobalFrameHandle], PrincOpsRuntime USING [GetFrame, GFT], Process USING [ GetPriority, Pause, Priority, SecondsToTicks, SetPriority --, Yield--], ProcessInternal USING [DisableInterrupts, EnableInterrupts], ProcessOperations USING [EnableAndRequeue, ReadPSB, ReadPTC, ReadWDC, WritePSB, WritePTC, WriteWDC], ProcessorFace USING [SetMP], ProcessPriorities USING [priorityPageFaultLow], PSB USING [PDA, PsbHandle], Runtime USING [GlobalFrame, LoadConfig -- for PilotLoaderOps hack only --], Snapshot USING [InLoad], Space USING [ CopyIn, CopyOut, Create, Deactivate, defaultWindow, Delete, GetAttributes, GetHandle, GetWindow, Handle, LongPointer, MakeReadOnly, MakeWritable, Map, nullHandle, PageCount, PageFromLongPointer, PageOffset, Unmap, virtualMemory, WindowOrigin], SpecialFile USING [Link, MakeBootable], SpecialSpace USING [ MakeGlobalFrameResident, MakeGlobalFrameSwappable, MakeProcedureResident, MakeProcedureSwappable, MakeResident, MakeSwappable, realMemorySize], SpecialVolume USING [GetLogicalVolumeBootFiles, SetLogicalVolumeBootFiles], SubVolumeImpl USING [CacheEntry, ceFirst, ceLast, CePtr], TemporarySetGMT USING [SetGMT], UserTerminal USING [CursorArray, GetCursorPattern, SetCursorPattern], VM USING [PageNumber], -- VMMapLog USING [WriteLog1], Volume USING [ID, InsufficientSpace, nullID, systemID], VolumeExtras USING [OpenVolume], -- VolumeImplInterface USING [SubvolumeOffline, SubvolumeOnline], VolumeImpl USING [CloseLogicalVolume, OpenLogicalVolume, SubvolumeOffline, SubvolumeOnline]; CedarSnapshotMain: MONITOR IMPORTS BcdOps, BootFile, BootSwap, DeviceCleanup, Directory, -- DiskChannel, -- File, Frame, -- Hierarchy, -- Inline, KernelFile, -- LogicalVolume, PilotLoaderOps, -- PilotLoadStateOps, PrincOpsRuntime, Process, ProcessInternal, ProcessOperations, ProcessorFace, Runtime, Snapshot, Space, SpecialFile, SpecialSpace, SpecialVolume, TemporarySetGMT, UserTerminal, -- VMMapLog, -- Volume, VolumeExtras EXPORTS CedarSnapshot, BootSwap -- hack!! SHARES BootSwap, BootSwapCross, DiskChannelImpl, File, SubVolumeImpl = BEGIN -- Miscellaneous Constants -- read: File.Permissions = File.read; write: File.Permissions = File.write; delete: File.Permissions = File.delete; clientImage: Boot.BootFileType = hardMicrocode; -- as good as any... firstOutloadPage: CARDINAL = 1; -- can't use 0 with temporary file => label check initialSpaceScriptPages: Space.PageCount = 3; initialLinkScriptPages: Space.PageCount = 4; maxReasonableSwapUnit: Space.PageCount = 32; -- The following hacks allow us to get to the interfaces that PilotKernel doesn't -- export. BEWARE IF THE GFIs CHANGE! BootSwapCrossGfi: PrincOps.GFTIndex = 3B; HierarchyImplGfi: PrincOps.GFTIndex = 55B; MapLogImplGfi: PrincOps.GFTIndex = 61B; SubVolumeImplGfi: PrincOps.GFTIndex = 26B; VolumeImplGfi: PrincOps.GFTIndex = 53B; -- Hack export to BootSwap -- pMon: PUBLIC LONG POINTER TO BootSwap.Mon ← LOOPHOLE[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[BootSwapCrossGfi]], POINTER TO FRAME[BootSwapCross]].pMon; -- Checkpoint -- Checkpoint: PUBLIC PROCEDURE [volume: Volume.ID ← Volume.nullID] RETURNS [outcome: CedarSnapshot.Outcome] = BEGIN outloadLocation: disk Boot.Location; ScriptFull: ERROR = CODE; -- Cork -- oldCursor: UserTerminal.CursorArray; oldPriority: Process.Priority = Process.GetPriority[]; cork: PROCESS; state: {initial, corked, uncork} ← initial; stateChange: CONDITION ← [timeout: 0]; CorkRestOfWorld: PROCEDURE = BEGIN EnsureCorked: ENTRY PROCEDURE = INLINE {cork ← FORK Cork[]; UNTIL state = corked DO WAIT stateChange; ENDLOOP}; Process.SetPriority[ProcessPriorities.priorityPageFaultLow]; oldCursor ← UserTerminal.GetCursorPattern[]; EnsureCorked[]; -- The cork is now at the head of the ready list for priorityPageFaultLow, -- and since it never faults, it yields only to its own priority level, -- so nothing below priorityPageFaultLow will run. This is assumed to -- include the Ethernet driver(s), Cedar runtime processes, and anything -- else that uses the Space machinery. END; UncorkRestOfWorld: PROCEDURE = BEGIN state ← uncork; JOIN cork; UserTerminal.SetCursorPattern[oldCursor]; Process.SetPriority[oldPriority]; END; Cork: PROCEDURE = BEGIN myGF: PrincOps.GlobalFrameHandle = Frame.MyGlobalFrame[]; AnnounceCorked: ENTRY PROCEDURE = INLINE {state ← corked; NOTIFY stateChange}; OutermostProcOf: PROCEDURE [p: UNSPECIFIED] RETURNS [link: PrincOps.ControlLink] = BEGIN -- This gets around a bug in SpecialSpace.MakeProcedure{Resident|Swappable} link ← LOOPHOLE[p]; WHILE link.indirect AND ~link.proc DO link ← link.link↑; ENDLOOP; IF ~link.proc THEN ERROR; RETURN[link] END; Yield: PROCEDURE = -- stolen from Processes to ensure residency BEGIN OPEN ProcessOperations; ProcessInternal.DisableInterrupts[]; EnableAndRequeue[@PSB.PDA.ready, @PSB.PDA.ready, ReadPSB[]]; END; Process.SetPriority[ProcessPriorities.priorityPageFaultLow]; SpecialSpace.MakeProcedureResident[OutermostProcOf[Cork]]; IF ~myGF.alloced THEN SpecialSpace.MakeGlobalFrameResident[CedarSnapshotMain]; AnnounceCorked[]; UNTIL state = uncork DO --Process.--Yield[]; ENDLOOP; IF ~myGF.alloced THEN SpecialSpace.MakeGlobalFrameSwappable[CedarSnapshotMain]; SpecialSpace.MakeProcedureSwappable[OutermostProcOf[Cork]]; END; -- Space script management outloadFile: File.Capability; firstVMPage: File.PageNumber; vmPages: File.PageCount; RollbackAction: TYPE = {keep, delete}; SpaceScriptEntry: TYPE = RECORD [ rollbackAction: RollbackAction, fill: [0..17777B] ← 0, level: CachedSpace.Level, page: VM.PageNumber]; spaceScript: LONG DESCRIPTOR FOR ARRAY OF SpaceScriptEntry ← NIL; spaceScriptSpace: Space.Handle ← Space.nullHandle; nSpaceScriptPages: Space.PageCount ← initialSpaceScriptPages; nSpaceEntries: CARDINAL; Hierarchy: POINTER TO FRAME [HierarchyImpl] ← LOOPHOLE[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[HierarchyImplGfi]]]; VMMapLog: POINTER TO FRAME [MapLogImpl] ← LOOPHOLE[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[MapLogImplGfi]]]; InitializeSpaceScript: PROCEDURE = BEGIN IF spaceScriptSpace ~= Space.nullHandle THEN FlushSpaceScript[]; spaceScriptSpace ← Space.Create[nSpaceScriptPages, Space.virtualMemory]; Space.Map[spaceScriptSpace]; SpecialSpace.MakeResident[spaceScriptSpace]; spaceScript ← DESCRIPTOR[Space.LongPointer[spaceScriptSpace], nSpaceScriptPages*Environment.wordsPerPage/SIZE[SpaceScriptEntry]]; nSpaceEntries ← 2; -- leave slots for space/region data base and maplog vmPages ← 0; END; FlushSpaceScript: PROCEDURE = BEGIN SpecialSpace.MakeSwappable[spaceScriptSpace]; Space.Unmap[spaceScriptSpace]; Space.Delete[spaceScriptSpace]; END; EnumerateChildren: PROCEDURE [space: Space.Handle, proc: PROCEDURE [Space.Handle]] = BEGIN FOR child: Space.Handle ← Space.GetAttributes[space].lowestChild, Space.GetAttributes[child].nextSibling UNTIL child = Space.nullHandle DO proc[child]; ENDLOOP; END; AddSpace: PROCEDURE [ space: Space.Handle, size: Space.PageCount, rollbackAction: RollbackAction] = BEGIN -- Possible future optimization: don't enter the space in the list if it is -- dead (i.e., the backing storage contents are worthless). To do this, we must -- iterate through the region cache, looking at every descriptor that overlaps -- the interval associated with 'space' and ask about its state. If all regions -- happen to be dead, we don't enter the space in the spaceScript (if the -- rollback action is 'delete', we should delete the space now). sH: CachedSpace.Handle = LOOPHOLE[space]; IF nSpaceEntries = LENGTH[spaceScript] THEN ERROR ScriptFull; spaceScript[nSpaceEntries] ← [rollbackAction: rollbackAction, level: sH.level, page: sH.page]; nSpaceEntries ← nSpaceEntries + 1; vmPages ← vmPages + size; END; EntryToHandle: PROCEDURE [i: CARDINAL] RETURNS [Space.Handle] = BEGIN sH: CachedSpace.Handle = [level: spaceScript[i].level, page: spaceScript[i].page]; RETURN[LOOPHOLE[sH]] END; BuildSpaceScript: PROCEDURE [space: Space.Handle] = BEGIN size: Space.PageCount; mapped: BOOLEAN; [size: size, mapped: mapped] ← Space.GetAttributes[space]; IF mapped THEN BEGIN window: Space.WindowOrigin = Space.GetWindow[space]; SELECT TRUE FROM space = spaceScriptSpace, space = linkScriptSpace => NULL; window = Space.defaultWindow => BEGIN -- The following ugly code is used to determine whether the space under -- consideration is pinned. If so, there is no need to save it in the -- VM checkpoint, since it will be saved by OutLoad. (Also, as it turns -- out, the implementation of CopyOut/CopyIn can't handle such spaces, -- even though the operations make sense.) desc: CachedSpace.Desc; validSpace, validSwapUnit: BOOLEAN; [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@desc, LOOPHOLE[space]]; IF ~(validSpace AND ~validSwapUnit) THEN ERROR; IF ~desc.pinned THEN -- pinned spaces will be saved by OutLoad. BEGIN -- Because of limitations in the implementation of CopyIn/CopyOut, we -- dare not present a space beyond a certain size, even though it has -- swap units. If a space is too big and has swap units, we perform -- the CopyIn/CopyOut on the swap units instead. If, however, a big -- space has no swap units, we have two options: we can do the copy in -- a single lump and hope enough main memory can be found, or we can -- temporarily add our own swap units and copy them out. Although the -- latter course is feasible, it doesn't seem worth the effort, since -- the guy who created the space will cause it to swap as a unit when -- he touches it, and there will have to be sufficient main memory then -- to accommodate it. child: Space.Handle; parentSize: Space.PageCount; [size: parentSize, lowestChild: child] ← Space.GetAttributes[space]; IF size <= maxReasonableSwapUnit OR child = Space.nullHandle THEN AddSpace[space, size, keep] ELSE BEGIN -- We do the CopyOut on the swap units here. We must be careful, -- however, since the parent space may not be completely tiled with -- swap units. This can't happen with uniform swap units, only with -- explicit subspaces; for example, the bitmap allocated by UserTerminalImpl -- works this way. expectedBase: Space.PageOffset ← 0; FillTo: PROCEDURE [nextBase: Space.PageOffset] = BEGIN fillerSize: Space.PageCount = nextBase - expectedBase; filler: Space.Handle = Space.Create[size: fillerSize, parent: space, base: expectedBase]; AddSpace[filler, fillerSize, delete]; END; DO base: Space.PageOffset; size: Space.PageCount; nextSibling: Space.Handle; [base: base, size: size, nextSibling: nextSibling] ← Space.GetAttributes[child]; IF base ~= expectedBase THEN FillTo[expectedBase]; AddSpace[child, size, keep]; expectedBase ← base + size; IF nextSibling = Space.nullHandle THEN EXIT; child ← nextSibling; ENDLOOP; IF expectedBase ~= parentSize THEN FillTo[parentSize]; END; END; END; window.file.fID = File.nullID => NULL; -- initial, pinned space File.GetAttributes[window.file].type = PilotFileTypes.tVMBackingFile => BEGIN -- spaceScript[0]: space/region data base, spaceScript[1]: maplog -- Note: it is assumed that these two spaces are not pinned! (If they -- were, we wouldn't have to copy them.) sH: CachedSpace.Handle = LOOPHOLE[space]; spaceScript[IF window.base = 0 THEN 1 ELSE 0] ← [rollbackAction: keep, level: sH.level, page: sH.page]; vmPages ← vmPages + size; END; ENDCASE => -- This deactivation isn't really necessary, but we want to be friendly. -- After the rollback, non-pinned spaces mapped to persistent files (as opposed -- to data spaces) will reflect the current contents of the disk. Also, -- by doing a Deactivate here, we increase the amount of memory available for -- the CopyOuts of data spaces. Note: the spaces that are mapped to the files -- that implement the Common Software directory are fixed up explicitly during -- rollback by RationalizeDirectory, below. Deactivation here is not sufficient. Space.Deactivate[space]; END ELSE EnumerateChildren[space, BuildSpaceScript]; END; CheckpointVM: PROCEDURE = BEGIN window: Space.WindowOrigin; BEGIN ENABLE ScriptFull => {nSpaceScriptPages ← nSpaceScriptPages + 1; RETRY}; InitializeSpaceScript[]; EnumerateChildren[Space.virtualMemory, BuildSpaceScript]; END; [outloadFile, firstVMPage, outloadLocation] ← MakeSnapshotFile[volume, firstOutloadPage, vmPages]; SetPhase[2, checkpoint]; -- Initialize the window to leave space for the space/region data base. window ← [outloadFile, firstVMPage + Space.GetAttributes[EntryToHandle[0]].size]; -- Save all spaces in the script except the space/region data base. FOR i: CARDINAL IN [1..nSpaceEntries) DO space: Space.Handle = EntryToHandle[i]; size: Space.PageCount; mapped: BOOLEAN; [mapped: mapped, size: size] ← Space.GetAttributes[space]; Space.CopyOut[space: space, window: window]; -- The following is an optimization. If the entry in the space script -- corresponds to a swap unit of a mapped space (rather than the mapped -- space itself, it means the mapped space is "unreasonably large" (see -- comments in BuildSpaceScript). We would prefer not to have this -- unreasonably large space hanging around in memory, so we Deactivate it. IF ~mapped THEN Space.Deactivate[space]; window.base ← window.base + size; Twiddle[]; ENDLOOP; -- Write the space/region data base. This requires a bit of care, since we want to be -- certain that the data base saved in the outloadFile is consistent with the region and -- space caches saved by the Snapshot.Outload. Once the data base is written, no changes -- to the caches are permitted. Since the act of doing a CopyOut on the data base may cause -- the caches to be loaded with descriptors for the space and regions that describe the data -- base itself, we CopyOut the data base a second time, assuming that the caches will not -- change again. Note that we require that there be enough main memory to hold the entire -- space/region data base at one time. If, in the future, this becomes unworkable, a more -- elaborate scheme for copying out the data base in pieces without sacrificing its -- consistency will have to be worked out. window ← [outloadFile, firstVMPage]; Space.CopyOut[space: EntryToHandle[0], window: window]; Space.CopyOut[space: EntryToHandle[0], window: window]; -- no, you aren't seeing double... END; RollbackVM: PROCEDURE = BEGIN window: Space.WindowOrigin ← [outloadFile, firstVMPage]; prevMappingSpace: Space.Handle ← Space.nullHandle; readOnly: BOOLEAN; SetPhase[0, rollback]; FOR i: CARDINAL IN [0..nSpaceEntries) DO special: BOOLEAN = i < 2; -- space/region data base and maplog space: Space.Handle = EntryToHandle[i]; size: Space.PageCount; mapped: BOOLEAN; parent: Space.Handle; mappingSpace: Space.Handle ← space; [mapped: mapped, size: size, parent: parent] ← Space.GetAttributes[space]; IF ~mapped THEN DO isMapped: BOOLEAN; mappingSpace ← parent; [parent: parent, mapped: isMapped] ← Space.GetAttributes[mappingSpace]; IF isMapped THEN EXIT; IF parent = Space.virtualMemory THEN ERROR; ENDLOOP; IF special THEN readOnly ← FALSE ELSE IF prevMappingSpace ~= mappingSpace THEN BEGIN -- The following dicey bit of code ensures that anonymous memory exists -- to back up the data space being restored. It obtains the file ID of the -- backing file behind the data space and touches it (File.GetSize) to see if -- it still exists. If not, we create a new backing file by unmapping and -- remapping the space. Note that this will never remap a data space whose -- backing storage is in the VM backing file, since this is assumed not to -- go away. (In principle, the space monitor should be locked while calling -- Hierarchy.GetDescriptor, but we're already playing fast and loose here...) desc: CachedSpace.Desc; needFile: BOOLEAN ← FALSE; validSpace, validSwapUnit: BOOLEAN; -- We must reapply write protect to the previous space if it originally had it. IF readOnly THEN Space.MakeReadOnly[prevMappingSpace]; [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@desc, LOOPHOLE[mappingSpace]]; IF ~(validSpace AND ~validSwapUnit) THEN ERROR; [] ← File.GetSize[desc.window.file ! File.Unknown => {needFile ← TRUE; CONTINUE}]; IF needFile THEN BEGIN Space.Unmap[mappingSpace ! File.Unknown => VMMapLog.WriteLog1[interval: desc.interval, pSpaceD: NIL] -- the Unmap wasn't done --]; Space.Map[mappingSpace]; END; IF (readOnly ← desc.writeProtected) THEN BEGIN -- We must make the space writable before rolling it back. window: Space.WindowOrigin ← Space.GetWindow[mappingSpace]; window.file.permissions ← Inline.BITOR[window.file.permissions, write]; Space.MakeWritable[mappingSpace, window.file]; END; prevMappingSpace ← mappingSpace; END; Space.CopyIn[space: space, window: window]; -- The following is the same performance optimization as we did in -- CheckpointVM, with the additional provision that we don't deactivate -- "special" spaces, since they will come back in right away anyway. IF ~(mapped OR special) THEN Space.Deactivate[space]; window.base ← window.base + size; Twiddle[]; IF spaceScript[i].rollbackAction = delete THEN Space.Delete[space]; REPEAT FINISHED => IF readOnly THEN Space.MakeReadOnly[prevMappingSpace]; ENDLOOP; FlushSpaceScript[]; END; -- Link script management PLinkScriptEntry: TYPE = LONG ORDERED POINTER TO LinkScriptEntry; LinkScriptEntry: TYPE = MACHINE DEPENDENT RECORD [ frame: PrincOps.GlobalFrameHandle, mth: BcdOps.MTHandle, bcd: BcdOps.BcdBase, links: ARRAY [0..0) OF PrincOps.ControlLink]; linkScriptBase, linkScriptLimit: PLinkScriptEntry ← NIL; linkScriptSpace: Space.Handle ← Space.nullHandle; nLinkScriptPages: Space.PageCount ← initialLinkScriptPages; nLinkScriptEntries: CARDINAL; -- Until PilotLoaderOps is exported, we have to do the following ugliness: PilotLoaderOps: POINTER TO FRAME [PilotLoaderSupport] ← LOOPHOLE[Runtime.GlobalFrame[Runtime.LoadConfig]]; InitializeLinkScript: PROCEDURE = BEGIN IF linkScriptSpace ~= Space.nullHandle THEN FlushLinkScript[]; linkScriptSpace ← Space.Create[nLinkScriptPages, Space.virtualMemory]; Space.Map[linkScriptSpace]; linkScriptBase ← LOOPHOLE[Space.LongPointer[linkScriptSpace]]; linkScriptLimit ← linkScriptBase + nLinkScriptPages*Environment.wordsPerPage; nLinkScriptEntries ← 0; END; FlushLinkScript: PROCEDURE = BEGIN Space.Unmap[linkScriptSpace]; Space.Delete[linkScriptSpace]; END; CheckpointLinks: PROCEDURE = BEGIN OPEN PilotLoadStateOps; bootfID: File.ID = GetBootFileID[]; lSE: PLinkScriptEntry; GetBootFileID: PROCEDURE RETURNS [File.ID] = BEGIN bootFiles: Boot.LVBootFiles; SpecialVolume.GetLogicalVolumeBootFiles[systemVolume, @bootFiles]; RETURN[bootFiles[pilot].fID] END; InBootFile: PROCEDURE [p: LONG POINTER] RETURNS [BOOLEAN] = INLINE BEGIN OPEN Space; RETURN[GetWindow[GetHandle[PageFromLongPointer[p]]].file.fID = bootfID] END; ProcessBcd: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] = BEGIN bcd: BcdOps.BcdBase ← AcquireBcd[config]; IF ~InBootFile[bcd] THEN BEGIN map: Map ← GetMap[config]; ProcessLinks: PROCEDURE [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOLEAN] = BEGIN OPEN PrincOps, PrincOpsRuntime; rgfi: GFTIndex = map[mth.gfi]; gf: GlobalFrameHandle = GetFrame[GFT[rgfi]]; IF ~GetModule[rgfi].resolved AND gf.codelinks THEN BEGIN OPEN PilotLoaderOps; nLinks: CARDINAL ← PilotLoaderOps.LinkSegmentLength[mth, bcd]; nextEntry: PLinkScriptEntry ← lSE + SIZE[LinkScriptEntry] + nLinks*SIZE[ControlLink]; IF nextEntry > linkScriptLimit THEN {ReleaseMap[map]; ReleaseBcd[bcd]; ERROR ScriptFull}; lSE↑ ← [frame: gf, mth: mth, bcd: bcd, links: ]; OpenLinkSpace[gf, mth, bcd]; FOR i: CARDINAL IN [0..nLinks) DO lSE.links[i] ← ReadLink[i]; ENDLOOP; CloseLinkSpace[gf]; lSE ← nextEntry; nLinkScriptEntries ← nLinkScriptEntries + 1; Twiddle[]; END; RETURN [FALSE] END; [] ← BcdOps.ProcessModules[bcd, ProcessLinks]; ReleaseMap[map]; END; ReleaseBcd[bcd]; RETURN[FALSE] END; SetPhase[0, checkpoint]; [] ← InputLoadState[]; BEGIN ENABLE ScriptFull => {nLinkScriptPages ← nLinkScriptPages + 1; RETRY}; InitializeLinkScript[]; lSE ← linkScriptBase; [] ← EnumerateBcds[recentlast, ProcessBcd]; END; ReleaseLoadState[]; END; RollbackLinks: PROCEDURE = BEGIN lSE: PLinkScriptEntry ← linkScriptBase; SetPhase[1, rollback]; THROUGH [0..nLinkScriptEntries) DO OPEN PilotLoaderOps, PrincOps; nLinks: CARDINAL = LinkSegmentLength[lSE.mth, lSE.bcd]; OpenLinkSpace[lSE.frame, lSE.mth, lSE.bcd]; FOR i: CARDINAL IN [0..nLinks) DO WriteLink[i, lSE.links[i]]; ENDLOOP; CloseLinkSpace[lSE.frame]; lSE ← lSE + SIZE[LinkScriptEntry] + nLinks*SIZE[ControlLink]; Twiddle[]; ENDLOOP; FlushLinkScript[]; END; -- System volume cleanup LogicalVolume: POINTER TO FRAME [VolumeImpl] ← LOOPHOLE[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[VolumeImplGfi]]]; VolumeImplInterface: POINTER TO FRAME [VolumeImpl] ← LogicalVolume; CheckpointSystemVolume: PROCEDURE = BEGIN -- We now flush the file cache to make sure that files deleted after the -- checkpoint will not be "remembered" when the rollback occurs. It is -- assumed that uses of the space machinery by the volume open/close do not -- cause any changes in the space/region caches (for reasons described in -- CheckpointVM, above). This works because the volume stuff uses -- SimpleSpace exclusively, which pins all descriptors in the caches. Since -- this is a logical requirement of the FileMgr, we aren't likely to get -- burned in the future. WaitForDiskToIdle: PROCEDURE = BEGIN -- All this is because the disk must be quiet before we can close the volume. subVolumeFrame: POINTER TO FRAME [SubVolumeImpl] ← LOOPHOLE[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[SubVolumeImplGfi]]]; diskChannelFrame: POINTER TO FRAME [DiskChannelImpl]; BEGIN OPEN subVolumeFrame; FOR cePtr: CePtr ← ceFirst, cePtr + SIZE[CacheEntry] WHILE cePtr <= ceLast DO IF cePtr.occupied THEN BEGIN -- Some turkey put the following procedures in a DISCARD CODE PACK, so we -- REALLY get our hands dirty! -- DiskChannel.Idle[cePtr.svDesc.channel]; -- DiskChannel.Restart[cePtr.svDesc.channel]; base: Environment.Base = Environment.first64K; channelObj: LONG POINTER TO diskChannelFrame.ChannelObject ← @base[LOOPHOLE[cePtr.svDesc.channel, diskChannelFrame.ChannelHandle]]; UNTIL channelObj.ioCount = 0 DO Process.Pause[Process.SecondsToTicks[1]]; ENDLOOP; END; ENDLOOP; END; END; SetPhase[3, checkpoint]; WaitForDiskToIdle[]; -- We now want to close the system volume, causing Pilot (specifically, the FileMgr) to -- forget everything it has cached about the contents of the system volume. This includes -- the file cache, the VFM, and the volume root page. LogicalVolume.CloseLogicalVolume -- should do all of these, but because of a bug, it retains a cached copy of the -- volume root page. To get around this, witness the crazy stuff below with -- bringing subvolumes on- and off-line, and observe the 'root' parameter is TRUE in -- the offline case. This has the effect of leaving the logical volume table unchanged, -- but causing the volume root page to be flushed. Look at the code in VolumeImpl if -- you are skeptical. VolumeImplInterface.SubvolumeOnline[lvID: systemVolume, root: TRUE]; LogicalVolume.CloseLogicalVolume[@systemVolume]; VolumeImplInterface.SubvolumeOffline[lvID: systemVolume, root: TRUE]; END; RollbackSystemVolume: PROCEDURE = BEGIN [] ← LogicalVolume.OpenLogicalVolume[@systemVolume]; END; -- OutLoad implementation OutLoad: PROCEDURE RETURNS [inLoaded: BOOLEAN] = BEGIN -- This code is stolen directly from SnapshotImpl.OutLoad. psb: PSB.PsbHandle; ptc: CARDINAL; wdc: CARDINAL; -- Save process state not captured in PDA: ProcessInternal.DisableInterrupts[]; -- make it hold still first psb ← ProcessOperations.ReadPSB[]; ptc ← ProcessOperations.ReadPTC[]; wdc ← ProcessOperations.ReadWDC[]; DeviceCleanup.Perform[turnOff]; -- turn all devices off. -- Save our state on a boot file: (If the boot file is inloaded -- later, we will reappear here with inLoaded=TRUE.) inLoaded ← BootSwap.OutLoad[@outloadLocation, restore] ~= outLoaded; IF inLoaded THEN BEGIN -- Restore process state not captured in PDA. ProcessOperations.WriteWDC[wdc]; ProcessOperations.WritePTC[ptc]; ProcessOperations.WritePSB[psb]; -- The following is a temporary substitute for a clock chip. -- We must do it with interrupts off or Communication will be using the Ethernet-- -- if we get an allocation trap, all is lost. [] ← TemporarySetGMT.SetGMT[]; END; DeviceCleanup.Perform[turnOn]; -- turn devices back on ProcessorFace.SetMP[PilotMP.cClient]; -- announce our return ProcessInternal.EnableInterrupts[]; END; -- Directory cleanup RationalizeDirectory: PROCEDURE = BEGIN directoryFilesFrame: POINTER TO FRAME [DirectoryFilesImpl] ← LOOPHOLE[Runtime.GlobalFrame[Directory.CreateFile]]; SetPhase[2, rollback]; FOR i: CARDINAL IN [1 .. DirectoryFiles.maxDCache] DO dCE: LONG POINTER TO DirectoryFiles.DCEntry = @directoryFilesFrame.directoryCache[i]; IF dCE.refCount > 0 THEN BEGIN -- This fixes up the space descriptor (in particular, the countMapped -- information) to account for any change in file size. window: Space.WindowOrigin = Space.GetWindow[dCE.dir.space]; Space.Unmap[dCE.dir.space]; Space.Map[dCE.dir.space, window]; Twiddle[]; END; ENDLOOP; END; -- *** Here's the real work *** systemVolume: Volume.ID ← Volume.systemID; CorkRestOfWorld[]; IF volume = Volume.nullID THEN volume ← systemVolume; -- Save all necessary code links CheckpointLinks[]; -- Save space/region data base, maplog, and all data spaces. BEGIN CheckpointVM[ ! Volume.InsufficientSpace => GO TO cantCheckpoint]; EXITS cantCheckpoint => {UncorkRestOfWorld[]; RETURN[insufficientDiskSpace]}; END; -- Flush file cache and shut down the system volume. CheckpointSystemVolume[]; -- Save real memory. Note: we can't use Snapshot.Outload because it requires -- that the volume containing the boot file be open, and we can't tolerate that. IF ~OutLoad[].inLoaded THEN BEGIN RollbackSystemVolume[]; -- system volume must be open before we proceed! UncorkRestOfWorld[]; RETURN[checkpointed] END; -- Inload has restored real memory. Reopen the system volume before restoring VM. RollbackSystemVolume[]; -- Restore space/region data base, maplog, and data spaces. RollbackVM[]; -- Restore code links that may have been invalidated. RollbackLinks[]; -- Now that memory is back together, make the directory system believe the current -- state of the directory files on the disk. RationalizeDirectory[]; UncorkRestOfWorld[]; RETURN[rolledBack] END; -- Rollback -- RollBack: PUBLIC PROCEDURE [volume: Volume.ID ← Volume.nullID] = BEGIN bootFiles: Boot.LVBootFiles; IF volume = Volume.nullID THEN volume ← Volume.systemID; SpecialVolume.GetLogicalVolumeBootFiles[volume, @bootFiles]; IF volume ~= Volume.systemID THEN VolumeExtras.OpenVolume[volume: volume, readOnly: TRUE]; Snapshot.InLoad[ pMicrocode: NIL, pGerm: NIL, countGerm: 0, file: [fID: bootFiles[clientImage].fID, permissions: read], firstPage: bootFiles[clientImage].firstPage ! ANY => CONTINUE]; -- Note: execution normally continues inside Checkpoint. If the -- above InLoad failed, we make sure that there is no trace of a -- possibly bad checkpoint file before returning to the caller of -- Rollback, who can decide what to do instead of the rollback. bootFiles[clientImage].fID ← File.nullID; SpecialVolume.SetLogicalVolumeBootFiles[volume, @bootFiles]; END; -- Utilities -- MakeSnapshotFile: PROCEDURE [ volume: Volume.ID, firstRMPage: File.PageNumber, pagesForVM: File.PageCount] RETURNS [file: File.Capability, firstVMPage: File.PageNumber, location: disk Boot.Location] = BEGIN pagesForRM: File.PageCount = BootFile.MemorySizeToFileSize[SpecialSpace.realMemorySize]; filePages: File.PageCount = firstRMPage + pagesForRM + pagesForVM; bootFiles: Boot.LVBootFiles; found: BOOLEAN; CheckForExistingFile: PROCEDURE RETURNS [found: BOOLEAN, file: File.Capability] = BEGIN found ← FALSE; IF bootFiles[clientImage].fID = File.nullID THEN RETURN; file ← [fID: bootFiles[clientImage].fID, permissions: read+write+delete]; IF File.GetSize[file ! File.Unknown => GO TO noGood] < filePages THEN {File.Delete[file]; GO TO noGood}; found ← TRUE; EXITS noGood => BEGIN bootFiles[clientImage].fID ← File.nullID; SpecialVolume.SetLogicalVolumeBootFiles[volume, @bootFiles]; END; END; SetPhase[1, checkpoint]; SpecialVolume.GetLogicalVolumeBootFiles[volume, @bootFiles]; [found, file] ← CheckForExistingFile[]; IF ~found THEN file ← File.Create[volume, filePages, FileTypes.tUntypedFile]; Twiddle[]; [] ← SpecialFile.MakeBootable[ file: file, firstPage: firstRMPage, count: pagesForRM, lastLink: SpecialFile.Link[0]]; Twiddle[]; location.diskFileID ← [fID: file.fID, firstPage: firstRMPage, da:]; [deviceType: location.deviceType, deviceOrdinal: location.deviceOrdinal, link: LOOPHOLE[location.diskFileID.da, SpecialFile.Link]] ← KernelFile.GetBootLocation[file, firstRMPage]; bootFiles[clientImage] ← [file.fID, firstRMPage, LOOPHOLE[location.diskFileID.da]]; SpecialVolume.SetLogicalVolumeBootFiles[volume, @bootFiles]; RETURN[File.LimitPermissions[file, read+write], firstRMPage + pagesForRM, location] END; -- Feedback to User -- cStart: CARDINAL = 0; cEnd: CARDINAL = SIZE[UserTerminal.CursorArray]; cMiddle: CARDINAL = (cEnd + cStart) / 2; Direction: TYPE = {checkpoint, rollback}; cursors: ARRAY Direction OF UserTerminal.CursorArray = [[075122B, 041122B, 041124B, 041734B, 041124B, 041122B, 075122B, 000000B, 017370B, 011040B, 011040B, 016040B, 010040B, 010040B, 010040B, 000000B], [167210B, 125210B, 125210B, 145210B, 125210B, 125210B, 127356B, 000000B, 147352B, 125212B, 125212B, 147214B, 125212B, 125212B, 145352B, 000000B]]; SetPhase: PROCEDURE [phase: [0..7], direction: Direction] = BEGIN cursor: UserTerminal.CursorArray ← cursors[direction]; topMasks: ARRAY [0..4) OF WORD = [0, 177400B, 377B, 177777B]; bottomMask: WORD = 177400B; topMask: WORD = topMasks[phase MOD 4]; IF topMask ~= 0 THEN FOR i: CARDINAL IN [0..cMiddle) DO cursor[i] ← Inline.BITXOR[cursor[i], topMask]; ENDLOOP; IF phase > 4 THEN FOR i: CARDINAL IN [cMiddle..cEnd) DO cursor[i] ← Inline.BITXOR[cursor[i], bottomMask]; ENDLOOP; UserTerminal.SetCursorPattern[cursor]; END; Twiddle: PROCEDURE = BEGIN cursor: UserTerminal.CursorArray ← UserTerminal.GetCursorPattern[]; FOR i: CARDINAL IN [cMiddle..cEnd) DO cursor[i] ← Inline.BITXOR[cursor[i], 377B]; ENDLOOP; UserTerminal.SetCursorPattern[cursor]; END; END.