DoIt:
PROC =
TRUSTED
BEGIN
ENABLE
BEGIN
File.Error =>
Complain[
SELECT why
FROM
wentOffline => "The system volume has gone offline",
nonCedarVolume => "The system volume is not a cedar volume",
inconsistent => "The 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 => "I don't understand the checkpoint file (unknownPage)",
volumeFull => "The system volume is full - can't create checkpoint",
fragmented => "The system volume is too fragmented - can't create checkpoint",
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"];
END;
outloadDataPages: PrincOps.PageCount;
countVM: INT ← 0;
mapPages: CARDINAL;
mapBufferSpace: VM.Interval = VM.Allocate[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 = VM.Allocate[vmImageBufferPages];
vmImageBufferAddr: LONG POINTER = VM.AddressForPageNumber[vmImageBufferSpace.page];
vmImageHeader: LONG POINTER TO VMImageHeader = vmImageBufferAddr;
vmImageRunTable: REF VMImageRunTable ← NEW[VMImageRunTable];
GetMapStart:
PROC
RETURNS[File.PageNumber] =
TRUSTED
BEGIN
fp: File.FP;
header: LONG POINTER TO BootFileChanges.Header = LOOPHOLE[mapBuffer];
[fp: fp, page: outLdFirstPage] ← File.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+BootFileChanges.MemorySizeToFileSize[outloadDataPages]]]
END;
[in: in, out: out] ← SimpleTerminal.TurnOn[];
VM.Pin[vmImageBufferSpace];
out.PutRope[IF which = checkpoint THEN "\nCheckpoint." ELSE "\nRollback."];
BEGIN
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;
END;
mapPages ← (countVM + mapEntriesPerPage - 1) / mapEntriesPerPage;
mapStart ← GetMapStart[];
vmImagePos ← [mapStart+mapPages];
IF which = checkpoint
THEN
BEGIN
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: BOOL ← TRUE;
InterestingPage:
PROC[e: [0..mapEntriesPerPage)]
RETURNS[yes:
BOOL] =
TRUSTED
BEGIN
mapEntry: BootFileChanges.PageValue = mapBuffer[e];
IF mapEntry.state.flags = PrincOps.flagsVacant
THEN yes ← VMBackingChanges.StateFromPageValue[mapEntry] = active
ELSE
BEGIN
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;
};
END;
END;
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
BEGIN
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
BEGIN
-- end of current run
work[];
runSize ← 0;
END;
IF interesting
THEN
BEGIN
IF runSize = 0
THEN
BEGIN
-- start of a new run
IF backingRun = NIL
OR pageNumber
NOT
IN
[backingRun.page..backingRun.page+backingRun.count)
THEN
BEGIN
-- 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;
END;
runStart ← pageNumber;
END;
runSize ← runSize+1;
END;
pageNumber ← pageNumber+1;
ENDLOOP;
IF firstEnumerate THEN PutMapPage[p];
ENDLOOP;
IF runSize > 0 THEN work[];
firstEnumerate ← FALSE;
END;
OpenOutLdFile[];
table ← ReadBackingMap[];
vmImageHeader^ ← [
runs: 0,
pages: 0,
bootVersion: SystemVersion.release,
bootFileDate: SystemVersion.bootFileDate,
checkpointDate: BasicTime.ToPupTime[BasicTime.Now[]]
];
BEGIN
-- First pass: count runs to determine file size needed
fileSize: File.PageCount; -- required checkpoint file size
CountRuns:
PROC =
BEGIN
vmImageHeader.runs ← vmImageHeader.runs+1;
vmImageHeader.pages ← vmImageHeader.pages + runSize;
END;
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];
END; -- first pass
Write the header to the checkpoint file
File.Write[outLdFile, vmImagePos, 1, vmImageHeader];
vmImagePos ← [vmImagePos+1];
BEGIN
-- 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
BEGIN
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[];
END;
CopyToBuffer:
PROC =
-- Read the runs into the buffer
BEGIN
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;
END;
WriteBuffer:
PROC =
-- Write from buffer into checkpoint file
BEGIN
File.Write[outLdFile, vmImagePos, pages, vmImageBufferAddr];
firstPassCount ← firstPassCount - pages;
vmImagePos ← [vmImagePos+pages];
out.PutChar['.];
pages ← 0;
END;
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[];
END; -- second pass
END
ELSE
BEGIN
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 =
BEGIN
amount: File.PageCount = MIN[vmImageBufferPages, remainingFile];
File.Read[outLdFile, vmImagePos, amount, vmImageBufferAddr];
vmImagePos ← [vmImagePos+amount];
remainingFile ← remainingFile - amount;
pages ← 0;
out.PutChar['.];
END;
ReadImageRunTable:
PROC =
BEGIN
vmImageRunTable^ ←
LOOPHOLE[vmImageBufferAddr + pages * PrincOps.wordsPerPage,
LONG POINTER TO VMImageRunTable]^;
pages ← pages + 1;
IF pages = vmImageBufferPages THEN ReadBuffer[];
END;
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;
END;
out.PutRope[" ok\nInloading real memory from checkpoint file ... "];
IF Booting.switches[l]
THEN
BEGIN
-- Pause so user can read the screen
out.PutRope["\nPausing (\"L\" switch). Type any character to continue: "];
[] ← in.GetChar[];
END;
[] ← Booting.Boot[boot: [file[outLdFile, outLdFirstPage]], switches: ALL[FALSE] ];
ERROR
END;