-- MesaRuntime>Processes.mesa (August 26, 1982 11:22 am by Levin) DIRECTORY CPSwapDefs USING [ProcessState], Environment USING [Long, PageCount, PageNumber, wordsPerPage], Frame USING [Alloc, Free, GetReturnFrame, MyLocalFrame], Inline USING [BITAND, BITNOT, BITOR, BITSHIFT, COPY], PrincOps USING [ControlLink, Frame, FrameHandle, FrameSizeIndex, NullFrame, NullLink, StateVector], Process USING [Milliseconds, Priority, Ticks], ProcessInternal USING [DisableInterrupts, EnableInterrupts], ProcessOperations USING [ Broadcast, EnableAndRequeue, Enter, Exit, HandleToIndex, IndexToHandle, Notify, ReadPSB, ReEnter, Requeue, Wait, WritePSB, WriteWDC], ProcessPriorities USING [ priorityClient, priorityClientHigh, priorityClientLow, priorityRealTime], ProcessorFace USING [millisecondsPerTick, reservedNakedNotifyMask], PSB USING [ Condition, ConditionVariable, FaultIndex, InterruptItem, Monitor, NoTimeout, NullPsbHandle, NullStateVectorHandle, PDA, PDABase, Priority, ProcessDataArea, ProcessStateBlock, PsbHandle, PsbIndex, PsbNull, Queue, StartPsb, StateVectorHandle, Ticks, UnlockedEmpty], RuntimeInternal USING [FrameSize], RuntimePrograms USING [], SDDefs USING [SD, sFork, sJoin, sProcessTrap], StartList USING [Base], StoragePrograms USING [tableBase]; Processes: MONITOR LOCKS processLock IMPORTS Frame, Inline, ProcessInternal, ProcessorFace, ProcessOperations, RuntimeInternal, StoragePrograms EXPORTS Process, ProcessInternal, RuntimePrograms = BEGIN OPEN PSB, ProcessOperations; priorityBackground: PUBLIC Process.Priority ← ProcessPriorities.priorityClientLow; priorityForeground: PUBLIC Process.Priority ← ProcessPriorities.priorityClientHigh; priorityInterrupt: PUBLIC Process.Priority ← ProcessPriorities.priorityRealTime; priorityNormal: PUBLIC Process.Priority ← ProcessPriorities.priorityClient; ConditionVariable: TYPE = PSB.ConditionVariable; FrameSizeIndex: TYPE = PrincOps.FrameSizeIndex; --PrincOps.--FsiFrame: TYPE = MACHINE DEPENDENT RECORD [ fsi (0): FrameSizeIndex, -- must be at 3 MOD 4 boundary. frame (1): local PrincOps.Frame]; assert1: [FIRST[Process.Priority]..FIRST[Process.Priority]] = FIRST[PSB.Priority]; assert2: [LAST[Process.Priority]..LAST[Process.Priority]] = LAST[PSB.Priority]; assert3: [FIRST[Process.Ticks]..FIRST[Process.Ticks]] = FIRST[PSB.Ticks]; -- assert4: [LAST[Process.Ticks]..LAST[Process.Ticks]] = LAST[PSB.Ticks]; ++ waiting on AR6834. assert5: [PSB.NoTimeout..PSB.NoTimeout] = 0; -- (see next statement.) collisionTimeout: PSB.Ticks = PSB.NoTimeout+1; -- value to use if client -- wants timeout numerically equal to NoTimeout. ProcessState: TYPE = CPSwapDefs.ProcessState; ProcState: -- for access to ProcessState field. PROCEDURE [psbh: PsbHandle] RETURNS [ProcessState] = INLINE { RETURN[LOOPHOLE[pda[psbh].flags.available, ProcessState]] }; NakedNotifyLevel: TYPE = [0..16); DyingFrameHandle: TYPE = POINTER TO dying PrincOps.Frame; InvalidProcess: PUBLIC ERROR [process: PsbIndex] = CODE; TooManyProcesses: PUBLIC ERROR = CODE; -- Queues of processes which are.. rebirth: CONDITION; -- ..unused. frameReady: CONDITION; -- ..parents waiting to rejoin their child. frameTaken: CONDITION; -- ..children waiting to rejoin their parent. dead: CONDITION; -- ..parents waiting for their child to clean up. pda: PSB.PDABase = PSB.PDA; processLock: MONITORLOCK; busyLevels: WORD; -- bit-mask of busy naked-notify levels -- (numbered right-to-left) deadFrame: DyingFrameHandle ← NIL; -- the top-level frame of a -- detached process which needs to be freed. Bug: PRIVATE ERROR [type: BugType] = CODE; BugType: TYPE = {insufficientMappedPDA, noMoreNakedNotifyLevels, noSuchCondition}; --~~~~~~~~~~~~ Initialization ~~~~~~~~~~~~ --RuntimePrograms.--InitializeProcesses: PUBLIC PROCEDURE [pagePDA: Environment.PageNumber, countPDA: Environment.PageCount] = -- (pagePDA is not used.) BEGIN AlignUp: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] = INLINE { RETURN[ ((unaligned + modulus-1) / modulus) * modulus] }; AlignDown: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] = INLINE { RETURN[unaligned - unaligned MOD modulus] }; rAlloc: PDABase RELATIVE POINTER TO UNSPECIFIED; -- allocation pointer. DisableTimeout[@dead]; DisableTimeout[@frameReady]; DisableTimeout[@frameTaken]; DisableTimeout[@rebirth]; SDDefs.SD[SDDefs.sProcessTrap] ← ProcessTrap; SDDefs.SD[SDDefs.sFork] ← Fork; SDDefs.SD[SDDefs.sJoin] ← Join; -- We will start at the end of the PDA and allocate downwards: rAlloc ← LOOPHOLE[countPDA*Environment.wordsPerPage]; -- Allocate StateVector pool: BEGIN alignmentStateVector: CARDINAL = 4; -- (a D0 requirement) sizeStateVector: CARDINAL = AlignUp[MAX[SIZE[PrincOps.StateVector], StoragePrograms.tableBase.stateVectorSize], alignmentStateVector]; rState: PSB.StateVectorHandle; rState ← AlignDown[rAlloc, alignmentStateVector]; FOR pri: PSB.Priority IN PSB.Priority DO pda.state[pri] ← PSB.NullStateVectorHandle; FOR k: CARDINAL IN [0..StoragePrograms.tableBase.stateVectorCounts[pri]) DO IF LOOPHOLE[rState, CARDINAL]<sizeStateVector THEN ERROR Bug[insufficientMappedPDA]; rState ← rState - sizeStateVector; pda[rState].stk[0] ← pda.state[pri]; -- chain onto list.. pda.state[pri] ← rState; ENDLOOP; ENDLOOP; rAlloc ← rState; END; BEGIN -- Find number of {PSB, timeout words} that will fit in space remaining: -- (considering TimeoutVector alignment and unused TimeoutVector -- positions.) sizePSBTimeout: CARDINAL -- (does not include alignment filler) = SIZE[ProcessStateBlock] + --interruptVectorWord--SIZE[Ticks]; alignmentTimeoutVector: CARDINAL = 16; -- (PrincOps requirement) rStartPsb: PDABase RELATIVE POINTER TO UNSPECIFIED = LOOPHOLE[IndexToHandle[StartPsb]]; totalPsbs: CARDINAL; pda.count ← 0; FOR nUseful: CARDINAL DECREASING IN [0..(rAlloc - rStartPsb)/sizePSBTimeout] --UNTIL nUseful Psbs will fit in available space-- DO totalPsbs ← StartPsb + nUseful; pda.timeout ← AlignDown[rAlloc-totalPsbs, alignmentTimeoutVector]; IF (LOOPHOLE[pda.timeout, CARDINAL]+StartPsb -- (first used word -- of timeout vector) - LOOPHOLE[rStartPsb, CARDINAL]) / SIZE[ProcessStateBlock] >= nUseful THEN {pda.count ← nUseful; EXIT}; -- that many Psbs fit in avail space. REPEAT FINISHED => ERROR Bug[insufficientMappedPDA]; ENDLOOP; IF pda.count <= 1 THEN ERROR Bug[insufficientMappedPDA]; END; -- Initialize TimeoutVector: -- (Note: TimeoutVector unused portions overlay Psbs) FOR psb: PsbIndex IN [StartPsb..StartPsb+pda.count) DO pda[pda.timeout][psb] ← NoTimeout; ENDLOOP; -- Initialize first PSB to describe self - detached: pda.block[StartPsb] ← ProcessStateBlock[ link: [failed: FALSE, priority: FIRST[Priority], next: StartPsb, vector: NULL], flags: [available: LOOPHOLE[ProcessState[state: alive, detached: TRUE]], cleanup: PsbNull, waiting: FALSE, abort: FALSE], context: NULL, -- TEMP until microcode uses timeout vector: -- mds: Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]] ]; mds: NoTimeout ]; WritePSB[IndexToHandle[StartPsb]]; -- tell the processor. -- TEMP until microcode takes current Psb off readyList: -- pda.ready ← Queue[tail: PsbNull]; pda.ready ← Queue[tail: StartPsb]; -- Finish making self be detached: -- Note that someone must have previously set the return link of the topmost frame to PrincOps.NullFrame. FOR root: PrincOps.FrameHandle ← Frame.MyLocalFrame[], root.returnlink.frame DO IF root.returnlink.frame = PrincOps.NullFrame THEN { root.returnlink ← LOOPHOLE[End, procedure PrincOps.ControlLink]; EXIT }; ENDLOOP; -- Put rest of PSBs into free pool: (chained off "rebirth" condition) -- The free list is set so that each successive new process created -- will have a lower PsbIndex, thus causing the most-recently-created -- processes to be listed first by CoPilot. BEGIN firstFree: PsbIndex ← StartPsb+1; lastFree: PsbIndex ← StartPsb+pda.count-1; LOOPHOLE[rebirth, ConditionVariable].condition.tail ← PsbNull; FOR psb: PsbIndex IN [firstFree..lastFree] DO pda.block[psb].link.next ← IF psb=firstFree THEN lastFree ELSE psb-1; -- Tell CoPilot that process is not in use: LOOPHOLE[pda.block[psb].flags.available, ProcessState].state ← dead; -- TEMP until microcode uses timeout vector: pda.block[psb].mds ← NoTimeout; REPEAT FINISHED => LOOPHOLE[rebirth, ConditionVariable].condition.tail ← firstFree; ENDLOOP; END; -- Initialize FaultVector: FOR flt: FaultIndex IN FaultIndex DO pda.fault[flt] ← [queue: [tail: PsbNull], condition: [tail: PsbNull, abortable: FALSE, wakeup: FALSE] ]; ENDLOOP; -- Initialize naked-notify allocator: busyLevels ← ProcessorFace.reservedNakedNotifyMask; ProcessOperations.WriteWDC[0]; -- start interrupts. END; --~~~~~~~~~~ EXTERNAL Procedures ~~~~~~~~~~ DisableAborts: PUBLIC --EXTERNAL-- PROCEDURE [pCondition: LONG POINTER TO CONDITION] = { LOOPHOLE[pCondition↑, ConditionVariable] .condition.abortable ← FALSE }; DisableTimeout: PUBLIC --EXTERNAL-- PROCEDURE [pCondition: LONG POINTER TO CONDITION] = { pCondition.timeout ← NoTimeout }; EnableAborts: PUBLIC --EXTERNAL-- PROCEDURE [pCondition: LONG POINTER TO CONDITION] = { LOOPHOLE[pCondition↑, ConditionVariable] .condition.abortable ← TRUE}; GetCurrent: PUBLIC --EXTERNAL-- PROCEDURE RETURNS [psbHandle: PROCESS] = { RETURN[ LOOPHOLE[HandleToIndex[ReadPSB[]], PROCESS] ] }; GetPriority: PUBLIC --EXTERNAL since atomic action-- SAFE PROCEDURE [] RETURNS [priority: Process.Priority] = TRUSTED { RETURN[pda[ReadPSB[]].link.priority]}; InitializeCondition: PUBLIC --EXTERNAL-- PROCEDURE [condition: LONG POINTER TO CONDITION, ticks: Process.Ticks] = { LOOPHOLE[condition↑, ConditionVariable] ← [ condition: [tail: PsbNull, abortable: FALSE, wakeup: FALSE], timeout: IF ticks=NoTimeout THEN collisionTimeout ELSE ticks ] }; InitializeMonitor: PUBLIC --EXTERNAL-- PROCEDURE [pMonitor: LONG POINTER TO MONITORLOCK] = { LOOPHOLE[pMonitor↑, PSB.Monitor] ← UnlockedEmpty }; MsecToTicks: PUBLIC --EXTERNAL-- SAFE PROCEDURE [ms: Process.Milliseconds] RETURNS [Process.Ticks] = TRUSTED { RETURN[ ( IF ms >= LAST[Process.Milliseconds]- (ProcessorFace.millisecondsPerTick-1) THEN LAST[Process.Milliseconds] -- (avoid overflow) ELSE ms + ProcessorFace.millisecondsPerTick-1 ) / ProcessorFace.millisecondsPerTick ] }; ProcessTrap: --EXTERNAL-- PROCEDURE RETURNS [BOOLEAN] = -- (called when an aborted process attempts to reenter its monitor.) BEGIN Enter: PROCEDURE [a: POINTER TO MONITORLOCK] RETURNS [BOOLEAN] = LOOPHOLE[ProcessOperations.Enter]; LongEnter: PROCEDURE [a,b: --LONG POINTER TO MONITORLOCK--UNSPECIFIED] RETURNS [BOOLEAN] = LOOPHOLE[ProcessOperations.Enter]; abortee: PrincOps.FrameHandle; state: RECORD [filler: UNSPECIFIED, v: PrincOps.StateVector]; state.v ← STATE; -- Acquire abortee's monitor lock: UNTIL (IF state.v.stkptr = 4 THEN LongEnter[state.v.stk[0], state.v.stk[1]] ELSE Enter[LOOPHOLE[state.v.stk[0], POINTER TO MONITORLOCK]] ) DO ENDLOOP; abortee ← Frame.GetReturnFrame[]; abortee.pc ← [abortee.pc + 1]; -- step past ME(L) instruction. pda[ReadPSB[]].flags.abort ← FALSE; ERROR ABORTED; -- if ABORTED is made resumable, we should return [monitorEntered: TRUE] -- as the result of the MonitorEnter instruction that got us here. END; SetPriority: PUBLIC --EXTERNAL-- PROCEDURE [p: Process.Priority] = BEGIN h: PsbHandle; h ← ReadPSB[]; pda[h].link.priority ← p; ProcessInternal.DisableInterrupts[]; -- (to cancel subsequent enable) -- move to appropriate spot in ready queue: EnableAndRequeue[@pda.ready, @pda.ready, h]; END; SecondsToTicks: PUBLIC --EXTERNAL-- SAFE PROCEDURE [sec: CARDINAL] RETURNS [Process.Ticks] = TRUSTED BEGIN ticks: Environment.Long = [lc[ (LONG[sec]*LONG[1000] + ProcessorFace.millisecondsPerTick-1) / ProcessorFace.millisecondsPerTick]]; RETURN[IF ticks.highbits ~= 0 THEN LAST[Ticks] ELSE ticks.lowbits] END; SetTimeout: PUBLIC --EXTERNAL-- PROCEDURE [condition: LONG POINTER TO CONDITION, ticks: Process.Ticks] = {condition.timeout ← IF ticks=NoTimeout THEN collisionTimeout ELSE ticks}; TicksToMsec: PUBLIC --EXTERNAL-- SAFE PROCEDURE [ticks: Ticks] RETURNS [Process.Milliseconds] = TRUSTED {RETURN[ IF ticks > LAST[Process.Milliseconds]/ProcessorFace.millisecondsPerTick THEN LAST[Process.Milliseconds] ELSE ticks*ProcessorFace.millisecondsPerTick ] }; Yield: PUBLIC --EXTERNAL-- SAFE PROCEDURE = TRUSTED { ProcessInternal.DisableInterrupts[]; -- (to cancel subsequent enable) EnableAndRequeue[@pda.ready, @pda.ready, ReadPSB[]] }; --~~~~~~~~~~~~ ENTRY Procedures ~~~~~~~~~~~~ Abort: PUBLIC ENTRY PROCEDURE [process: PsbIndex] = BEGIN h: PsbHandle; IF (h ← ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; ProcessInternal.DisableInterrupts[]; -- (also stops -- ProcessTimeoutCounter from ticking.) IF ProcState[h].state = alive THEN { pda[h].flags.abort ← TRUE; IF pda[h].flags.waiting THEN -- Wake the victim up..-- { pda[h].flags.waiting ← FALSE; pda[pda.timeout][HandleToIndex[h]] ← NoTimeout; -- TEMP until microcode uses timeout vector: pda[h].--timeout--mds ← NoTimeout; Requeue[NIL, @pda.ready, h] }}; ProcessInternal.EnableInterrupts[]; END; --ProcessInternal.--AllocateNakedCondition: PUBLIC ENTRY PROC [] RETURNS [cv: LONG POINTER TO CONDITION, mask: WORD] = BEGIN level: NakedNotifyLevel; FOR level IN NakedNotifyLevel DO mask ← Inline.BITSHIFT[1, LAST[NakedNotifyLevel] - level]; IF Inline.BITAND[mask, busyLevels] = 0 THEN BEGIN busyLevels ← Inline.BITOR[busyLevels, mask]; cv ← LOOPHOLE[@pda.interrupt[level]]; RETURN; END; ENDLOOP; ERROR Bug[noMoreNakedNotifyLevels]; END; --ProcessInternal.--DeallocateNakedCondition: PUBLIC PROC [cv: LONG POINTER TO CONDITION] = BEGIN level: NakedNotifyLevel; FOR level IN NakedNotifyLevel DO mask: WORD ← Inline.BITSHIFT[1, LAST[NakedNotifyLevel] - level]; IF cv = LOOPHOLE[@pda.interrupt[level], LONG POINTER TO CONDITION] THEN BEGIN busyLevels ← Inline.BITAND[busyLevels, Inline.BITNOT[mask]]; pda.interrupt[level].condition.tail ← PsbNull; RETURN; END; ENDLOOP; ERROR Bug[noSuchCondition]; END; Detach: PUBLIC ENTRY PROCEDURE [process: PROCESS] = BEGIN h: PsbHandle; IF (h ← ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[LOOPHOLE[process, UNSPECIFIED]]; LOOPHOLE[pda[h].flags.available, ProcessState].detached ← TRUE; BROADCAST frameTaken; -- wake child if waiting to JOIN. END; End: --"ENTRY"-- PROCEDURE = -- When the top context of a process "returns", it Xfers to -- this procedure with its results on the stack. BEGIN sv: RECORD [filler: UNSPECIFIED, results: PrincOps.StateVector]; frame: DyingFrameHandle; h: PsbHandle; sv.results ← STATE; -- save stack containing returned results. WHILE ~Enter[@processLock] DO NULL ENDLOOP; frame ← LOOPHOLE[Frame.MyLocalFrame[]]; frame.state ← alive; h ← ReadPSB[]; LOOPHOLE[pda[h].flags.available, ProcessState].state ← frameReady; pda[h].flags.abort ← FALSE; -- too late for Aborts: they no-op Broadcast[@frameReady]; -- wake any parent process waiting to Join. -- Wait till this process is Detached or Joined: UNTIL ProcState[h].state = frameTaken OR ProcState[h].detached DO Wait[@processLock, @frameTaken, LOOPHOLE[frameTaken, ConditionVariable].timeout]; WHILE ~ReEnter[@processLock, @frameTaken] DO NULL ENDLOOP; ENDLOOP; -- Free any frame left over from a previous dead detached process: IF deadFrame ~= NIL THEN {Frame.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. LOOPHOLE[pda[h].flags.available, ProcessState].state ← dead; Broadcast[@dead]; -- tell parent our frame has been left for freeing. Wait[@processLock, @rebirth, LOOPHOLE[rebirth, ConditionVariable].timeout]; -- This process is dead. Its PSB sits in the rebirth queue until -- it is recycled into a new process by Fork. -- Our current frame however, has one of two fates: -- (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! WHILE ~ReEnter[@processLock, @rebirth] DO NULL ENDLOOP; sv.results.dest ← LOOPHOLE[frame.returnlink, PrincOps.ControlLink]; -- (frame.returnlink was set by Join[] to point to the context of JOIN.) sv.results.source ← PrincOps.NullLink; Exit[@processLock]; -- Reload returned results into stack, return to parent: RETURN WITH sv.results; END; Fork: --"ENTRY"-- PROCEDURE [--argsForRoot,-- root: PrincOps.ControlLink] RETURNS [childPsb: PsbIndex] = BEGIN PForkFrame: TYPE = POINTER TO FRAME[Fork]; ChildBuilder: PROC [--parent's locals--] RETURNS [--MUST BE NULL!--] = BEGIN pChild: LONG POINTER TO ProcessStateBlock ← @pda.block[childPsb]; parentFrame: PForkFrame = LOOPHOLE[Frame.GetReturnFrame[]]; fsi: FrameSizeIndex = LOOPHOLE[ (parentFrame-1), POINTER TO FsiFrame].fsi; childFrame: PForkFrame = Frame.Alloc[fsi]; Inline.COPY[from: parentFrame-1, to: childFrame-1, nwords: RuntimeInternal.FrameSize[fsi] + SIZE[FrameSizeIndex] ]; childFrame.identity ← child; pChild.link.failed ← FALSE; pChild.link.priority ← pda[ReadPSB[]].link.priority; pChild.flags ← [available: LOOPHOLE[ProcessState[state: alive, detached: FALSE]], cleanup: PsbNull, waiting: FALSE, abort: FALSE]; pChild.context ← [frame[LOOPHOLE[childFrame, PrincOps.FrameHandle]]]; -- TEMP until microcode uses timeout vector: -- pChild.mds ← Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]]; pChild.mds ← NoTimeout; pda[pda.timeout][childPsb] ← NoTimeout; Notify[@rebirth]; -- starts the new process executing. Its PC -- is set to begin execution at the instruction after the call -- to ChildBuilder. Its stack is empty. Therefore, -- ChildBuilder MUST NOT RETURN ANY RESULTS! END; identity: {parent, child}; argsForChild: PrincOps.StateVector; -- ("root" is automatically popped off the stack first.) argsForChild ← STATE; -- must be first! identity ← parent; WHILE ~Enter[@processLock] DO NULL ENDLOOP; IF LOOPHOLE[rebirth, ConditionVariable].condition.tail = PsbNull THEN { Exit[@processLock]; ERROR TooManyProcesses }; childPsb ← pda.block[LOOPHOLE[rebirth, ConditionVariable].condition.tail] .link.next; -- walk to tail, then to head. [] ← ChildBuilder[--my local vars--]; -- Both parent and child processes will execute the following code: SELECT identity FROM parent => { Exit[@processLock]; RETURN[childPsb] }; -- return child handle to FORKing parent. child => BEGIN -- To simulate a procedure call, we must also store dest and -- source links *above* the stack since ReturnWithState doesn't. -- (stack pointer is *not* incremented.) argsForChild.stk[argsForChild.stkptr] ← root; argsForChild.dest ← root; -- Set child's top context to call End when it returns: argsForChild.stk[argsForChild.stkptr+1] ← End; argsForChild.source ← LOOPHOLE[End, PrincOps.ControlLink]; RETURN WITH argsForChild; -- "call" root procedure of child. END; ENDCASE; END; Join: ENTRY PROCEDURE [process: PsbIndex] RETURNS [loadResults: PrincOps.FrameHandle] = BEGIN h: PsbHandle; frame: DyingFrameHandle; self: PrincOps.FrameHandle = Frame.MyLocalFrame[]; IF (h ← ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; -- Wait till process ready to be joined: WHILE ProcState[h].state ~= frameReady DO WAIT frameReady ENDLOOP; -- Guaranteed to be a dying frame by the time we get here. frame ← LOOPHOLE[pda[h].context.frame, DyingFrameHandle]; LOOPHOLE[pda[h].flags.available, ProcessState].state ← frameTaken; BROADCAST frameTaken; -- tell child process we've got his frame. -- Wait till he has finished cleaning up: WHILE frame.state ~= dead DO WAIT dead ENDLOOP; -- At this point, we (the parent process) have acquired responsibility -- for the child's frame. IT MUST BE IN THE SAME MDS AS THE PARENT. frame.returnlink ← self.returnlink; -- We use the child frame's -- return link as a mailbox to pass to the child's frame the address -- of the JOINer's frame, which the child should return to. RETURN[frame]; -- JOINer will next Xfer to "frame", which will reload the -- results into the stack and return them to the JOINer. END; Pause: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED BEGIN ENABLE ABORTED => GO TO Aborted; c: CONDITION; SetTimeout[@c, ticks]; EnableAborts[@c]; WAIT c; EXITS Aborted => RETURN WITH ERROR ABORTED; END; ValidateProcess: PUBLIC ENTRY PROCEDURE [p: PsbIndex] = { IF ValidProcess[p] = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[p] }; --~~~~~~~~~~~~ INTERNAL Procedures ~~~~~~~~~~~~ ValidProcess: INTERNAL PROCEDURE [p: --PsbIndex--UNSPECIFIED] RETURNS [h: PsbHandle] = INLINE -- returns NullPsbHandle if invalid. { RETURN[ IF ~(LOOPHOLE[p, PsbIndex] IN [StartPsb..StartPsb+pda.count)) OR ProcState[h ← IndexToHandle[LOOPHOLE[p, PsbIndex]]].state IN [frameTaken..dead] THEN NullPsbHandle ELSE h ] }; END. LOG (For earlier log entries see Pilot 4.0 archive version.) April 29, 1980 5:53 PM Forrest Drop PSB's crossing pages on floor; move Initialize Timeout machinery to ProcessorHead. May 3, 1980 10:46 AM Forrest Mesa 6.0 Conversion. May 14, 1980 6:19 PM McJones Use OISProcessorFace.reservedNakedNotifyMask; start interrupts at end of initialization (as before). June 23, 1980 5:34 PM McJones OISProcessorFace=>ProcessorFace. August 5, 1980 5:54 PM Sandman New PSB format. September 29, 1980 10:30 AM Johnsson New ProcessTrap. January 21, 1981 9:45 AM Knutsen New PDA layout: state vector pool, timeout vector. New Fork. Fix bugs. InitializeProcesses[]. Use waiting bit in Abort. January 27, 1981 8:17 AM Knutsen Use stateVectorCount from StartList. Forking requires storing controlLinks above the stack. February 4, 1981 1:25 PM Knutsen Export priorities to Process. Enable ABORTED on Pause. PrincOps fields changed names. February 25, 1981 2:03 PM Knutsen Use stateVectorSize from StartList. Pause[0] must not wait forever. March 20, 1981 1:45 PM Fay/Sandman/Knutsen Fix AR 7493 by changing Abort procedure. March 26, 1981 5:39 PM Luniewski/McJones Abort must clear waiting flag when doing requeue. August 3, 1982 4:16 pm Levin Correct all occurrences of ~IN. August 26, 1982 11:22 am Levin Make things SAFE.