-- File: AltoKD.mesa -- Last edited by Levin: 12-Apr-83 11:53:38 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, pageFreed, 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]; AlertWaiters[pageFreed]; 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, pageFreed => NULL; goingAway => RETURN[FALSE]; ENDCASE; ENDLOOP; END; GetDiskSpace: PUBLIC ENTRY PROCEDURE [wait: BOOLEAN] RETURNS [pages: CARDINAL, ok: BOOLEAN] = BEGIN originalCount: CARDINAL = freeCount; DO IF freeCount ~= originalCount OR ~wait THEN RETURN[freeCount, TRUE]; wakeUpReason _ timeOut; WAIT timeToCheckAgain; SELECT wakeUpReason FROM timeOut => freeCount _ DiskKDDefs.CountFreeDiskPages[]; pageAllocated, pageFreed => NULL; goingAway => RETURN[freeCount, 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.