RollbackImpl.mesa (Checkpoint and Rollback: VM copying)
Copyright © 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell December 7, 1983 1:22 pm
Levin, September 22, 1983 1:48 pm
Russ Atkinson (RRA) June 5, 1985 3:33:47 pm PDT
DIRECTORY
BasicTime USING [ FromPupTime, Now, ToPupTime ],
BootFile USING [Entry, Header, maxEntriesPerHeader, maxEntriesPerTrailer, MemorySizeToFileSize, TrailerArray, TrailerStart ],
Booting USING [ Boot, Switches, switches ],
DebuggerFormat USING [ExternalStateVector, LabelChecksum, Run, SwapInfo, VersionID, VMRunTable ],
Disk USING [ Channel, DoIO, DriveAttributes, Label, NextChannel, ok, Request, Status ],
DiskFace USING [ Type ],
File USING [Error, FindVM, FP, Handle, Info, Open, PageCount, PageNumber, Read, SetSize, SystemVolume, wordsPerPage, Write ],
FileBackdoor USING [GetRoot],
IO USING [ GetChar, Flush, PutChar, PutF, PutRope, STREAM ],
PrincOps USING [ flagsVacant, PageCount, PageNumber, PageValue, PDA, wordsPerPage ],
PrincOpsUtils USING [ LongCopy ],
ProcessorFace USING [ GetNextAvailableVM ],
Rope USING [ ROPE ],
SimpleTerminal USING [ TurnOn ],
SystemVersion USING [ bootFileDate, Date, release, ReleaseNumber ],
VM USING [AddressForPageNumber, CantAllocate, Interval, PageCount, PageNumber, PagesForWords, Pin, SimpleAllocate],
VMBacking USING [StateFromPageValue];
RollbackImpl: MONITOR
IMPORTS BasicTime, BootFile, Booting, DebuggerFormat, Disk, File, FileBackdoor, IO, PrincOpsUtils, ProcessorFace, SimpleTerminal, SystemVersion, VM, VMBacking = {
vmPagesPerFilePage: VM.PageCount = VM.PagesForWords[File.wordsPerPage];
valuesPerPage: CARDINAL = PrincOps.wordsPerPage / SIZE[PrincOps.PageValue];
PageValueArray: TYPE = ARRAY [0..valuesPerPage) OF PrincOps.PageValue;
mapEntriesPerPage: CARDINAL = valuesPerPage;
Accessing outload image on checkpoint file. (Mainly stolen from WVMCache.mesa)
outLdFile: File.Handle ← NIL;
outLdFirstPage: File.PageNumber;
outLdSpace: VM.Interval = VM.SimpleAllocate[vmPagesPerFilePage];
outLdPage: File.PageNumber ← [LAST[INT]];
outLdAddress: LONG POINTER TO ARRAY [0..PrincOps.wordsPerPage) OF WORD =
VM.AddressForPageNumber[outLdSpace.page];
ReadOutLdPage: PROC [page: File.PageNumber] = {
IF outLdPage # page THEN File.Read[outLdFile, outLdPage←page, 1, outLdAddress];
};
Layout of boot and outload files
BFMEntryIndex: TYPE = [0..MAX[BootFile.maxEntriesPerHeader,
BootFile.maxEntriesPerTrailer]];
BootFileMapItem: TYPE = RECORD [
link: BFMPointer ← NIL,
lastMemPage: PrincOps.PageNumber,
filePageOffset: File.PageNumber,
entryCount: BFMEntryIndex,
entries: EntrySeq];
EntrySeq: TYPE = LONG POINTER TO BootFile.TrailerArray;
BFMPointer: TYPE = REF BootFileMapItem;
bfmHead: BFMPointer ← NIL;
OpenOutLdFile: PROC = {
bfm: BFMPointer;
bfh: LONG POINTER TO BootFile.Header;
entries: EntrySeq;
curmax: CARDINAL ← BootFile.maxEntriesPerHeader;
curbase: File.PageNumber ← [0];
OutLdPageSpace: PROC RETURNS [vm: LONG POINTER] = {
returns pointer into vm containing data from outload file map page
space: VM.Interval = VM.SimpleAllocate[vmPagesPerFilePage];
vm ← VM.AddressForPageNumber[space.page];
File.Read[outLdFile, curbase, 1, vm];
};
pagesremaining: CARDINAL;
bfmHead ← NIL;
bfh ← OutLdPageSpace[];
entries ← LOOPHOLE[@bfh.entries];
pagesremaining ← bfh.countData;
bfmHead ← bfm ← NEW[BootFileMapItem];
DO
bfm.entries ← entries;
bfm.entryCount ← MIN[curmax, pagesremaining];
bfm.lastMemPage ← bfm.entries[bfm.entryCount-1].page;
bfm.filePageOffset ← [curbase+1];
pagesremaining ← pagesremaining - bfm.entryCount;
IF pagesremaining = 0 THEN EXIT;
curbase ← [bfm.filePageOffset+bfm.entryCount];
curmax ← BootFile.maxEntriesPerTrailer;
entries ← OutLdPageSpace[] + BootFile.TrailerStart;
bfm.link ← NEW[BootFileMapItem];
bfm ← bfm.link;
ENDLOOP;
};
PageNotFound: ERROR[mempage: PrincOps.PageNumber] = CODE;
MalformedOutload: ERROR = CODE;
SearchOutLdFile: PROCEDURE [mempage: PrincOps.PageNumber] = {
Leaves result in outLdAddress
FOR bfm: BFMPointer ← bfmHead, bfm.link UNTIL bfm = NIL DO
IF mempage <= PrincOps.PageNumber[bfm.lastMemPage] THEN {
it's in this page of the bootFileMap
FOR i: BFMEntryIndex IN [0..bfm.entryCount] DO
SELECT PrincOps.PageNumber[bfm.entries[i].page] FROM
< mempage => NULL;
> mempage => ERROR PageNotFound[mempage];
ENDCASE => {
ReadOutLdPage[[bfm.filePageOffset+i]];
RETURN
};
REPEAT FINISHED => ERROR MalformedOutload[];
ENDLOOP;
};
REPEAT FINISHED => ERROR PageNotFound[mempage]
ENDLOOP;
};
CopyRead: PUBLIC PROC [from: INT, nwords: INT, to: LONG POINTER] = {
Reads from address space of outload file (cf. WorldVM.CopyRead)
n: PrincOps.PageNumber;
w: [0..PrincOps.wordsPerPage);
WHILE nwords > 0
DO amount: (0..PrincOps.wordsPerPage];
n ← from / PrincOps.wordsPerPage;
w ← from - n * LONG[PrincOps.wordsPerPage];
SearchOutLdFile[n];
amount ← MIN[nwords,PrincOps.wordsPerPage-w];
PrincOpsUtils.LongCopy[from: @outLdAddress[w], to: to, nwords: amount];
nwords ← nwords - amount; from ← from + amount; to ← to + amount;
ENDLOOP;
};
Transfers from backing store (also stolen from WVMCache.mesa)
UnknownDrive: ERROR = CODE;
BadTransfer: ERROR = CODE;
DiskAddress: TYPE = RECORD[
deviceType: DiskFace.Type,
deviceOrdinal: CARDINAL,
diskPage: LONG CARDINAL, -- device-relative page number of start of run
offset: CARDINAL, -- page number from start of run, for label checksum calculation
labelCheck: CARDINAL -- label checksum of start of run
];
lastChannel: Disk.Channel ← NIL;
lastType: DiskFace.Type;
lastOrdinal: CARDINAL;
TransferBackingRun: PUBLIC ENTRY PROC [
data: LONG POINTER,
label: LONG POINTER TO Disk.Label,
which: { readAndChecksum, readAndVerify, write },
count: INT,
addr: DiskAddress] = {
Assumes data is pinned and VM page aligned
ENABLE UNWIND => NULL;
req: Disk.Request;
status: Disk.Status;
countDone: INT;
IF lastChannel = NIL OR addr.deviceType # lastType OR addr.deviceOrdinal # lastOrdinal THEN {
FOR lastChannel ← Disk.NextChannel[NIL], Disk.NextChannel[lastChannel] UNTIL lastChannel = NIL DO
[type: lastType, ordinal: lastOrdinal] ← Disk.DriveAttributes[lastChannel];
IF addr.deviceType = lastType AND addr.deviceOrdinal = lastOrdinal THEN EXIT;
REPEAT FINISHED => ERROR UnknownDrive[];
ENDLOOP;
};
req ← [
diskPage: [addr.diskPage],
data: data,
command: SELECT which FROM
readAndChecksum => [header: verify, label: read, data: read],
readAndVerify => [header: verify, label: verify, data: read],
write => [header: verify, label: verify, data: write],
ENDCASE => ERROR,
count: count ];
[status, countDone] ← Disk.DoIO[lastChannel, label, @req];
IF status # Disk.ok THEN ERROR BadTransfer[];
IF which = readAndChecksum AND DebuggerFormat.LabelChecksum[label^, addr.offset + count-1] # addr.labelCheck THEN ERROR BadTransfer[];
label.filePage ← label.filePage - (count-1);
};
VM backing data structure (ReadBackingMap stolen from WVMBackingMap.mesa)
ReadBackingMap: PROC RETURNS [table: REF DebuggerFormat.VMRunTable] = {
swapInfo: DebuggerFormat.SwapInfo;
swapInfoAddr: INT = LOOPHOLE[@PrincOps.PDA.available];
esv: DebuggerFormat.ExternalStateVector;
esvAddr: INT;
CopyRead[from: swapInfoAddr, nwords: SIZE[DebuggerFormat.SwapInfo], to: @swapInfo];
esvAddr ← LOOPHOLE[swapInfo.externalStateVector];
CopyRead[esvAddr, SIZE[DebuggerFormat.ExternalStateVector], @esv];
SELECT esv.versionident FROM
DebuggerFormat.VersionID => {
IF esv.vmRunTable = NIL
THEN table ← NIL
ELSE {
temp: ARRAY [0..SIZE[DebuggerFormat.VMRunTable[0]]) OF WORD;
cheat: POINTER TO DebuggerFormat.VMRunTable = LOOPHOLE[@temp];
CopyRead[
LOOPHOLE[esv.vmRunTable, INT],
SIZE[DebuggerFormat.VMRunTable[0]],
@temp ];
table ← NEW[DebuggerFormat.VMRunTable[cheat.length]];
CopyRead[
LOOPHOLE[esv.vmRunTable, INT],
SIZE[DebuggerFormat.VMRunTable[cheat.length]],
LOOPHOLE[table, LONG POINTER] ];
};
};
ENDCASE => table ← NIL;
};
Top level procedure
in, out: IO.STREAM;
vmImageVersion: CARDINAL = 1;
VMImageHeader: TYPE = MACHINE DEPENDENT RECORD[
version(0): CARDINAL ← vmImageVersion,
runs(1): INT,
pages(3): INT,
bootVersion(5): SystemVersion.ReleaseNumber,
bootFileDate(8): SystemVersion.Date,
checkpointDate(10): SystemVersion.Date
];
VMImageRun: TYPE = MACHINE DEPENDENT RECORD[
pages: CARDINAL,
deviceType: DiskFace.Type,
deviceOrdinal: CARDINAL,
diskPage: LONG CARDINAL,
label: Disk.Label];
vmImageRunsPerPage: CARDINAL = File.wordsPerPage / SIZE[VMImageRun];
VMImageRunTable: TYPE = ARRAY [0..vmImageRunsPerPage) OF VMImageRun;
vmImageBufferPages: NAT ← 256;
NoBackingStore: ERROR[PrincOps.PageNumber] = CODE;
ShortFile: ERROR = CODE;
WrongPageCount: ERROR = CODE;
which: { checkpoint, rollback };
Complain: PROC [msg: Rope.ROPE] = {
out.PutRope[msg];
out.PutRope["\nType any character to "];
out.PutRope[IF which = checkpoint
THEN "resume the outloaded session: "
ELSE "re-boot with the \"f\" switch: "];
in.Flush[];
[] ← in.GetChar[];
IF which = checkpoint
THEN [] ← Booting.Boot[boot: [file[outLdFile, outLdFirstPage]], switches: ALL[FALSE] ]
ELSE [] ← Booting.Boot[boot: [self[]], switches: [f: TRUE] ];
ERROR
};
DoIt: PROC = TRUSTED {
ENABLE {
File.Error =>
Complain[SELECT why FROM
wentOffline => "System volume has gone offline",
nonCedarVolume => "System volume is not a cedar volume",
inconsistent => "System volume or checkpoint file is inconsistent - scavenge",
software => "Label-check when accessing the checkpoint file",
hardware => "Hard disk error when accessing the checkpoint file",
unknownFile => "The checkpoint file does not exist",
unknownPage => "Bogus checkpoint file (unknownPage)",
volumeFull => "Can't checkpoint (system volume full)",
fragmented => "Can't checkpoint (system volume fragmented)",
ENDCASE => "Unexpected file error - consult expert"];
PageNotFound => Complain["Can't find required swapped-in page in outload"];
MalformedOutload => Complain["The outload appears to be malformed"];
UnknownDrive => Complain["Can't find the disk drive for a swapped-out page"];
BadTransfer => Complain["Disk error when accessing a swapped out page"];
NoBackingStore => Complain["Can't find the backing store for a swapped out page"];
ShortFile => Complain["The checkpoint file appears to be truncated"];
WrongPageCount => Complain["I'm confused about how many pages to transfer"];
};
outloadDataPages: PrincOps.PageCount;
countVM: INT ← 0;
mapPages: CARDINAL;
mapBufferSpace: VM.Interval = VM.SimpleAllocate[VM.PagesForWords[File.wordsPerPage]];
mapBuffer: LONG POINTER TO PageValueArray =
VM.AddressForPageNumber[mapBufferSpace.page];
mapStart: File.PageNumber; -- start of map dump in checkpoint file
vmImagePos: File.PageNumber; -- start of vmImage in checkpoint file
vmImageBufferSpace: VM.Interval = AllocateBuffer[];
vmImageBufferAddr: LONG POINTER = VM.AddressForPageNumber[vmImageBufferSpace.page];
vmImageHeader: LONG POINTER TO VMImageHeader = vmImageBufferAddr;
vmImageRunTable: REF VMImageRunTable ← NEW[VMImageRunTable];
AllocateBuffer: PROC RETURNS [interval: VM.Interval] = {
Just in case we are running tight enough to fall back to a smaller interval
DO
RETURN [VM.SimpleAllocate[vmImageBufferPages
! VM.CantAllocate =>
IF vmImageBufferPages > 4
THEN {vmImageBufferPages ← vmImageBufferPages / 2; LOOP}
ELSE REJECT;
]];
ENDLOOP;
};
GetMapStart: PROC RETURNS [File.PageNumber] = TRUSTED {
fp: File.FP;
header: LONG POINTER TO BootFile.Header = LOOPHOLE[mapBuffer];
[fp: fp, page: outLdFirstPage] ← FileBackdoor.GetRoot[File.SystemVolume[], checkpoint];
outLdFile ← File.Open[File.SystemVolume[], fp];
File.Read[file: outLdFile, from: outLdFirstPage, nPages: 1, to: header];
outloadDataPages ← header.countData;
RETURN [[outLdFirstPage+BootFile.MemorySizeToFileSize[outloadDataPages]]]
};
[in: in, out: out] ← SimpleTerminal.TurnOn[];
VM.Pin[vmImageBufferSpace];
out.PutRope[IF which = checkpoint THEN "\nCheckpoint." ELSE "\nRollback."];
{
count: PrincOps.PageCount;
page: PrincOps.PageNumber;
FOR countVM ← 0, page + count DO
[firstPage: page, count: count] ← ProcessorFace.GetNextAvailableVM[countVM];
IF count = 0 THEN EXIT;
ENDLOOP;
};
mapPages ← (countVM + mapEntriesPerPage - 1) / mapEntriesPerPage;
mapStart ← GetMapStart[];
vmImagePos ← [mapStart+mapPages];
IF which = checkpoint THEN {
Copy from VM backing storage to checkpoint file, then inload from checkpoint file
GetMapPage: PROC [p: CARDINAL] = TRUSTED
{ File.Read[file: outLdFile, from: [mapStart+p], nPages: 1, to: mapBuffer] };
PutMapPage: PROC [p: CARDINAL] = TRUSTED
{ File.Write[file: outLdFile, to: [mapStart+p], nPages: 1, from: mapBuffer] };
firstEnumerate: BOOLTRUE;
InterestingPage: PROC [e: [0..mapEntriesPerPage)] RETURNS [yes: BOOL] = TRUSTED {
mapEntry: PrincOps.PageValue = mapBuffer[e];
IF mapEntry.state.flags = PrincOps.flagsVacant
THEN yes ← VMBacking.StateFromPageValue[mapEntry] = active
ELSE {
yes ← FALSE;
Mark it dirty because we won't restore the backing disk.
And mark it referenced so it won't look vacant!
( "firstEnumerate" optimizes so we only update the entry once )
IF firstEnumerate THEN {
mapBuffer[e].state.flags.referenced ← TRUE;
mapBuffer[e].state.flags.dirty ← TRUE;
};
};
};
firstPassCount: INT ← 0; -- consistency check between passes of EnumerateRuns
table: REF DebuggerFormat.VMRunTable;
The following are set by EnumerateRuns before calling "work":
runSize: CARDINAL; -- pages in current run;
runStart: VM.PageNumber; -- page number of first page in run
backingRun: LONG POINTER TO DebuggerFormat.Run ← NIL; -- covers entire run
EnumerateRuns: PROC [work: PROC] = {
call "work" for each run of interesting pages
pageNumber: VM.PageNumber ← 0;
runSize ← 0;
FOR p: CARDINAL IN [0..mapPages) DO
GetMapPage[p];
FOR e: [0..mapEntriesPerPage) IN [0..mapEntriesPerPage) DO
interesting: BOOL = InterestingPage[e];
IF runSize > 0 AND (NOT interesting OR pageNumber NOT IN
[backingRun.page..backingRun.page+backingRun.count) ) THEN { -- end of current run
work[];
runSize ← 0;
};
IF interesting THEN {
IF runSize = 0 THEN {
start of a new run
IF backingRun = NIL OR pageNumber NOT IN [backingRun.page..backingRun.page+backingRun.count)
THEN { -- find entry in backing run table
IF table = NIL THEN ERROR NoBackingStore[pageNumber];
FOR i: CARDINAL IN [0..table.nRuns) DO
backingRun ← @table[i];
IF pageNumber IN [backingRun.page..backingRun.page+backingRun.count)
THEN EXIT;
REPEAT FINISHED => ERROR NoBackingStore[pageNumber]
ENDLOOP;
};
runStart ← pageNumber;
};
runSize ← runSize+1;
};
pageNumber ← pageNumber+1;
ENDLOOP;
IF firstEnumerate THEN PutMapPage[p];
ENDLOOP;
IF runSize > 0 THEN work[];
firstEnumerate ← FALSE;
};
OpenOutLdFile[];
table ← ReadBackingMap[];
vmImageHeader^ ← [
runs: 0,
pages: 0,
bootVersion: SystemVersion.release,
bootFileDate: SystemVersion.bootFileDate,
checkpointDate: BasicTime.ToPupTime[BasicTime.Now[]]
];
{ -- First pass: count runs to determine file size needed
fileSize: File.PageCount; -- required checkpoint file size
CountRuns: PROC = {
vmImageHeader.runs ← vmImageHeader.runs+1;
vmImageHeader.pages ← vmImageHeader.pages + runSize;
};
out.PutRope["\nCounting swapped-out VM pages ... "];
EnumerateRuns[CountRuns];
firstPassCount ← vmImageHeader.pages;
fileSize ← vmImagePos+1+vmImageHeader.pages+
(vmImageHeader.runs +vmImageRunsPerPage-1) / vmImageRunsPerPage;
out.PutF["\nThe VM has %g pages, of which %g are swapped-in and %g are swapped-out (in %g runs).\nEnsuring checkpoint file length is at least %g ... ",
[integer[countVM]],
[integer[outloadDataPages]],
[integer[vmImageHeader.pages]],
[integer[vmImageHeader.runs]],
[integer[fileSize]]
];
IF File.Info[outLdFile].size < fileSize THEN File.SetSize[outLdFile, fileSize];
}; -- first pass
Write the header to the checkpoint file
File.Write[outLdFile, vmImagePos, 1, vmImageHeader];
vmImagePos ← [vmImagePos+1];
{ -- Second pass: transfer the pages
vmImageRunTablePos: [0..vmImageRunsPerPage] ← 0;
pages: CARDINAL ← 0; -- pages used in vmImageBuffer
Copier: PROC = -- Add run to the vmImageRuntable we're building
{
offset: CARDINAL = runStart-backingRun.page;
vmImageRunTable[vmImageRunTablePos] ← [
pages: runSize,
deviceType: backingRun.deviceType,
deviceOrdinal: backingRun.deviceOrdinal,
diskPage: backingRun.diskPage + offset,
label: NULL ];
Read first page of run to get its label (sigh!)
TransferBackingRun[
data: vmImageBufferAddr + pages * PrincOps.wordsPerPage,
label: @vmImageRunTable[vmImageRunTablePos].label,
which: readAndChecksum,
count: 1,
addr: [
deviceType: vmImageRunTable[vmImageRunTablePos].deviceType,
deviceOrdinal: vmImageRunTable[vmImageRunTablePos].deviceOrdinal,
diskPage: vmImageRunTable[vmImageRunTablePos].diskPage,
offset: offset,
labelCheck: backingRun.labelCheck]
];
vmImageRunTablePos ← vmImageRunTablePos+1;
IF vmImageRunTablePos = vmImageRunsPerPage THEN CopyToBuffer[];
};
CopyToBuffer: PROC = {
Read the runs into the buffer
LOOPHOLE[vmImageBufferAddr + pages * PrincOps.wordsPerPage,
LONG POINTER TO VMImageRunTable]^ ← vmImageRunTable^;
pages ← pages + 1;
firstPassCount ← firstPassCount + 1;
IF pages = vmImageBufferPages THEN WriteBuffer[];
FOR r: CARDINAL IN [0..vmImageRunTablePos) DO
total: CARDINAL = vmImageRunTable[r].pages;
reqd: CARDINAL ← total;
WHILE reqd > 0 DO
amount: CARDINAL = MIN[vmImageBufferPages-pages, reqd];
label: Disk.Label ← vmImageRunTable[r].label;
label.filePage ← label.filePage + (total-reqd);
TransferBackingRun[
data: vmImageBufferAddr + pages * PrincOps.wordsPerPage,
label: @label,
which: readAndVerify,
count: amount,
addr: [
deviceType: vmImageRunTable[r].deviceType,
deviceOrdinal: vmImageRunTable[r].deviceOrdinal,
diskPage: vmImageRunTable[r].diskPage + (total-reqd),
offset: 0,
labelCheck: 0 ]
];
pages ← pages + amount;
IF pages = vmImageBufferPages THEN WriteBuffer[];
reqd ← reqd - amount;
ENDLOOP;
ENDLOOP;
vmImageRunTablePos ← 0;
};
WriteBuffer: PROC = {
Write from buffer into checkpoint file
File.Write[outLdFile, vmImagePos, pages, vmImageBufferAddr];
firstPassCount ← firstPassCount - pages;
vmImagePos ← [vmImagePos+pages];
out.PutChar['.];
pages ← 0;
};
out.PutF["ok\nCopying VM into checkpoint file (each dot = %g pages transferred) ",
[integer[vmImageBufferPages]]];
EnumerateRuns[Copier];
IF vmImageRunTablePos # 0 THEN CopyToBuffer[];
WriteBuffer[];
IF firstPassCount # 0 THEN ERROR WrongPageCount[];
}; -- second pass
}
ELSE {
Copy from checkpoint file to VM backing storage, then inload from checkpoint file
remainingFile: File.PageCount; -- amount of file that should remain
remainingRuns: INT;
pages: CARDINAL ← 0; -- position in vmImageBuffer
ReadBuffer: PROC = {
amount: File.PageCount = MIN[vmImageBufferPages, remainingFile];
File.Read[outLdFile, vmImagePos, amount, vmImageBufferAddr];
vmImagePos ← [vmImagePos+amount];
remainingFile ← remainingFile - amount;
pages ← 0;
out.PutChar['.];
};
ReadImageRunTable: PROC = {
vmImageRunTable^ ← LOOPHOLE[vmImageBufferAddr + pages * PrincOps.wordsPerPage,
LONG POINTER TO VMImageRunTable]^;
pages ← pages + 1;
IF pages = vmImageBufferPages THEN ReadBuffer[];
};
File.Read[outLdFile, vmImagePos, 1, vmImageHeader];
vmImagePos ← [vmImagePos+1];
out.PutF["\nThis checkpoint was created on %g by boot file version %g.%g.%g of %g",
[time[BasicTime.FromPupTime[vmImageHeader.checkpointDate]]],
[integer[vmImageHeader.bootVersion.major]],
[integer[vmImageHeader.bootVersion.minor]],
[integer[vmImageHeader.bootVersion.patch]],
[time[BasicTime.FromPupTime[vmImageHeader.bootFileDate]]]
];
out.PutF["\nThere were %g swapped-in pages, and %g swapped-out pages in %g runs",
[integer[outloadDataPages]],
[integer[vmImageHeader.pages]],
[integer[vmImageHeader.runs]]
];
remainingFile ← vmImageHeader.pages+
(vmImageHeader.runs +vmImageRunsPerPage-1) / vmImageRunsPerPage;
IF File.Info[outLdFile].size < vmImagePos+remainingFile THEN ERROR ShortFile[];
remainingRuns ← vmImageHeader.runs;
Beyond here, we should not access vmImageHeader (it's overlaid on the buffer!)
out.PutF["\nCopying VM from checkpoint file (each dot = %g pages transferred) ",
[integer[vmImageBufferPages]]];
ReadBuffer[];
WHILE remainingRuns > 0 DO
runsThisTime: CARDINAL = MIN[remainingRuns, vmImageRunsPerPage];
remainingRuns ← remainingRuns - runsThisTime;
ReadImageRunTable[];
FOR r: CARDINAL IN [0 .. runsThisTime) DO
total: CARDINAL = vmImageRunTable[r].pages;
reqd: CARDINAL ← total;
WHILE reqd > 0 DO
amount: CARDINAL = MIN[vmImageBufferPages-pages, reqd];
label: Disk.Label ← vmImageRunTable[r].label;
label.filePage ← label.filePage + (total-reqd);
TransferBackingRun[
data: vmImageBufferAddr + pages * PrincOps.wordsPerPage,
label: @label,
which: write,
count: amount,
addr: [
deviceType: vmImageRunTable[r].deviceType,
deviceOrdinal: vmImageRunTable[r].deviceOrdinal,
diskPage: vmImageRunTable[r].diskPage + (total-reqd),
offset: 0,
labelCheck: 0 ]
];
pages ← pages + amount;
IF pages = vmImageBufferPages THEN ReadBuffer[];
reqd ← reqd - amount;
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
out.PutRope[" ok\nInloading real memory from checkpoint file ... "];
IF Booting.switches[l] THEN {
Pause so user can read the screen
out.PutRope["\nPausing (\"L\" switch). Type any character to continue: "];
[] ← in.GetChar[];
};
[] ← Booting.Boot[boot: [file[outLdFile, outLdFirstPage]], switches: ALL[FALSE] ];
ERROR
};
IF Booting.switches[v] THEN { which ← checkpoint; DoIt[] };
IF Booting.switches[r] THEN { which ← rollback; DoIt[] };
[] ← File.FindVM[]; -- in this case we're continuing with the full boot sequence
}.