*----------------------------------------------------------- Title[PilotMesaProcess.mc...January 22, 1982 5:51 PM...Taft]; * Process/monitor opcodes, interrupt handler, and process scheduler. IfE[AltoMode, 0, , ER[Pilot-only.module--will.not.work.with.Alto, 1]]; *----------------------------------------------------------- % CONTENTS, by order of occurence ME Monitor Enter MRE Monitor Reenter MXD Monitor Exit and Depart MXW Monitor Exit and Wait NOTIFY Notify BCAST Broadcast REQUEUE Requeue Pop2ShortOrLong Pop 2 short or long pointers and load base registers Pop1ShortOrLong Pop 1 short or long pointer and load base register FetchCPFlags Fetch current process's flags FetchCPLink Fetch current process's link EnterFailed Put current process on ML queue ExitMonitor Unlock monitor and awaken process at head of ML queue NotifyWakeup Naked Notify of CV (awaken process or set wakeup waiting bit) WakeHead Awaken process at head of CV queue Requeue Move a process from a source queue to a destination queue CleanUpCVQueue Clean up CV queue that may have been left in a mess by Requeue IWDC Increment WDC, disable interrupts DWDC Decrement WDC, reenable interrupts as appropriate MesaFault Suspend current process and notify fault handler MesaReschedTrap Mesa Reschedule trap MesaInterrupt Notify naked CVs according to wakeups pending in NWW CheckForTimeouts Scan PSBs and awaken any that have timed out MesaReschedule Suspend current process and begin new one from Ready Queue % TopLevel; KnowRBase[RTemp0]; *----------------------------------------------------------- * Base registers. *----------------------------------------------------------- * PDA * Process data area -- permanently assigned * Following two registers must be IFU-addressable and mutually xor 1. BR[MLBR, 34]; * Monitor lock BR[CVBR, 35]; * Condition variable BR[RQBR, IP[PDA]]; * Ready queue -- know it is word 0 of the PDA *----------------------------------------------------------- * R register usage. *----------------------------------------------------------- * Permanently assigned: * CurrentPSB PsbHandle of Current PSB (accessible via RR/WR) * Note: PrincOps says this should be a PsbIndex, but * the D0 implementation says otherwise! * CurrentTime Current time, in ~50 ms ticks * TickCount Counter to divide down vertical field interrupts to make ticks * RTemp0-3 are used for general scratch purposes. * The remaining RTemps are assigned for specific purposes: RME[Process, RTemp4]; * -> PSB being worked on RME[DQBRReg, RTemp5]; * MemBase value for Requeue destination queue * B0=1 => Requeue has been done RME[SQBRReg, RTemp6]; * MemBase value for Requeue source queue * B15=1 => source queue is NIL RME[IndexMask, XferFlags]; * Constant mask for extracting Q.tail words * BRConst[XX] generates a constant to select base register XX, and with * bit 0 = 1 to set the Requeue flag in DQBRReg (if relevant). M[BRConst, Add[100000, LShift[IP[#1], 10]]C]; *----------------------------------------------------------- * Process/monitor data structures *----------------------------------------------------------- * Priority: TYPE = [0..7]; * Note: QueueHandles are either short or long pointers; the process/monitor * opcodes must distinguish between them by testing the stack depth. * QueueHandle: LONG POINTER TO Queue; * Note: a Queue has the same structure as the words containing * CV.tail, ML.tail, PSBL.next, and PSBF.cleanup. * Queue: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] ← 0, MC[Q.tail, 7774]; * tail: PsbIndex, * reserved: [0..3]; MC[shcQ.tail, 44]; * ShC for making Q.tail: LMask = 4, RMask = 2 * PsbHandle: TYPE = POINTER TO PSB; -- PDA-relative * PsbIndex: TYPE = [0..1024); * Invariant: for any given PSB, PsbHandle = 4*PsbIndex. * PSB: TYPE = MACHINE DEPENDENT RECORD [ MC[PSB.link, 0]; * Link: PsbLink, MC[PSB.flags, 1]; * flags: PsbFlags, MC[PSB.context, 2]; * context: POINTER, -- to Frame or StateVector MC[PSB.timeout, 3]; * timeout: CARDINAL ]; MC[sizePSB, 4]; * PsbLink: TYPE = MACHINE DEPENDENT RECORD [ MC[PSBL.failed, 100000]; * failed: BOOLEAN, -- monitor enter failed MC[PSBL.priority, 70000]; * priority: Priority, MC[PSBL.next, 7774]; * next: PsbIndex, * reserved: BIT ← 0, MC[PSBL.vector, 1]; * vector: BOOLEAN ]; -- context is StateVector * PsbFlags: TYPE = MACHINE DEPENDENT RECORD [ * available: [0..17B], -- software use MC[PSBF.cleanup, 7774]; * cleanup: PsbIndex, -- cleanup link MC[PSBF.waiting, 2]; * waiting: BOOLEAN, -- waiting on CV MC[PSBF.abort, 1]; * abort: BOOLEAN ]; -- abort at next wait * Condition: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] ← 0, MC[CV.tail, 7774], * tail: PsbIndex, MC[CV.abortable, 2]; * abortable: BOOLEAN, MC[CV.wakeup, 1]; * wakeup: BOOLEAN ]; -- wakeup waiting * MonitorLock: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] ← 0, MC[ML.tail, 7774]; * tail: PsbIndex, * available: BIT, -- currently unused MC[ML.locked, 1]; * locked: BOOLEAN ]; * Data structures (cont'd) * StateVector: TYPE = MACHINE DEPENDENT RECORD [ MC[SV.stack, 0]; * stack: ARRAY[0..7] OF UNSPECIFIED, MC[SV.stkp, 16]; * breakbyte, stkp: BYTE, MC[SV.frame, 17]; * frame: ControlLink, MC[SV.data, 20]; * data: ARRAY[0..1] OF UNSPECIFIED ]; * FaultIndex: TYPE = [0..7]; * Process Data Area (PDA) format, relative to PDA base register * PDA: TYPE = MACHINE DEPENDENT RECORD [ * block: ARRAY [0..0) OF PSB, -- zero length, * -- enables PDA-relative addressing of PSBs MSC[PDA.ready, 0]; * ready: Queue, MSC[PDA.count, 1]; * count: CARDINAL, -- number of PSBs in PDA * MSC[PDA.timeout, 2]; * timeout: POINTER TO TimeoutVector, -- unused * available: ARRAY [3..7] OF UNSPECIFIED, MC[PDA.state, 10]; * state: ARRAY Priority OF POINTER TO StateVector, MC[PDA.interrupt, 20]; * interrupt: ARRAY [0..15] OF RECORD [ * Condition, UNSPECIFIED], MC[PDA.fault, 60]; * fault: ARRAY FaultIndex OF RECORD [ * Queue, Condition], MC[PDA.psbs, 100]; * psbs: ARRAY [0..count) OF PSB ]; * Other stuff * MC[TimerChanMask, 100000]; * This channel is used to run the 60-Hz timer * Must be sign bit *----------------------------------------------------------- IFUR[ME, 1, MLBR]; * MonitorEnter[m]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1← MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR← m :Else; Call[Pop1ShortOrLong]; * MLBR← m :EndIf; * ml ← Fetch[m]↑; IF ~ml.locked THEN * BEGIN ml.locked ← TRUE; Store[m]↑ ← ml; Push[TRUE]; END * ELSE EnterFailed[m]; Fetch← 0S, Call[TestMonitorLock]; LockMonitorAndExit: Stack← (Store← 0S)+1, DBuf← Q, IFUNext0; *----------------------------------------------------------- IFUR[MRE, 1, CVBR]; * MonitorReEnter[m, c]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1← MD, Branch[.+1]; StkP+1, Call[Pop2ShortOrLong]; * CVBR← c, MLBR← m :Else; Call[Pop2ShortOrLong]; * CVBR← c, MLBR← m :EndIf; * ml ← Fetch[m]↑; * IF ml.locked THEN EnterFailed[m] ELSE ... Fetch← 0S, Call[TestMonitorLock]; * BEGIN CleanUpCVQueue[c]; * flags ← Fetch[@PDA.block[PSB].flags]↑; flags.cleanup ← PsbNull; * Store[@PDA.block[PSB].flags]↑ ← flags; T← A0, MemBase← CVBR, Call[CleanUpCVQueue]; T← (CurrentPSB)+1, MemBase← PDA; Fetch← T, RTemp2← NOT (IndexMask); RTemp2← (RTemp2) AND MD; Store← T, DBuf← RTemp2, Branch[MRENoAbort, R even]; * abort is bit 15 * IF flags.abort THEN * BEGIN cond ← Fetch[c]↑; IF cond.abortable THEN ProcessTrap[]; END; * ml.locked ← TRUE; Store[m]↑ ← ml; Push[TRUE]; END; * RTemp0 contains cond (left over from CleanUpCVQueue). PD← (RTemp0) AND (CV.abortable); Branch[.+2, ALU=0]; T← sProcessTrap, Branch[SavePCAndTrap]; Nop; MRENoAbort: MemBase← MLBR, Branch[LockMonitorAndExit]; *----------------------------------------------------------- TestMonitorLock: * Test and set monitor lock; shared by ME and MRE. * Enter: MD = monitor lock * Exit: if monitor is locked, goes to EnterFailed; otherwise: * RTemp1 = Q = monitor lock value, with ML.locked = TRUE. * StkP incremented (in preparation for pushing TRUE result) * Clobbers T *----------------------------------------------------------- Subroutine; T← A0, RTemp1← MD; T← RTemp1← (Store← T)+(DBuf← RTemp1)+1, * Dirty monitor, set ML.locked Branch[EnterFailed, R odd]; * Branch if already locked Q← T, StkP+1, Return; *----------------------------------------------------------- IFUR[MXD, 1, MLBR]; * MonitorExitAndDepart[m]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1← MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR← m :Else; Call[Pop1ShortOrLong]; * MLBR← m :EndIf; * IF ExitMonitor[m] THEN Reschedule[]; T← A0, Call[ExitMonitor]; RTemp1← ID, Branch[MesaReschedule1]; * IL=1 *----------------------------------------------------------- IFUR[MXW, 1, CVBR]; * MonitorExitAndWait[m, c, t]; *----------------------------------------------------------- T← Stack&-1, Branch[MXWM2]; :IfMEP; T← Stack&-1← MD, Branch[.+1]; :EndIf; MXWM2: RTemp3← T, Call[Pop2ShortOrLong]; * RTemp3← t, CVBR← c, MLBR← m * CleanUpCVQueue[c]; ExitMonitor[m]; T← A0, MemBase← CVBR, Call[CleanUpCVQueue]; T← A0, MemBase← MLBR, Call[ExitMonitor]; * flags ← Fetch[@PDA.block[PSB].flags]↑; cond ← Fetch[c]↑; * IF ~(flags.abort AND cond.abortable) THEN ... MemBase← PDA, Call[FetchCPFlags]; * Returns RTemp1=flags, T=@PSB.flags RTemp2← T, MemBase← CVBR; * Save @PSB.flags PD← Fetch← 0S, RTemp1, Branch[.+2, R even]; * Fetch[c], test flags.abort PD← (Add[CV.abortable!]S) AND MD; T← A0, RTemp0← MD, * RTemp0← cond Branch[.+2, ALU=0]; * Branch if PSB not aborting or CV not abortable RTemp1← ID, Branch[MesaReschedule1]; * Aborting, don't wait * IF cond.wakeup THEN * BEGIN cond.wakeupWaiting ← FALSE; Store[c]↑ ← cond; END ... RTemp0← (RTemp0) AND NOT (CV.wakeup), Branch[.+2, R even]; * cond.wakeup is bit 15 Store← T, DBuf← RTemp0, Branch[MesaReschedule]; * ... ELSE BEGIN * Store[@PDA.block[PSB].timeout]↑ ← IF t=0 THEN 0 ELSE MAX[1, ticks+t]; * flags.waiting ← TRUE; Store[@PDA.block[PSB].flags]↑ ← flags; * Requeue[src: @PDA.ready, dst: c, psb: PSB]; END; T← RTemp3, MemBase← PDA; * RTemp3 = time to wait (0 = infinity) T← (CurrentTime)+T, Branch[.+3, ALU=0]; PD← T-1; * Generates carry unless T=0 RTemp3← T+1, XorSavedCarry; T← (RTemp1) OR (PSBF.waiting); Store← RTemp2, DBuf← T; * Store updated flags T← (CurrentPSB)+(PSB.timeout); DQBRReg← BRConst[CVBR]; * dst = c SQBRReg← BRConst[RQBR]; * src = ready Store← T, DBuf← RTemp3, Branch[Requeue&Resched]; *----------------------------------------------------------- IFUR[NOTIFY, 1, CVBR, N[1]]; * Notify[c]; IFUR[BCAST, 1, CVBR, N[0]]; * Broadcast[c]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1← MD, Branch[.+1]; RTemp3← ID-1, StkP+1, Call[Pop1ShortOrLong]; * CVBR← c :Else; RTemp3← ID-1, Call[Pop1ShortOrLong]; * CVBR← c :EndIf; * This code is shared by NOTIFY and BCAST. * RTemp3 = 0 if NOTIFY, -1 if BCAST. * CleanUpCVQueue[c]; * WHILE (cond ← Fetch[c]).tail # PsbNull DO * WakeHead[c]; requeue ← TRUE; ENDLOOP; * IF requeue THEN Reschedule[]; * Note that we actually call CleanUpCVQueue every iteration. * This is ok since it is an idempotent operation. Nop; BroadcastLoop: T← A0, Call[CleanUpCVQueue]; * Returns Process = ALU = c.tail RTemp3← (RTemp3)+1, Branch[.+2, ALU=0]; RTemp3← (RTemp3)-1, Call[WakeHead]; MemBase← CVBR, RTemp3, Branch[BroadcastLoop, R<0]; RTemp1← ID, Branch[MesaReschedule1]; *----------------------------------------------------------- IFUR[REQUEUE, 1, CVBR]; * Requeue[sq, dq, psb]; *----------------------------------------------------------- T← Stack&-1, Branch[REQUEUEM2]; * PSB handle :IfMEP; T← Stack&-1← MD, Branch[.+1]; :EndIf; REQUEUEM2: Process← T, Call[Pop2ShortOrLong]; * CVBR← dq, MLBR← sq * Dirty both queues so that if a write-protect fault is to occur, * it will occur now. SQBRReg← T-T-1, Branch[ReQSQNil, ALU=0]; * Branch if sq=NIL T← Fetch← 0S; SQBRReg← BRConst[MLBR]; * sq in MLBR Store← T, DBuf← MD, FlipMemBase, Branch[.+2]; ReQSQNil: T← A0, FlipMemBase; Fetch← T, DQBRReg← BRConst[CVBR]; * dq in CVBR Store← T, DBuf← MD, Branch[Requeue&Resched]; *----------------------------------------------------------- Pop2ShortOrLong: * Pop two short or long pointers and load base registers. * Enter: MemBase, MemBase xor 1 = first and second BRs to be loaded * TIOA=0 * Exit: MemBase = entering MemBase xor 1 * Both BRs loaded * T = ALU = 0 iff second pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * IndexMask initialized * Clobbers RTemp0, DQBRReg *----------------------------------------------------------- Subroutine; DQBRReg← TIOA&StkP; * Assume emulator's TIOA=0 ! DQBRReg← (DQBRReg)-(2C); IndexMask← And[Q.tail!, 177400]C, Branch[.+2, ALU=0]; * StkP=4: long pointer, pop high part off stack. T← A0, BRHi← Stack&-1, Branch[.+2]; * StkP=2: short pointer, lengthen it with high part of MDS. T← (BRHi← MDSHi)-(MDSHi)-1; BRLo← Stack&-1; * Now ready to pop second pointer. * DQBRReg=2 and T=0 if first pointer was long; DQBRReg=0 and T=-1 if short. * Adjust DQBRReg to 1 in latter case. DQBRReg← (DQBRReg)-T, FlipMemBase, Branch[Pop1Tail]; *----------------------------------------------------------- Pop1ShortOrLong: * Pop one short or long pointer and load base register. * Enter: MemBase = BR to be loaded * TIOA=0 * Exit: MemBase unchanged * BR loaded * T = ALU = 0 iff pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * IndexMask initialized * Clobbers RTemp0, DQBRReg *----------------------------------------------------------- Subroutine; IndexMask← And[Q.tail!, 177400]C; DQBRReg← TIOA&StkP; * Assume emulator's TIOA=0 ! Pop1Tail: PD← (DQBRReg)-(2C); IndexMask← (IndexMask) OR (And[Q.tail!, 377]C), Branch[.+2, ALU#0]; * StkP=2: long pointer, pop high part off stack. T← BRHi← Stack&-1, Branch[.+2]; * StkP=1: short pointer, lengthen it with high part of MDS. T← A0, BRHi← MDSHi, Branch[Pop1StackErr, Carry]; T← (BRLo← Stack&-1) OR T, Return; * Opcode was not called with minimal stack; generate stack error trap. TopLevel; Pop1StackErr: Branch[StackError]; *----------------------------------------------------------- FetchCPFlags: * Fetches current process's flags. * Enter: MemBase = PDA * CurrentPSB set up * Exit: RTemp1 = CurrentPSB↑.flags * T = pointer to CurrentPSB↑.flags * Process = CurrentPSB *----------------------------------------------------------- Subroutine; T← (CurrentPSB)+1; * Know PSB.flags is word 1 Process← (Fetch← T)-1, Branch[.+2]; *----------------------------------------------------------- FetchCPLink: * Fetches current process's link * Enter: MemBase = PDA * CurrentPSB set up * Exit: RTemp1 = CurrentPSB↑.link * T = pointer to CurrentPSB↑.link * Process = CurrentPSB *----------------------------------------------------------- Subroutine; T← Process← Fetch← CurrentPSB; * Know that link is word 0 RTemp1← MD, Return; *----------------------------------------------------------- EnterFailed: * Enter: MLBR points to monitor lock * Exit: Does not return but rather goes directly to MesaReschedule *----------------------------------------------------------- TopLevel; * link ← Fetch[@PDA.block[PSB].link]↑; link.failed ← TRUE; * Store[@PDA.block[PSB].link]↑ ← link; * Requeue[src: @PSB.ready, dst: m, psb: PSB]; MemBase← PDA, Call[FetchCPLink]; * RTemp1← PSB.link, T← @PSB.link RTemp1← (RTemp1) OR (PSBL.failed); Store← T, DBuf← RTemp1; DQBRReg← BRConst[MLBR]; * dst = ML SQBRReg← BRConst[RQBR]; * src = ReadyQ Process← T, Branch[Requeue&Resched]; * T = CurrentPSB *----------------------------------------------------------- ExitMonitor: * Unlocks monitor, and moves process at head of monitor lock queue to * ready queue, if there is one. * Enter: T = 0 * MemBase = MLBR * MLBR = QueueHandle for MonitorLock * Clobbers T, RTemp0, RTemp1, RTemp2, Process, MemBase *----------------------------------------------------------- Subroutine; * ml ← Fetch[m]↑; ml.locked ← FALSE; Store[m]↑ ← ml; * IF ml.tail # PsbNull THEN ... Fetch← T; RTemp1← MD, T← (IndexMask) AND MD; RTemp1← (RTemp1) AND NOT (ML.locked), Branch[.+2, ALU#0]; Store← T, DBuf← RTemp1, Return; * BEGIN link ← Fetch[@PDA.block[mon.tail].link]↑; * Requeue[src: m, dst: @PDA.ready, psb: link.next]; END; Store← 0S, DBuf← RTemp1; MemBase← PDA; Fetch← T, DQBRReg← BRConst[RQBR]; * dst = ReadyQ SQBRReg← BRConst[MLBR]; * src = ML T← (IndexMask) AND MD; Process← Fetch← T, Branch[Requeue1]; *----------------------------------------------------------- NotifyWakeup: * Wakes the process at the head of the CV queue if there is one; * sets the wakeup waiting bit in the CV if there isn't. * Enter: CVBR = pointer to CV * IndexMask initialized * Exit: ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg, MemBase *----------------------------------------------------------- Subroutine; RTemp2← Link; TopLevel; * CleanupCondition[c]; * IF (cond ← Fetch[c]↑).tail = PsbNull THEN * BEGIN cond.wakeup ← TRUE; Store[c]↑ ← cond; END * ELSE WakeHead[c]; T← A0, MemBase← CVBR, Call[CleanUpCVQueue]; T← (RTemp0) OR (CV.wakeup), Branch[.+2, ALU=0]; Link← RTemp2, Branch[WakeHead]; * CV queue non-empty, awaken head Link← RTemp2; * CV queue empty, set wakeup waiting Subroutine; Store← 0S, PD← DBuf← T, Return; *----------------------------------------------------------- WakeHead: * Wakes the process at the head of the CV queue (there must be one). * Enter: CVBR = pointer to CV * Process = CV.tail * IndexMask initialized * Exit: MemBase = PDA * ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg *----------------------------------------------------------- Subroutine; T← Process, MemBase← PDA; * link ← Fetch[@PDA.block[cond.tail].link]↑; Fetch← T, * PSB.link is word 0 DQBRReg← BRConst[RQBR]; * dst = ReadyQ Process← (IndexMask) AND MD; * flags ← Fetch[@PDA.block[link.next].flags]↑; flags.waiting ← FALSE; * Store[@PDA.block[link.next].flags]↑ ← flags; * Store[@PDA.block[link.next].timeout]↑ ← 0; T← (Process)+(PSB.flags); RTemp0← (Fetch← T)+(Sub[PSB.timeout!, PSB.flags!]C); Store← RTemp0, DBuf← 0C, RTemp0← MD; RTemp0← (RTemp0) AND NOT (PSBF.waiting); Store← T, DBuf← RTemp0; * Requeue[src: c, dst: ready, psb: link.next]; SQBRReg← BRConst[CVBR]; * src = CV Fetch← Process, Branch[Requeue1]; *----------------------------------------------------------- Requeue: * Moves a process from a source queue to a destination queue. * Enter: SQBRReg = MemBase value for BR containing source QueueHandle; * bit 15 = 1 iff source QueueHandle is NIL, meaning the caller * doesn't know what queue the process is on. * DQBRReg = MemBase value for BR containing destination QueueHandle * Process = PSB to be requeued * MemBase = PDA * IndexMask initialized * Exit: MemBase = PDA * ALU#0 (for benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, ShC *----------------------------------------------------------- Subroutine; * Here we compute "prev", the predecessor of process, and put it in RTemp1. * If psb.link points to itself, queue will be empty after psb is removed. * IF src#NIL THEN queue ← Fetch[src]↑; * link ← Fetch[@PDA.block[psb].link]↑; * IF link.next = psb THEN prev ← PsbNull ... Fetch← Process, Global; Requeue1: RTemp1← shcQ.tail; * Set shifter for Q.tail masking RTemp0← T← (IndexMask) AND MD; RTemp2← MD, PD← (Process)#T; * RTemp2 = link, will be used later RTemp1← A0, ShC← RTemp1, Branch[RequeueGotPrev, ALU=0]; * Search for predecessor. Start at psb if sq is NIL, else at sq.tail. * ELSE BEGIN prev ← IF sq=NIL THEN psb ELSE queue.tail; * DO temp ← Fetch[@PDA.block[prev].link]↑; IF temp.next = psb THEN EXIT; * prev ← temp.next; ENDLOOP; * temp.next ← link.next; Store[@PDA.block[prev].link]↑ ← temp; END; MemBase← SQBRReg, Branch[.+2, R odd]; * Branch if src=NIL Fetch← 0S; * queue ← Fetch[src]↑ T← (IndexMask) AND MD, MemBase← PDA; * T = psb.next or queue.tail RequeueSearchSQ: RTemp1← Fetch← T; * T = potential predecessor T← (IndexMask) AND MD; PD← (Process)#T; Branch[RequeueSearchSQ, ALU#0]; * Now RTemp1 has predecessor of process, and MD has its link word. * Bypass process in queue structure. T← ShMDBothMasks[RTemp0]; * Insert RTemp0 = psb↑.link.next Store← RTemp1, DBuf← T; * RTemp1 = predecessor of process, or 0 if there wasn't one. * RTemp0 = psb↑.link.next. * IF src=NIL THEN * BEGIN flags ← Fetch[@PDA.block[psb].flags]↑; flags.cleanup ← link.next; * Store[@PDA.block[psb].flags]↑ ← flags; END; * ELSE IF queue.tail = psb THEN * BEGIN queue.tail ← prev; Store[src]↑ ← queue; END; RequeueGotPrev: T← A0, MemBase← SQBRReg, Branch[.+3, R even]; * Branch if src#NIL T← (Process)+1, MemBase← PDA; * Know flags is word 1 RTemp1← Fetch← T, Branch[StoreQueueOrCleanup]; Fetch← T, RTemp0← RTemp1; * Fetch[src]↑; RTemp0← prev T← (IndexMask) AND (MD); PD← (Process)#T; RTemp1← A0, Branch[BeginInsertDQ, ALU#0]; * In the following multi-purpose code, either: * MemBase = PDA, RTemp1 = @psb.flags, MD = psb.flags, and RTemp0 = link.next; * or: MemBase = SQBR, RTemp1 = 0, MD = src↑, and RTemp0 = prev. StoreQueueOrCleanup: T← ShMDBothMasks[RTemp0]; * Insert into Q.tail or flags.next Store← RTemp1, DBuf← T; * Requeue (cont'd) * Now begin to insert process into dst queue. First, test for the easy case * in which the dst queue is initially empty. RTemp2 still contains psb↑.link. * queue ← Fetch[dst]↑; link ← Fetch[@PDA.block[psb].link]↑; * IF queue.tail = PsbNull THEN * BEGIN link.next ← psb; Store[@PDA.block[psb].link]↑ ← link; * queue.tail ← psb; Store[dst]↑ ← queue; END; BeginInsertDQ: T← A0, MemBase← DQBRReg; RTemp0← Fetch← T; * Fetch[dst]↑ RTemp1← (IndexMask) AND MD; T← ShMDBothMasks[Process], Branch[CheckDQTail, ALU#0]; Store← RTemp0, DBuf← T; * Queue empty, make it point to psb T← Process, MemBase← PDA, * Link psb to itself Branch[RequeueStorePSBLink]; * dst is non-empty. Begin by testing whether process's priority is <= * the priority of the current tail process, and if so just append process. * RTemp1 points to current tail process; RTemp2 = psb↑.link. * ELSE BEGIN prev ← queue.tail; temp ← Fetch[@PDA.block[prev].link]↑; * IF temp.priority >= link.priority THEN * BEGIN queue.tail ← psb; Store[dst]↑ ← queue; END; CheckDQTail: RTemp0← T, MemBase← PDA; * In case psb becomes new tail (below) Fetch← RTemp1, T← PSBL.priority; RTemp2← (RTemp2) AND T; * Extract psb↑.link.priority T← T AND MD; * Extract prev↑.link.priority PD← T-(RTemp2); T← (IndexMask) AND MD, Branch[SearchDQLoop, ALU<0]; * Process priority <= tail priority. Just make process be new tail. T← A0, MemBase← DQBRReg; Store← T, DBuf← RTemp0; * Make queue point to psb MemBase← PDA, Branch[RequeueStorePrevLink]; * Link it into the queue * Must search queue to find proper place to insert process. * Find prev = last process whose priority is >= that of psb. * At this point, RTemp1 points to tail process, and MD = its link word, * which is a pointer to the head process. T = temp.next. * ELSE DO next ← Fetch[@PDA.block[temp.next].link]↑; * IF link.priority > next.priority THEN EXIT; * prev ← temp.next; temp ← next; ENDLOOP; SearchDQLoop: RTemp0← Fetch← T; T← PSBL.priority; T← T AND MD; PD← (RTemp2)-T-1; T← (IndexMask) AND MD, Branch[RequeueStorePrevLink, ALU>=0]; RTemp1← RTemp0, Branch[SearchDQLoop]; * Now RTemp1 = prev = the process after which we are to insert. * link.next ← temp.next; Store[@PDA.block[psb].link]↑ ← link; * temp.next ← psb; Store[@PDA.block[prev].link]↑ ← temp; END; RequeueStorePrevLink: Fetch← RTemp1; * Insert Process into prev↑.link.next T← ShMDBothMasks[Process]; Store← RTemp1, DBuf← T, T← MD; * T← former prev↑.link * Insert T into psb↑.link.next and return. RequeueStorePSBLink: RTemp0← T, Fetch← Process; * Know link is word 0 T← ShMDBothMasks[RTemp0]; PD← Store← Process, DBuf← T, Return; * ALU#0 *----------------------------------------------------------- CleanUpCVQueue: * Cleans up CV queue that may have been left in a mess by Requeue. * Enter: MemBase = CVBR * CVBR points to condition variable c * T = 0 * IndexMask initialized * Exit: MemBase = CVBR * ALU = Process = c↑.tail (as modified by subroutine, if necessary) * RTemp0 = c↑ (modified, if necessary; tail field may not be valid) * Clobbers T, RTemp0, RTemp1 *----------------------------------------------------------- Subroutine; * If CV queue is empty then do nothing. * cond ← Fetch[c]↑; psb ← cond.tail; IF psb # PsbNull THEN ... T← IndexMask, Fetch← T, Global; * Force write-protect fault to occur here if it's going to occur at all. Store← 0S, Process← DBuf← MD; Process← T← (Process) AND T, MemBase← PDA; T← T+1, RTemp0← MD, Branch[.+2, ALU#0]; * RTemp0← cond CleanupCVReturn: PD← Process, MemBase← CVBR, Return; * Queue is empty, nothing to do * If head PSB's cleanup link is nil then do nothing. * BEGIN flags ← Fetch[@PDA.block[psb].flags]↑; * IF flags.cleanup # PsbNull THEN ... T← IndexMask, Fetch← T; * psb↑.flags -- know flags are word 1 RTemp0← (RTemp0) AND NOT T; * cond.tail ← PsbNull (for later) PD← (IndexMask) AND MD; SearchForCVQHead: T← (IndexMask) AND MD, Branch[.+2, ALU#0]; * T← psb↑.flags.cleanup PD← Process, MemBase← CVBR, Return; * No cleanup queue, nothing to do * Process has a psb to check, and ALU = T = psb↑.flags.cleanup. * Follow cleanup links until we either hit NIL or * find a PSB with a cleanup link pointing to itself. * BEGIN DO IF flags.cleanup = psb then * BEGIN cond.wakeup ← FALSE; cond.tail ← PsbNull; Store[c]↑ ← cond; * RETURN; END; * psb ← flags.cleanup; flags ← Fetch[@PDA.block[psb].flags]↑; * IF flags.cleanup = PsbNull THEN EXIT; * ENDLOOP; PD← (Process)#T, Branch[FoundCVQHead, ALU=0]; T← T+1, Branch[MakeCVQEmpty, ALU=0]; Process← (Fetch← T)-1, Branch[SearchForCVQHead]; * ALU#0 guaranteed * Found a PSB with a cleanup link pointing to itself. This can happen * if a PSB is Requeued when it is the only PSB on the CV queue. * Reset the CV queue to empty. MakeCVQEmpty: RTemp0← (RTemp0) AND NOT (CV.wakeup); Process← T← A0, Branch[StoreCVQueue]; * Found a PSB with a NIL cleanUp link, and not the first in the cleanUp queue. * Put it at the head of the CV queue. Since queues are circular and rooted * through their tail, this requires chasing down the queue to find the tail. * head ← psb; * DO link ← Fetch[@PDA.block[psb].link]↑; IF link.next = head THEN EXIT; * psb ← link.next; ENDLOOP; FoundCVQHead: RTemp1← T← Fetch← Process; * Remember head, fetch psb↑.link SearchForCVQTail: Process← T; T← (IndexMask) AND MD; * T← psb↑.link.next PD← (RTemp1)#T; Fetch← T, Branch[SearchForCVQTail, ALU#0]; * Process = new tail PSB; RTemp0 = cond with tail field zeroed out. * cond.tail ← psb; Store[c]↑ ← cond; END; END; T← Process; StoreCVQueue: RTemp0← (RTemp0) OR T, MemBase← CVBR; Store← 0S, DBuf← RTemp0, Branch[CleanupCVReturn]; *----------------------------------------------------------- IFUR[IWDC, 1, L]; * Increment Wakeup Disable Counter: wdc ← wdc+1; *----------------------------------------------------------- T← (WDC)+1, Branch[CheckValidWDC]; :IfMEP; Stack← MD, T← (WDC)+1, Branch[CheckValidWDC]; StkP+1, Branch[.-2]; :EndIf; *----------------------------------------------------------- IFUR[DWDC, 1, L]; * Decrement Wakeup Disable Counter: wdc ← wdc-1; * Note: one instruction must be executed after DWDC before an interrupt may occur. *----------------------------------------------------------- PD← NWW, NoReschedule, DblBranch[DWDCRescPend, DWDCNoRescPend, Reschedule]; :IfMEP; Stack← MD, Branch[.-1]; StkP+1, Branch[.-2]; :EndIf; * If a Reschedule was already pending, we must restart the IFU in order to * prevent a reschedule trap from occurring on the next IFUJump, even though * we have already done a NoReschedule. DWDCRescPend: T← (ID)-(PCX')-1; PD← NWW, PCF← T; DWDCNoRescPend: T← (WDC)-1, Branch[.+2, ALU=0]; * Any interrupt requests in? * Actually, should Reschedule only if WDC=0 now, but incrementing WDC * more than once is uncommon, and MesaReschedTrap will sort this out anyway. * Note that Reschedule doesn't take effect until the second successful IFUJump. PD← T, Reschedule; CheckValidWDC: Branch[.+2, ALU<0]; WDC← T, IFUNext0; * Normal exit T← sWakeupError, Branch[SavePCAndTrap]; * Over-incremented/decremented *----------------------------------------------------------- MesaFault: * Suspend current process and invoke fault handler * Enter: T = FaultVector offset = 2*FaultIndex for fault handler * FaultParam0, FaultParam1 = fault parameters, if any * Exit: Puts current process on fault queue, awakens handler, and * performs a Reschedule. *----------------------------------------------------------- TopLevel; Q← XferFlags, * Preserve XferFlags (=IndexMask) Branch[.+2, R<0]; * Branch if xf.invalidContext RestoreStkP; * Requeue[dst: @PDA.fault[fi].queue, src: @PDA.ready, psb: PSB]; MemBase← MLBR; RTemp0← T+(PDA.fault); RTemp1← PDAHi; RTemp0← (BRLo← RTemp0)+1; * MLBR← @PDA.fault[fi].queue BRHi← RTemp1; T← CurrentPSB, MemBase← CVBR; * Process← PSB to requeue BRLo← RTemp0; * CVBR← @PDA.fault[fi].condition BRHi← RTemp1; DQBRReg← BRConst[MLBR]; * Destination is fault queue SQBRReg← BRConst[RQBR]; * Source is ready queue IndexMask← And[Q.tail!, 177400]C; IndexMask← (IndexMask) OR (And[Q.tail!, 377]C); Process← T, MemBase← PDA, Call[Requeue]; * Move CurrentPSB to fault queue * NotifyWakeup[@PDA.fault[fi].condition]; * PC ← savedPC; -- done by hardware * SP ← savedSP; -- done by RestoreStkP (above) * Reschedule[preemption: TRUE]; Call[NotifyWakeup]; * CVBR points to CV already XferFlags← Q, Branch[NoMoreInts]; * Recover XferFlags, go preempt current process *----------------------------------------------------------- * These are exception entries for the Mesa instruction set *----------------------------------------------------------- Set[MesaTrapBase, Sub[300, LShift[MesaInsSet, 6]]]; DontKnowRBase; TopLevel; MesaIFUNotReady: At[MesaTrapBase, 34], IFUNext0; :IfMEP; At[MesaTrapBase, 35], T← Stack&-1← MD, IFUNext2; At[MesaTrapBase, 36], IFUNext2; :EndIf; *----------------------------------------------------------- * Reschedule trap and interrupt processing *----------------------------------------------------------- MesaReschedTrap: At[MesaTrapBase, 14], RBase← RBase[WDC], Branch[MesaResched1]; :IfMEP; At[MesaTrapBase, 15], Stack← MD, RBase← RBase[WDC], Branch[MesaResched1]; At[MesaTrapBase, 16], StkP+1, RBase← RBase[WDC], Branch[MesaResched1]; :EndIf; * Immediately reset the Reschedule trap condition and restart the IFU, so * that if we subsequently decide there is nothing to do (either here or * later), we can simply exit with an IFUNext0. MesaResched1: PD← WDC, NoReschedule; T← NOT (PCX'), Branch[MesaIntDisabled, ALU#0]; T← NWW, PCF← T; * NWW#0 means interrupt pending MesaReschedTest: NWW← (NWW) AND NOT T, * Clear interrupts we will process Branch[MesaInterrupt, ALU#0]; IFUNext0; * No interrupt, resume execution MesaIntDisabled: T← A0, PCF← T, Branch[MesaReschedTest]; *----------------------------------------------------------- MesaInterrupt: * Enter: T = interrupt request mask * CurrentPSB#0 if interrupted from normal emulation; CurrentPSB=0 if from idle loop. * WDC=0 *----------------------------------------------------------- * Make a special test for an interrupt occurring on the timer channel * (60 hz), and do CheckForTimeouts when one occurs. IndexMask← And[Q.tail!, 177400]C; IndexMask← (IndexMask) OR (And[Q.tail!, 377]C); WDC← T; DQBRReg← A0, MemBase← PDA, Branch[CheckForTimeouts, ALU<0]; * For each interrupt that is pending, check the naked CV in the corresponding * slot in the PDA.interrupt array. * RTemp3 keeps the PDA-relative pointer, and WDC is used to shift the * interrupt request bits. * WW: ARRAY InterruptLevel OF BIT; * FOR level DECREASING IN InterruptLevel DO * IF WW[level]#0 THEN * requeue ← requeue OR NotifyWakeup[@PDA.interrupt[level].condition]; CheckNakedCVs: RTemp3← Add[PDA.interrupt!, 40]C, Branch[CheckNakedCVLoop]; NakedCVWakeup: BRLo← RTemp3, Call[NotifyWakeup]; * Returns with ALU#0 CheckNakedCVLoop: RTemp3← (RTemp3)-(2C), Branch[NoMoreInts, ALU=0]; WDC← (WDC) RSH 1, Branch[.-1, R even]; MemBase← CVBR; * Interrupt for this CV T← PDAhi; BRHi← T, Branch[NakedCVWakeup]; * All interrupt requests have been processed, and WDC=0 again. * If we were interrupted from normal emulation (CurrentPSB#0), do a Reschedule iff * the interrupt actually caused any processes to be requeued. * If we were interrupted from the idle loop (CurrentPSB=0), go reconsider the ReadyQ. NoMoreInts: PD← CurrentPSB, MemBase← PDA; RTemp1← A0, DblBranch[MesaReschedule1, IdleReschedule, ALU#0]; *----------------------------------------------------------- CheckForTimeouts: * This is invoked every time an interrupt is requested on the * 60 hz timer channel. This microcode "knows" which channel the * Mesa system enables for that purpose! * Enter: MemBase = PDA * Exit: MemBase = PDA * Clobbers T, RTemp0-3, Process, DQBRReg, SQBRReg *----------------------------------------------------------- * Scan PSBs only every third tick RBase← RBase[TickCount]; TickCount← (TickCount)-1, RBase← RBase[RTemp0]; T← Fetch← PDA.count, Branch[.+2, ALU=0]; Branch[CheckNakedCVs]; * count ← Fetch[@PDA.count]↑; * FOR psb IN [StartPsb..StartPsb+count) DO * timeout ← Fetch[@PDA.block[psb].timeout]↑; * IF timeout#0 AND timeout=CurrentTime THEN * BEGIN flags ← Fetch[@PSB.block[psb].flags]↑; * flags.waiting ← FALSE; Store[@PDA.block[psb].flags]↑ ← flags; * Store[@PDA.block[psb].timeout]↑ ← 0; * Requeue[src: 0, dst: @PDA.ready, psb:psb]; * END; ENDLOOP TickCount← T+T+1; * = 3 (know PDA.count=1) CurrentTime← (CurrentTime)+1; T← Add[PDA.psbs!, PSB.timeout!]C, * Timeout word of first PSB RTemp3← MD; * Use this to count PSBs * T points to PSB timeout word CheckTimerLoop: Process← (Fetch← T)-(PSB.timeout); PD← MD; PD← (CurrentTime)-MD, Branch[CanTimeout, ALU#0]; RTemp3← (RTemp3)-1; NoTimeout: T← (Process)+(Add[PSB.timeout!, sizePSB!]C), Branch[CheckTimerLoop, ALU#0]; Branch[CheckNakedCVs]; CanTimeout: RTemp3← (RTemp3)-1, Branch[NoTimeout, ALU#0]; * This process has timed out. T = @psb.timeout RTemp0← T+(Sub[PSB.flags!, PSB.timeout!]C); Fetch← RTemp0; * Fetch psb.flags Store← T, DBuf← 0C, T← MD; * psb.timeout ← 0 T← T AND (Not[PSBF.waiting!]C); Store← RTemp0, DBuf← T; DQBRReg← BRConst[RQBR]; * dst = ReadyQ SQBRReg← T-T-1, Call[Requeue]; * src = NIL PD← RTemp3, Branch[NoTimeout]; *----------------------------------------------------------- Requeue&Resched: * Do a Requeue followed by a Reschedule. * Arguments as for Requeue. *----------------------------------------------------------- MemBase← PDA, Call[Requeue]; *----------------------------------------------------------- MesaReschedule: * PrincOps Reschedule operation * Also includes SaveProcess, LoadProcess, AllocState, FreeState, and * part of Fault, suitably flattened out into in-line code. * Process/monitor opcodes normally branch here when they are done. * Can get here at the end of interrupt processing also. * Enter: DQBRReg[0] = 1 iff a Requeue has been done. * Exit: Resumes execution of this or some other process. *----------------------------------------------------------- TopLevel; RTemp1← ID; * RTemp1← IL * Here RTemp1 = 0 if came from MesaInterrupt or MesaFault; = IL if came from * process/monitor opcode. Note that IL = 1 for all process/monitor opcodes; * therefore "RTemp1 even" is synonymous with "preemption". * IF ~reschedulePending THEN RETURN; MesaReschedule1: MemBase← PDA, DQBRReg, Branch[.+2, R<0]; IFUNext0; * No Requeue, resume normal execution *----------------------------------------------------------- * Save state of current process. * Can get here due to a Fault, after requeueing CurrentPSB onto a fault queue. *----------------------------------------------------------- * link ← Fetch[@PDA.block[PSB].link]↑; * IF ValidContext[] THEN StoreMDS[@LocalBase[L].pc]↑ ← PC; * IF (link.vector ← preemption) THEN * BEGIN state ← AllocState[link.priority]; SaveStack[state]; * Store[@state.frame]↑ ← L; Store[@PDA.block[PSB].context]↑ ← state; END * ELSE Store[@PDA.block[psb].context]↑ ← L; * Store[@PDA.block[PSB].link]↑ ← link; * Validity of context is indicated by XferFlags[xf.invalidContext], which * is checked by SavePCInFrame. Fetch← CurrentPSB; * Know PSB.link is word 0 T← MD, RTemp1← (RTemp1)-(PCX')-1, * PCX or PCX+IL Branch[NotPreemption, R odd]; * Here to preempt current process (possibly due to a fault). * AllocState: need to allocate a StateVector. * state ← Fetch[@PDA.state[priority]]↑; IF state=NIL THEN ERROR; * Store[@PDA.state[priority]]↑ ← Fetch[state]↑; * Careful not to clobber RTemp3 (=FaultParam1) until it is stored. * Must call SaveState before removing StateVector from list, because SaveState * might generate a StackError trap (during interrupts only, never during faults). RTemp2← T OR (PSBL.vector); RTemp4← LDF[T, 3, 14]; * Extract link.priority RTemp4← (RTemp4)+(PDA.state); Fetch← RTemp4, * Fetch head of state list Call[SavePCInFrame]; * Returns L in T RTemp0← PD← MD, MemBase← PDA; * RTemp0← head StateVector DLink← T, Branch[NoStateVector, ALU=0]; * Branch if list empty Fetch← RTemp0; * Fetch successor Call[SaveState]; * Save stack, StkP, DLink; doesn't clobber MD Store← RTemp4, DBuf← MD; * Store successor as head * The following is really part of FaultTwo rather than SaveProcess: * Store[@state.data[0]]↑ ← LowHalf[parameter]; -- = FaultParam0 * Store[@state.data[1]]↑ ← HighHalf[parameter]; -- = FaultParam1 T← (Store← T)+1, DBuf← FaultParam0; Store← T, DBuf← FaultParam1, Branch[EndSaveProcess]; * Use RTemp0 = state as context NoStateVector: Breakpoint, Branch[.]; * No state vector for preempted process * Here if preemption not in progress: just save PC and use L as context. NotPreemption: RTemp2← T AND NOT (PSBL.vector), Call[SavePCInFrame]; * Returns L in T RTemp0← T, MemBase← PDA; * Together again here. * Now RTemp0 = context (frame or state vector); RTemp2 = updated PSB.link. EndSaveProcess: T← CurrentPSB; Store← T, DBuf← RTemp2; T← T+(PSB.context); Store← T, DBuf← RTemp0; *----------------------------------------------------------- * Check ready queue for new process to run. * Can get here after saving current process (above), or after * an interrupt from BusyWait (below). * A process is runnable if either it already has a StateVector (i.e., the * process was preempted) or a free StateVector is available at the * process's priority level. *----------------------------------------------------------- * queue ← Fetch[@PDA.ready]↑; IF queue.tail = PsbNull THEN GOTO BusyWait; * link ← Fetch[@PDA.block[queue.tail].link]↑; * DO psb ← link.next; link ← Fetch[@PDA.block[psb].link]↑; * IF link.vector OR ~EmptyState[link.priority] THEN EXIT; * IF psb = queue.tail THEN GOTO BusyWait; * ENDLOOP; * EXITS BusyWait => * BEGIN IF WDC#0 THEN RescheduleError[]; running ← FALSE; RETURN; END; IdleReschedule: Fetch← PDA.ready; * Fetch queue IndexMask← And[Q.tail!, 177400]C; IndexMask← (IndexMask) OR (And[Q.tail!, 377]C), T← MD; RTemp0← Fetch← T; * Fetch link word of tail PSB T← (IndexMask) AND MD, FreezeBC; FindRunnableProcess: CurrentPSB← Fetch← T, Q← T, * Fetch link word of next PSB Branch[BusyWait, ALU=0]; * Branch if queue empty or hit tail RTemp2← MD; T← LDF[RTemp2, 3, 14]; * T← link.priority RTemp2← T+(PDA.state), * See if StateVector is available Branch[LoadPreempted, R odd]; * Branch if link.vector=TRUE Fetch← RTemp2, RTemp2← T← B← MD; T← (IndexMask) AND T; * T← next PSB PD← MD; PD← (RTemp0)-Q, * See if CurrentPSB = queue.tail DblBranch[LoadNonPreempted, FindRunnableProcess, ALU#0]; * Ready queue is empty, or there are no runnable processes. * The only way out of this state is to get a naked notify (or a timeout) * on a CV that some process is waiting on. Therefore, spin here until * an interrupt request is raised. (This is our implementation of setting * the "running" flag to FALSE and returning.) BusyWait: PD← WDC; T← sWakeupError, Branch[.+2, ALU=0]; Branch[MTrap]; * It's an error to be idle with WDC#0 CurrentPSB← A0; * Indicate we are idle T← NWW, NoReschedule, Branch[., ALU=0]; NWW← (NWW) AND NOT T, * Clear interrupts about to be caused Branch[MesaInterrupt]; * CurrentPSB=0 => will return to IdleReschedule * Reschedule (cont'd) *----------------------------------------------------------- * Run preempted process. * CurrentPSB = process to run; RTemp2 = @PDA.state[link.priority]. *----------------------------------------------------------- * state ← Fetch[@PDA.block[PSB].context]↑ * LoadStack[state]; L ← Fetch[@state.frame]; * FreeState[link.priority, state]; LoadPreempted: T← (CurrentPSB)+(PSB.context); Fetch← T, * PSB.context = StateVector XferFlags← xf.invalidContext; * xf.free← 0, xf.push← 0 RTemp0← MD, Call[LoadStack]; * Load stack, StkP, DLink * FreeState: * Store[state]↑ ← Fetch[@PDA.state[priority]]↑; * Store[@PDA.state[priority]]↑ ← state; T← Fetch← RTemp2; Store← T, DBuf← RTemp0, T← MD; Store← RTemp0, DBuf← T, Branch[XferToNewProcess]; *----------------------------------------------------------- * Run non-preempted process. * CurrentPSB = Q = process to run; RTemp2 = PSB.link. *----------------------------------------------------------- * L ← Fetch[@PDA.block[PSB].context]; * IF link.failed THEN * BEGIN Push[FALSE]; link.failed ← FALSE; * Store[@PDA.block[psb].link]↑ ← link; END; LoadNonPreempted: T← (CurrentPSB)+(PSB.context); Fetch← T, * PSB.context = frame XferFlags← xf.invalidContext; * xf.free← 0, xf.push← 0 DLink← MD, T← RTemp2, Branch[XferToNewProcess, R>=0]; T← T AND NOT (PSBL.failed), StkP+1; Store← Q, DBuf← T, Stack← A0; * Store updated link; Push[FALSE] * Preempted and non-preempted cases together here. DLink = new local frame. * Set the new local frame now so that if a fault occurs during the * subsequent Xfer, the correct context is saved. XferToNewProcess: MemBase← L; BRLo← DLink; SLink← A0, MemBase← MDS, Branch[Xfer];