<> <> <> DIRECTORY Basics USING [BITSHIFT, LongNumber], MesaRuntimeInit USING [StateVectorCounts], PrincOps USING [ Condition, ConditionVariable, ControlLink, FaultIndex, Frame, FrameHandle, FrameSizeIndex, InterruptItem, NoTimeout, NullFrame, NullLink, NullPsbHandle, NullStateVectorHandle, PDA, PDABase, Priority, ProcessDataArea, ProcessState, ProcessStateBlock, PsbHandle, PsbIndex, PsbLink, PsbNull, Queue, SD, sFork, sJoin, sProcessTrap, StartPsb, StateVector, StateVectorHandle, Ticks], Process USING [ DisableTimeout, EnableAborts, Milliseconds, Priority, priorityFaultHandlers, SetPriority, SetTimeout, Ticks], ProcessExtras USING [], PrincOpsUtils USING [ Alloc, Broadcast, COPY, DisableInterrupts, EnableAndRequeue, EnableInterrupts, Enter, Exit, FrameSize, Free, GetReturnFrame, MyLocalFrame, Notify, PsbHandleToIndex, PsbIndexToHandle, ReadPSB, ReEnter, Requeue, Wait, WritePSB, WriteWDC]; ProcessImpl: MONITOR [ pdaWords: CARDINAL, svCounts: MesaRuntimeInit.StateVectorCounts, wordsPerSV: CARDINAL, reservedNakedNotifyMask: WORD, millisecondsPerTick: CARDINAL] LOCKS processLock IMPORTS Basics, PrincOpsUtils, Process EXPORTS MesaRuntimeInit, PrincOpsUtils, Process, ProcessExtras = BEGIN OPEN PrincOps; <> <<>> FsiFrame: TYPE = MACHINE DEPENDENT RECORD [ fsi (0): FrameSizeIndex, -- must be at 3 MOD 4 boundary. frame (1): local Frame]; NakedNotifyLevel: TYPE = [0..--VM.bitsPerWord--16); DyingFrameHandle: TYPE = POINTER TO dying Frame; <<>> <> processLock: MONITORLOCK; rebirth: CONDITION; -- queue of unused processes. frameReady: CONDITION; -- queue of parents waiting to rejoin their child. frameTaken: CONDITION; -- queue of children waiting to rejoin their parent. dead: CONDITION; -- queue of parents waiting for their child to clean up. <<>> deadFrame: DyingFrameHandle _ NIL; -- the top-level frame of a detached process which needs to be freed. busyLevels: PACKED ARRAY NakedNotifyLevel OF BOOL; <> <<>> <> <<>> <> MsecToTicks: PUBLIC SAFE PROC [ms: Process.Milliseconds] RETURNS [Process.Ticks] = TRUSTED { RETURN[ (IF ms >= Process.Milliseconds.LAST-(millisecondsPerTick-1) THEN Process.Milliseconds.LAST -- (avoid overflow) ELSE ms + millisecondsPerTick-1) / millisecondsPerTick ] }; SecondsToTicks: PUBLIC SAFE PROC [sec: CARDINAL] RETURNS [Process.Ticks] = TRUSTED { ticks: Basics.LongNumber = [lc[(sec.LONG*1000 + millisecondsPerTick-1) / millisecondsPerTick]]; RETURN[IF ticks.highbits ~= 0 THEN Ticks.LAST ELSE ticks.lowbits] }; TicksToMsec: PUBLIC SAFE PROC [ticks: Ticks] RETURNS [Process.Milliseconds] = TRUSTED { RETURN[ IF ticks > Process.Milliseconds.LAST/millisecondsPerTick THEN Process.Milliseconds.LAST ELSE ticks*millisecondsPerTick ] }; <> <<>> Detach: PUBLIC ENTRY PROCEDURE [process: PROCESS] = BEGIN h: PsbHandle; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[LOOPHOLE[process, UNSPECIFIED]]; PDA[h].flags.processState.detached _ TRUE; BROADCAST frameTaken; -- wake child if waiting to JOIN. END; <> <<>> Abort: PUBLIC ENTRY PROCEDURE [process: PsbIndex] = { h: PsbHandle; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; IF h = PrincOpsUtils.ReadPSB[] THEN { PDA[h].flags.abort _ FALSE; RETURN WITH ERROR ABORTED } ELSE { PrincOpsUtils.DisableInterrupts[]; -- (also stops ProcessTimeoutCounter from ticking.) IF ProcState[h].state = alive THEN { PDA[h].flags.abort _ TRUE; IF PDA[h].flags.waiting THEN { <> PDA[h].flags.waiting _ FALSE; PDA[PDA.timeout][PrincOpsUtils.PsbHandleToIndex[h]] _ NoTimeout; <> PDA[h].--timeout--mds _ NoTimeout; PrincOpsUtils.Requeue[NIL, @PDA.ready, h]; }; }; PrincOpsUtils.EnableInterrupts[]; }; }; CheckForAbort: PUBLIC ENTRY SAFE PROCEDURE = TRUSTED { h: PsbHandle = PrincOpsUtils.ReadPSB[]; IF PDA[h].flags.abort THEN { PDA[h].flags.abort _ FALSE; RETURN WITH ERROR ABORTED }; }; <> <<>> Pause: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED { ENABLE ABORTED => GO TO Aborted; c: CONDITION; Process.SetTimeout[@c, ticks]; Process.EnableAborts[@c]; WAIT c; EXITS Aborted => RETURN WITH ERROR ABORTED; }; SetTimeSlice: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED { IF pSchedulingInterval = NIL THEN RETURN; pSchedulingInterval^.timeout _ ticks; NOTIFY pSchedulingInterval^; }; <> ValidateProcess: PUBLIC ENTRY PROC [p: PsbIndex] = { IF ValidProcess[p] = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[p]; }; InvalidProcess: PUBLIC ERROR [process: PsbIndex] = CODE; <> AllocateNakedCondition: PUBLIC ENTRY PROC RETURNS [cv: LONG POINTER TO CONDITION, mask: WORD] = BEGIN FOR level: NakedNotifyLevel IN NakedNotifyLevel DO IF ~busyLevels[level] THEN { busyLevels[level] _ TRUE; RETURN[ cv: LOOPHOLE[@PDA.interrupt[level]], mask: Basics.BITSHIFT[1, NakedNotifyLevel.LAST - level] ]; }; ENDLOOP; ERROR END; DeallocateNakedCondition: PUBLIC ENTRY PROC [cv: LONG POINTER TO CONDITION] = { FOR level: NakedNotifyLevel IN NakedNotifyLevel DO IF busyLevels[level] AND cv = LOOPHOLE[@PDA.interrupt[level], LONG POINTER TO CONDITION] THEN { busyLevels[level] _ FALSE; PDA.interrupt[level].condition.tail _ PsbNull; RETURN; }; ENDLOOP; ERROR; }; <> <<>> ProcState: PROC [psbh: PsbHandle] RETURNS [ProcessState] = INLINE { RETURN[PDA[psbh].flags.processState] }; ProcessTrap: PROC RETURNS [BOOL] = <> BEGIN LongEnter: PROC [a,b: --LONG POINTER TO MONITORLOCK--UNSPECIFIED] RETURNS [BOOLEAN] = LOOPHOLE[PrincOpsUtils.Enter]; abortee: FrameHandle; state: RECORD [filler: UNSPECIFIED, v: StateVector]; state.v _ STATE; <> UNTIL (IF state.v.stkptr = 4 THEN LongEnter[state.v.stk[0], state.v.stk[1]] ELSE PrincOpsUtils.Enter[LOOPHOLE[state.v.stk[0], POINTER TO MONITORLOCK]]) DO ENDLOOP; abortee _ PrincOpsUtils.GetReturnFrame[]; abortee.pc _ [abortee.pc + 1]; -- step past ME(L) instruction. PDA[PrincOpsUtils.ReadPSB[]].flags.abort _ FALSE; ERROR ABORTED; <> <> END; ValidProcess: INTERNAL PROC [p: --PsbIndex--UNSPECIFIED] RETURNS [h: PsbHandle] = INLINE { <> RETURN[ IF ~(LOOPHOLE[p, PsbIndex] IN [StartPsb..StartPsb+PDA.count)) OR ProcState[h _ PrincOpsUtils.PsbIndexToHandle[LOOPHOLE[p, PsbIndex]]].state IN [frameTaken..dead] THEN NullPsbHandle ELSE h ] }; <<>> <> Fork: PROC [--argsForRoot,-- root: ControlLink] RETURNS [childPsb: PsbIndex] = { PForkFrame: TYPE = POINTER TO FRAME[Fork]; ChildBuilder: PROC [--parent's locals--] RETURNS [--MUST BE NULL!--] = { pChild: LONG POINTER TO ProcessStateBlock _ @PDA.block[childPsb]; parentFrame: PForkFrame = LOOPHOLE[PrincOpsUtils.GetReturnFrame[]]; fsi: FrameSizeIndex = LOOPHOLE[parentFrame-1, POINTER TO FsiFrame].fsi; childFrame: PForkFrame = PrincOpsUtils.Alloc[fsi]; PrincOpsUtils.COPY[ from: parentFrame, to: childFrame, nwords: PrincOpsUtils.FrameSize[fsi]]; childFrame.identity _ child; pChild.link.failed _ FALSE; pChild.link.priority _ PDA[PrincOpsUtils.ReadPSB[]].link.priority; pChild.flags _ [ processState: [state: alive, detached: FALSE], cleanup: PsbNull, waiting: FALSE, abort: FALSE ]; pChild.context _ [frame[LOOPHOLE[childFrame, FrameHandle]]]; <> <> pChild.mds _ NoTimeout; PDA[PDA.timeout][childPsb] _ NoTimeout; <> PrincOpsUtils.Notify[@rebirth]; }; identity: {parent, child}; argsForChild: StateVector; argsForChild _ STATE; -- must be first! ("root" is automatically popped off the stack first.) identity _ parent; UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; WHILE LOOPHOLE[rebirth, ConditionVariable].condition.tail = PsbNull DO PrincOpsUtils.Wait[@processLock, @dead, LOOPHOLE[dead, ConditionVariable].timeout]; UNTIL PrincOpsUtils.ReEnter[@processLock, @dead] DO NULL ENDLOOP; ENDLOOP; childPsb _ PDA.block[LOOPHOLE[rebirth, ConditionVariable].condition.tail].link.next; -- walk to tail, then to head. [] _ ChildBuilder[--my local vars--]; <> SELECT identity FROM parent => {PrincOpsUtils.Exit[@processLock]; RETURN[childPsb]}; -- return child handle to FORKing parent. child => { <> argsForChild.stk[argsForChild.stkptr] _ root; argsForChild.dest _ root; <> argsForChild.stk[argsForChild.stkptr+1] _ End; argsForChild.source _ LOOPHOLE[End, ControlLink]; RETURN WITH argsForChild; -- "call" root procedure of child. }; ENDCASE; }; End: PROC = { <> sv: RECORD [filler: UNSPECIFIED, results: StateVector]; frame: DyingFrameHandle; h: PsbHandle; sv.results _ STATE; -- save stack containing returned results. UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; frame _ LOOPHOLE[PrincOpsUtils.MyLocalFrame[]]; frame.state _ alive; h _ PrincOpsUtils.ReadPSB[]; PDA[h].flags.processState.state _ frameReady; PDA[h].flags.abort _ FALSE; -- too late for Aborts: they no-op PrincOpsUtils.Broadcast[@frameReady]; -- wake any parent process waiting to Join. <> UNTIL ProcState[h].state = frameTaken OR ProcState[h].detached DO PrincOpsUtils.Wait[@processLock, @frameTaken, LOOPHOLE[frameTaken, ConditionVariable].timeout]; WHILE ~PrincOpsUtils.ReEnter[@processLock, @frameTaken] DO ENDLOOP; ENDLOOP; <> IF deadFrame ~= NIL THEN {PrincOpsUtils.Free[deadFrame]; deadFrame _ NIL}; IF ProcState[h].detached THEN deadFrame _ frame; -- If detached, leave our frame for freeing. frame.state _ dead; -- tell Joiner that we're done. PDA[h].flags.processState.state _ dead; PrincOpsUtils.Broadcast[@dead]; -- tell parent our frame has been left for freeing. PrincOpsUtils.Wait[@processLock, @rebirth, LOOPHOLE[rebirth, ConditionVariable].timeout]; <> <<(a) If this process was detached, the frame will simply be freed by the next process that finishes ("deadFrame").>> <<(b) if this process is being Joined, the parent process will have acquired a pointer to our frame. The JOIN code will Xfer to our frame and the code below will be executed BY THE PARENT PROCESS. The parent process therefore MUST BE RUNNING IN THE SAME MDS as the child process!>> UNTIL PrincOpsUtils.ReEnter[@processLock, @rebirth] DO ENDLOOP; sv.results.dest _ LOOPHOLE[frame.returnlink, ControlLink]; -- (frame.returnlink was set by Join[] to point to the context of JOIN.) sv.results.source _ NullLink; PrincOpsUtils.Exit[@processLock]; <> RETURN WITH sv.results }; Join: ENTRY PROC [process: PsbIndex] RETURNS [loadResults: FrameHandle] = { h: PsbHandle; frame: DyingFrameHandle; self: FrameHandle = PrincOpsUtils.MyLocalFrame[]; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; <> WHILE ProcState[h].state ~= frameReady DO WAIT frameReady ENDLOOP; <> frame _ LOOPHOLE[PDA[h].context.frame, DyingFrameHandle]; PDA[h].flags.processState.state _ frameTaken; BROADCAST frameTaken; -- tell child process we've got his frame. <> WHILE frame.state ~= dead DO WAIT dead ENDLOOP; <> frame.returnlink _ self.returnlink; RETURN[frame] -- JOINer will next Xfer to "frame", which will reload the results into the stack and return them to the JOINer. }; <<>> <