<> <> <> <> <> <> 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], ProcessBackdoor USING [ParentChildCallback, SingleCallback], PrincOpsUtils USING [Alloc, Broadcast, ConditionPointer, DisableInterrupts, EnableAndRequeue, EnableInterrupts, Enter, Exit, FrameSize, Free, GetReturnFrame, LongCopy, 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, ProcessBackdoor = { OPEN PrincOps; ConditionPointer: TYPE = PrincOpsUtils.ConditionPointer; <> <<>> 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; ParentChildCallback: TYPE = ProcessBackdoor.ParentChildCallback; SingleCallback: TYPE = ProcessBackdoor.SingleCallback; <<>> <> 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; <> <<>> <> ForkProc: ParentChildCallback _ NIL; JoinProc: ParentChildCallback _ NIL; DetachProc: SingleCallback _ NIL; EndProc: SingleCallback _ NIL ; <<>> <> <<>> <> 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 PROC [process: PROCESS] = { h: PsbHandle; px: PsbIndex = LOOPHOLE[process]; IF (h _ ValidProcess[px]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; IF DetachProc # NIL THEN DetachProc[LOOPHOLE[process]]; PDA[h].flags.processState.detached _ TRUE; BROADCAST frameTaken; -- wake child if waiting to JOIN. }; <> <<>> Abort: PUBLIC ENTRY PROC [process: PROCESS] = { h: PsbHandle; px: PsbIndex = LOOPHOLE[process]; IF (h _ ValidProcess[px]) = 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 PROC = 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^; }; <> CountFreeProcesses: PUBLIC ENTRY SAFE PROC RETURNS [freeProcesses: CARDINAL _ 0] = TRUSTED { pbsIndex: PrincOps.PsbIndex; firstPBSIndex: PrincOps.PsbIndex ; pbsIndex _ LOOPHOLE[rebirth, ConditionVariable].condition.tail; firstPBSIndex _ pbsIndex; IF pbsIndex # PrincOps.PsbNull THEN DO freeProcesses _ freeProcesses + 1; pbsIndex _ PrincOps.PDA.block[pbsIndex].link.next; IF pbsIndex = firstPBSIndex OR pbsIndex = PrincOps.PsbNull THEN EXIT; ENDLOOP; }; RegisterEventProcs: PUBLIC SAFE PROC [forkProc: ParentChildCallback, joinProc: ParentChildCallback, endProc: SingleCallback, detachProc: SingleCallback] RETURNS [oldForkProc: ParentChildCallback, oldJoinProc: ParentChildCallback, oldEndProc: SingleCallback, oldDetachProc: SingleCallback] = CHECKED { oldForkProc _ ForkProc; ForkProc _ forkProc; oldJoinProc _ JoinProc; JoinProc _ joinProc; oldEndProc _ EndProc ; EndProc _ endProc ; oldDetachProc _ DetachProc; DetachProc _ detachProc; }; <> ValidateProcess: PUBLIC ENTRY PROC [p: PROCESS] = { IF ValidProcess[LOOPHOLE[p]] = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[p]; }; InvalidProcess: PUBLIC ERROR [process: PROCESS] = CODE; <> AllocateNakedCondition: PUBLIC ENTRY PROC RETURNS [cv: ConditionPointer, mask: WORD] = { 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 }; DeallocateNakedCondition: PUBLIC ENTRY PROC [cv: ConditionPointer] = { FOR level: NakedNotifyLevel IN NakedNotifyLevel DO IF busyLevels[level] AND cv = LOOPHOLE[@PDA.interrupt[level], ConditionPointer] 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] = { <> LongEnter: PROC [a,b: --LONG POINTER TO MONITORLOCK--WORD] RETURNS [BOOL] = LOOPHOLE[PrincOpsUtils.Enter]; abortee: FrameHandle; state: RECORD [filler: WORD, 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; <> <> }; ValidProcess: INTERNAL PROC [p: PsbIndex] RETURNS [h: PsbHandle] = INLINE { <> IF p < StartPsb OR p > LOOPHOLE[PDA.count+StartPsb, PsbIndex] THEN RETURN [NullPsbHandle]; h _ PrincOpsUtils.PsbIndexToHandle[p]; IF ProcState[h].state IN [frameTaken..dead] THEN RETURN [NullPsbHandle]; }; <<>> <> 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.LongCopy[ 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 => { IF ForkProc # NIL THEN ForkProc[PrincOpsUtils.PsbHandleToIndex[PrincOpsUtils.ReadPSB[]], childPsb]; PrincOpsUtils.Exit[@processLock]; RETURN[childPsb]}; -- return child handle to FORKing parent. child => { <> argsForChild.stk[argsForChild.stkptr] _ LOOPHOLE[root]; argsForChild.dest _ root; <> argsForChild.stk[argsForChild.stkptr+1] _ LOOPHOLE[End, WORD]; argsForChild.source _ LOOPHOLE[End, ControlLink]; IF ForkProc # NIL THEN { -- insure ForkProc was called in parent before child returns UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; PrincOpsUtils.Exit[@processLock]; }; RETURN WITH argsForChild; -- "call" root procedure of child. }; ENDCASE; }; End: PROC = { <> sv: RECORD [filler: WORD, results: StateVector]; frame: DyingFrameHandle; h: PsbHandle; sv.results _ STATE; -- save stack containing returned results. h _ PrincOpsUtils.ReadPSB[]; IF EndProc # NIL THEN EndProc[Psb: PrincOpsUtils.PsbHandleToIndex[h]]; UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; frame _ LOOPHOLE[PrincOpsUtils.MyLocalFrame[]]; frame.state _ alive; 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: PROCESS] RETURNS [loadResults: FrameHandle] = { h: PsbHandle; px: PsbIndex = LOOPHOLE[process]; frame: DyingFrameHandle; self: FrameHandle = PrincOpsUtils.MyLocalFrame[]; IF (h _ ValidProcess[px]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; IF JoinProc # NIL THEN JoinProc[PrincOpsUtils.PsbHandleToIndex[PrincOpsUtils.ReadPSB[]], px]; <> 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. }; <<>> <