-- File: AltoKD.mesa
-- Last edited by Levin: 30-Apr-81 13:19:28
DIRECTORY
AltoFile USING [FreePageFileID],
AltoFileDefs USING [KD, SN],
AltoFilePrivate USING [DoDiskRequest, loggingEnabled],
DiskIODefs USING [
CompletionStatus, DiskError, DiskRequest, eofvDA, FID, InitiateDiskIO,
ResetDiskShape, vDA, VerboseCompletionProcedure, XferSpec],
DiskKDDefs USING [
AssignDiskPage, CountFreeDiskPages, DiskFull, NewSN, ReleaseDiskPage, UpdateDiskKD],
LogDefs USING [DisplayNumber],
ProcessDefs USING [Seconds, SecondsToTicks],
VMDefs USING [CloseFile, FileHandle, OpenFile, Page, ReadPage, Release],
VMStorage USING [AllocatePage, FreePage];
AltoKD: MONITOR
IMPORTS
AltoFilePrivate, DiskIODefs, DiskKDDefs, LogDefs, ProcessDefs, VMDefs, VMStorage
EXPORTS AltoFile, AltoFilePrivate
SHARES DiskIODefs =
BEGIN OPEN DiskIODefs;
-- A note of caution:
-- It is tempting to eliminate the dependency of this code on DiskKDDefs, since most
-- of the work is being done here anyway. However, it is important that only one
-- copy of DiskDescriptor be in use, and Mesa got there first. In principle, the
-- bit table contained therein is only a hint, but it practice, everyone tends to
-- believe the free page count, which would get screwed up if two autonomous modules
-- were maintaining it. We defer to Mesa here, with one exception. Mesa should
-- update the disk descriptor header (which contains the serial number generator)
-- whenever a file is created, but it doesn't. We do so in our version of NewSN.
-- Types and Related Constants --
WakeUpReason: TYPE = {timeOut, pageAllocated, goingAway};
-- Global Variables --
freeCount: CARDINAL;
wakeUpReason: WakeUpReason;
freeSpaceWatchInterval: ProcessDefs.Seconds = 60;
timeToCheckAgain: CONDITION;
-- Procedures and Signals Exported to AltoFile --
AllocateDiskPage: PUBLIC PROCEDURE [vda: vDA] RETURNS [vDA] =
BEGIN
request: DiskRequest;
xferSpec: ARRAY [0..1) OF XferSpec;
readStatus: CompletionStatus;
labelRead: CONDITION ← [timeout: 0];
buffer: POINTER = VMStorage.AllocatePage[];
AllocationState: TYPE = {unassigned, assigned, diskfull, diskerror};
state: AllocationState ← unassigned;
-- In principle, the following procedures should have a separate monitor lock all
-- to themselves. However, that is sufficiently inconvenient that we "borrow" the
-- KD lock. It would also be nice to be able to use DoDiskRequest, but we need
-- to look at the label during the completion procedure.
ReadLabel: ENTRY PROCEDURE = INLINE
-- initiates the disk read and waits for completion.
BEGIN
DO
readStatus ← noStatus;
InitiateDiskIO[@request];
WHILE readStatus = noStatus DO WAIT labelRead; ENDLOOP;
IF readStatus ~= neverStarted THEN EXIT;
ENDLOOP;
END;
CheckFreePage: ENTRY VerboseCompletionProcedure =
-- invoked at completion of read to test that page is actually free.
BEGIN
readStatus ← status;
IF readStatus = ok AND label.fileID = AltoFile.FreePageFileID THEN
state ← assigned;
NOTIFY labelRead;
END;
WHILE state = unassigned DO
vda ← AssignPage[vda ! DiskKDDefs.DiskFull => {state ← diskfull; EXIT}];
xferSpec[0] ← [buffer, vda, 0];
request ← DiskRequest[
firstPage:, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:,
xfers: DESCRIPTOR[@xferSpec, 1], proc: [verbose[CheckFreePage]],
noRestore: FALSE, command: ReadLD[]];
ReadLabel[];
IF readStatus ~= ok THEN state ← diskerror;
ENDLOOP;
VMStorage.FreePage[buffer];
SELECT state FROM
assigned => AlertWaiters[pageAllocated];
diskfull => ERROR DiskFull;
diskerror => ERROR DiskError[readStatus];
ENDCASE;
RETURN[vda]
END;
DiskFull: PUBLIC ERROR = CODE;
FreeDiskPage: PUBLIC PROCEDURE [vda: vDA] =
BEGIN
buffer: POINTER = VMStorage.AllocatePage[];
request: DiskRequest;
xferSpec: ARRAY [0..1) OF XferSpec ← [[buffer, vda, 0]];
status: CompletionStatus;
request ← DiskRequest[
firstPage: 0, fileID: AltoFile.FreePageFileID, firstPagevDA:,
pagesToSkip: 0, nonXferID:, xfers: DESCRIPTOR[@xferSpec, 1],
proc: , noRestore: FALSE,
command: WriteLD[next: eofvDA, prev: eofvDA, lastByteCount: 0]];
[status, , ] ← AltoFilePrivate.DoDiskRequest[req: @request, signalError: FALSE];
VMStorage.FreePage[buffer];
IF status = ok THEN DeassignPage[vda]
ELSE ERROR DiskError[status];
END;
WaitForSpaceCrunch: PUBLIC ENTRY PROCEDURE [pages: CARDINAL] RETURNS [BOOLEAN] =
BEGIN
DO
IF freeCount <= pages THEN RETURN[TRUE];
wakeUpReason ← timeOut;
WAIT timeToCheckAgain;
SELECT wakeUpReason FROM
timeOut => freeCount ← DiskKDDefs.CountFreeDiskPages[];
pageAllocated => NULL;
goingAway => RETURN[FALSE];
ENDCASE;
ENDLOOP;
END;
NewSN: PUBLIC ENTRY PROCEDURE RETURNS [sn: AltoFileDefs.SN] =
BEGIN
sn ← DiskKDDefs.NewSN[];
DiskKDDefs.UpdateDiskKD[]; -- DiskKDDefs.NewSN should have done this.
END;
-- Procedures and Signals Exported to AltoFilePrivate --
InitializeKD: PUBLIC PROCEDURE =
-- initializes disk descriptor.
BEGIN
freeCount ← DiskKDDefs.CountFreeDiskPages[];
timeToCheckAgain.timeout ← ProcessDefs.SecondsToTicks[freeSpaceWatchInterval];
IF AltoFilePrivate.loggingEnabled THEN
LogDefs.DisplayNumber["Free Disk Pages"L, [short[@freeCount]]];
BEGIN OPEN VMDefs;
kdFile: FileHandle ← OpenFile[name: "DiskDescriptor"L];
page: Page = ReadPage[addr: [kdFile, 0]];
kd: POINTER TO AltoFileDefs.KD = LOOPHOLE[page];
ResetDiskShape[kd.disk];
Release[page];
CloseFile[kdFile];
END;
END;
FinalizeKD: PUBLIC PROCEDURE =
{AlertWaiters[goingAway]};
-- Internal Procedures --
AssignPage: ENTRY PROCEDURE [nearvDA: vDA] RETURNS [vDA] =
BEGIN
RETURN[DiskKDDefs.AssignDiskPage[nearvDA ! UNWIND => NULL]]
END;
DeassignPage: ENTRY PROCEDURE [vda: vDA] =
BEGIN
DiskKDDefs.ReleaseDiskPage[vda];
freeCount ← freeCount + 1;
END;
AlertWaiters: ENTRY PROCEDURE [why: WakeUpReason] =
-- wakes up any processes blocked on TimeToCheckAgain, passing the specified reason.
BEGIN
IF (wakeUpReason ← why) = pageAllocated THEN freeCount ← freeCount - 1;
BROADCAST timeToCheckAgain;
END;
END.