*----------------------------------------------------------- Title[Process.mc...November 19, 1982 11:00 AM...Taft]; * Processes (PrincOps, chapter 10) *----------------------------------------------------------- % CONTENTS, by order of occurence Process instructions ME Monitor Entry MX Monitor Exit MW Monitor Wait MR Monitor Reentry NC Notify Condition BC Broadcast Condition REQ Requeue SPP Set Process Priority DI Disable Interrupts EI Enable Interrupts Scheduling MesaFault Fault handling MesaReschedTrap Reschedule trap InterruptLongRunningOpcode Reschedule called explicitly from emulator MesaInterrupt Interrupt handling CheckForTimeouts Check for process timeouts MesaReschedule PrincOps Reschedule operation Subroutines Pop2MinimalStack Prepare 2 long pointer arguments Pop1MinimalStack Prepare 1 long pointer argument FetchCPFlags Fetch current process's flags FetchCPLink Fetch current process's link TestMonitorLock Test and set monitor lock EnterFailed Handle failure to enter monitor ExitMonitor Exit from monitor NotifyWakeup Perform naked notify WakeHead Awaken head process on CV queue Requeue Move process from one queue to another CleanUpCVQueue Clean up condition queue % TopLevel; *----------------------------------------------------------- * 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: * PSB Process State Block (PsbIndex*SIZE[ProcessStateBlock]) * PTC Process Timeout Count, in ~50 ms ticks * WDC Wakeup Disable Counter * WP Wakeups Pending * TickCount Counter to divide down vertical field interrupts to make ticks * IndexMask Constant mask for extracting Q.tail words * RTemp0-3 are used for general scratch purposes. * The remaining RTemps are uniformly 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 * 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: a Queue has the same structure as the words containing * CV.tail, ML.tail, PSBL.next, and PSBF.cleanup. * QueueHandle: LONG POINTER TO Queue; * Queue: TYPE = MACHINE DEPENDENT RECORD [ * reserved1 (0: 0..3): [0..17B] ← 0, MC[Q.tail, 7774]; * tail (0: 4..13): PsbIndex, * reserved2 (0: 14..15): [0..3]; MC[shcQ.tail, 44]; * ShC for masking Q.tail: Count = 0, LMask = 4, RMask = 2 * PsbHandle: TYPE = POINTER TO ProcessStateBlock; -- PDA-relative * PsbIndex: TYPE = [0..1024); * Invariant: for any given PSB, PsbHandle = 4*PsbIndex. * ProcessStateBlock: TYPE = MACHINE DEPENDENT RECORD [ MC[PSB.link, 0]; * link (0): PsbLink, MC[PSB.flags, 1]; * flags (1): PsbFlags, MC[PSB.context, 2]; * context (2): POINTER, -- to Frame or StateVector MC[PSB.mds, 3]; * mds (3): CARDINAL ]; MC[sizePSB, 4]; Set[logSizePSB, 2]; * PsbLink: TYPE = MACHINE DEPENDENT RECORD [ MC[PSBL.failed, 100000]; * failed (0: 0..0): BOOLEAN, MC[PSBL.priority, 70000]; * priority (0: 1..3): Priority, MC[PSBL.next, 7774]; * next (0: 4..13): PsbIndex, * permanent (0: 14..14): BOOLEAN, MC[PSBL.preempted, 1]; * preempted (0: 15..15): BOOLEAN ]; -- know this is bit 15 * PsbFlags: TYPE = MACHINE DEPENDENT RECORD [ * available (0: 0..2): [0..7], * reserved (0: 3..3): BIT ← 0, MC[PSBF.cleanup, 7774]; * cleanup (0: 4..13): PsbIndex, MC[PSBF.waiting, 2]; * waiting (0: 14..14): BOOLEAN, -- know this is bit 14 MC[PSBF.abort, 1]; * abort (0: 15..15): BOOLEAN ]; -- know this is bit 15 * Condition: TYPE = MACHINE DEPENDENT RECORD [ * reserved (0: 0..3): [0..17B] ← 0, MC[CV.tail, 7774], * tail (0: 4..13): PsbIndex, MC[CV.abortable, 2]; * abortable (0: 14..14): BOOLEAN, MC[CV.wakeup, 1]; * wakeup (0: 15..15): BOOLEAN ]; -- know this is bit 15 * Monitor: TYPE = MACHINE DEPENDENT RECORD [ * reserved (0: 0..3): [0..17B] ← 0, MC[ML.tail, 7774]; * tail (0: 4..13): PsbIndex, * available (0: 14..14): BIT, MC[ML.locked, 1]; * locked (0: 15..15): BOOLEAN ]; -- know this is bit 15 * 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 ]; * TimeoutVector: TYPE = ARRAY PsbIndex OF TICKS; * FaultIndex: TYPE = [0..7]; * InterruptLevel: TYPE = [0..WordSize); * Process Data Area (PDA) format, relative to PDA base register * ProcessDataArea: TYPE = MACHINE DEPENDENT RECORD [ * SELECT OVERLAID * FROM * header => [ 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, * available: ARRAY [3..7] OF UNSPECIFIED, MC[PDA.state, 10]; * state: ARRAY Priority OF POINTER TO StateVector, MC[PDA.interrupt, 20]; * interrupt: ARRAY InterruptLevel OF RECORD [ * condition: Condition, available: UNSPECIFIED], MC[PDA.fault, 60]; * fault: ARRAY FaultIndex OF RECORD [ * queue: Queue, condition: Condition]], * blocks => [ * block: ARRAY [0..0) OF ProcessStateBlock], * ENDCASE]; MC[StartPsb, 20]; * = SIZE[ProcessDataArea]/Size[ProcessStateBlock] *----------------------------------------------------------- NewOp; ESCEntry[ME]; * Monitor Entry * m: LONG POINTER TO Monitor ← PopLong[]; mon: Monitor; MinimalStack[]; * mon ← Fetch[m]↑; * IF ~mon.locked THEN {mon.locked ← TRUE; Store[m]↑ ← mon; Push[TRUE]} * ELSE EnterFailed[m]; *----------------------------------------------------------- RTemp0← A0, MemBase← MLBR, Call[Pop1MinimalStack]; * MLBR← m Fetch← RTemp0, Call[TestMonitorLock]; * Does not return if already locked * Here with Q = monitor with locked bit set; StkP advanced. LockMonitorAndExit: Stack← (Store← 0S)+1, DBuf← Q, * Store monitor and push TRUE NextOpcode; * This cannot fault, so CF not needed *----------------------------------------------------------- NewOp; ESCEntry[MX]; * Monitor Exit * m: LONG POINTER TO Monitor ← PopLong[]; MinimalStack[]; * IF Exit[m] THEN Reschedule; *----------------------------------------------------------- RTemp0← A0, MemBase← MLBR, Call[Pop1MinimalStack]; * MLBR← m Fetch← RTemp0, Call[ExitMonitor]; RTemp1← (ID)-1, Branch[MesaReschedule1]; * RTemp1← IL-1; reschedule if requeued *----------------------------------------------------------- NewOp; ESCEntry[MW]; * Monitor Wait * t: Ticks ← Pop[]; c: LONG POINTER TO Condition ← PopLong[]; * m: LONG POINTER TO Monitor ← PopLong[]; * flags: PsbFlags; cond: Condition; requeue: BOOLEAN; * MinimalStack[]; CleanupCondition[c]; requeue ← Exit[m]; * flags ← Fetch[@PDA.block[PSB].flags]↑; cond ← Fetch[c]↑; * IF ~(flags.abort AND cond.abortable) THEN { * IF cond.wakeup THEN {cond.wakeup ← FALSE; Store[c]↑ ← cond} * ELSE { * timeout: POINTER TO TimeoutVector ← Fetch[@PDA.timeout]↑; * StorePda[@timeout[PSB]] ← 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]; requeue ← TRUE}; * IF requeue THEN Reschedule[]; *----------------------------------------------------------- T← Stack&-1, MemBase← CVBR; RTemp3← T, Call[Pop2MinimalStack]; * RTemp3← t, CVBR← c, MLBR← m T← A0, MemBase← CVBR, Call[CleanUpCVQueue]; T← A0, MemBase← MLBR; Fetch← T, Call[ExitMonitor]; * Test for abort pending 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)-1, Branch[MesaReschedule1]; * Aborting, don't wait * Test for wakeup pending RTemp0← (RTemp0) AND NOT (CV.wakeup), Branch[.+2, R even]; * cond.wakeup is bit 15 Store← T, DBuf← RTemp0, Branch[MesaReschedule]; * Wakeup pending, don't wait * Set timeout and requeue PSB onto condition T← RTemp3, RBase← RBase[PTC]; * T← timeout T← (PTC)+T, RBase← RBase[MesaRegsMain], Branch[.+3, ALU=0]; * Branch if timeout is zero (= infinity) PD← T-1; * Generates carry unless T=0 RTemp3← T+1, XorSavedCarry; T← (ID) OR (RTemp1), MemBase← PDA; * Know ID = 2 = PSBF.waiting Store← RTemp2, DBuf← T; * Store updated flags Fetch← PDA.timeout; * Compute @PDA.timeout[PSB] T← RSH[PSB, logSizePSB]; T← T+MD; DQBRReg← BRConst[CVBR]; * dst = c SQBRReg← BRConst[RQBR]; * src = ready Store← T, DBuf← RTemp3, Branch[Requeue&Resched]; *----------------------------------------------------------- NewOp; ESCEntry[MR]; * Monitor Reentry * c: LONG POINTER TO Condition ← PopLong[]; * m: LONG POINTER TO Monitor ← PopLong[]; mon: Monitor; MinimalStack[]; * mon ← Fetch[m]↑; * IF ~mon.locked THEN { * flags: PsbFlags; CleanupCondition[c]; flags ← Fetch[@PDA.block[PSB].flags]↑; * flags.cleanup ← PsbNull; Store[@PDA.block[PSB].flags]↑ ← flags; * IF flags.abort THEN { * cond: Condition ← Fetch[c]↑; IF cond.abortable THEN ProcessTrap[]}; * mon.locked ← TRUE; Store[m]↑ ← mon; Push[TRUE]} * ELSE EnterFailed[m]; *----------------------------------------------------------- RTemp0← A0, MemBase← CVBR, Call[Pop2MinimalStack]; * CVBR← c, MLBR← m, MemBase← MLBR Fetch← RTemp0, Call[TestMonitorLock]; * Does not return if already locked T← A0, MemBase← CVBR, Call[CleanUpCVQueue]; T← (PSB)+1, MemBase← PDA; * PSB.flags is word 1 Fetch← T, RTemp2← NOT (IndexMask); RTemp2← (RTemp2) AND MD; * Zero cleanup link Store← T, DBuf← RTemp2, Branch[.+2, R odd]; * abort is bit 15 MemBase← MLBR, Branch[LockMonitorAndExit]; * Abort pending. RTemp0 contains cond (left over from CleanUpCVQueue). PD← (RTemp0) AND (CV.abortable); Branch[.+2, ALU=0]; T← sProcessTrap, Branch[SavePCAndTrap]; MemBase← MLBR, Branch[LockMonitorAndExit]; *----------------------------------------------------------- NewOp; ESCEntry[NC], * Notify Condition * c: LONG POINTER TO Condition ← PopLong[]; cond: Condition; * MinimalStack[]; CleanupCondition[c]; * IF (cond ← Fetch[c]↑).tail#PsbNull THEN {WakeHead[c]; Reschedule[]}; *----------------------------------------------------------- RTemp3← A0, Branch[NotifyBroadcast]; *----------------------------------------------------------- NewOp; ESCEntry[BC], * Broadcast Condition * c: LONG POINTER TO Condition ← PopLong[]; cond: Condition; requeue: BOOLEAN; * MinimalStack[]; CleanupCondition[c]; * WHILE (cond ← Fetch[c]↑).tail#PsbNull DO WakeHead[c]; requeue ← TRUE; ENDLOOP; * IF requeue THEN Reschedule[]; *----------------------------------------------------------- RTemp3← T-T-1, Branch[NotifyBroadcast]; * This code is shared by NC and BC. RTemp3 = 0 if NC, -1 if BC. * Note that we actually call CleanUpCVQueue every iteration. * This is ok since it is an idempotent operation. NotifyBroadcast: MemBase← CVBR, Call[Pop1MinimalStack]; * CVBR← c 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)-1, Branch[MesaReschedule1]; *----------------------------------------------------------- NewOp; ESCEntry[REQ]; * Requeue * psb: PsbHandle ← Pop[]; dstque: QueueHandle ← PopLong[]; * srcque: QueueHandle ← PopLong[]; MinimalStack[]; * Requeue[src: srcque, dst: dstque, psb: Index[psb]]; Reschedule[]; *----------------------------------------------------------- T← Stack&-1, MemBase← CVBR; Process← T, Call[Pop2MinimalStack]; * CVBR← dstque, MLBR← srcque * 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 srcque=NIL T← Fetch← 0S; SQBRReg← BRConst[MLBR]; * srcque in MLBR Store← T, DBuf← MD, FlipMemBase, Branch[.+2]; ReQSQNil: T← A0, FlipMemBase; Fetch← T, DQBRReg← BRConst[CVBR]; * dstque in CVBR Store← T, DBuf← MD, Branch[Requeue&Resched]; *----------------------------------------------------------- NewOp; ESCEntry[SPP]; * Set Process Priority * priority: Priority ← Pop[]; link: PsbLink; MinimalStack[]; * link ← Fetch[@PDA.block[PSB].link]↑; link.priority ← priority; * Store[@PDA.block[PSB].link]↑ ← link; * Requeue[src: @PDA.ready, dst: @PDA.ready, psb: PSB]; Reschedule[]; *----------------------------------------------------------- T← TIOA&StkP; T← T-1, MemBase← PDA; Fetch← PSB, PD← T; * Know link is word 0 T← DPF[Stack&-1, 3, 14, MD], Branch[SPPMinimalStackErr, ALU#0]; Process← Store← PSB, DBuf← T; DQBRReg← BRConst[RQBR]; * Requeue from ready to ready SQBRReg← BRConst[RQBR], Branch[Requeue&Resched]; SPPMinimalStackErr: Branch[StackError]; *----------------------------------------------------------- NewOp; ESCEntry[DI]; * Disable Interrupts * IF WDC=WdcMax THEN InterruptError[]; WDC ← WDC+1; *----------------------------------------------------------- T← (ID)-1, RBase← RBase[WDC], Branch[AdjustWDC]; * T← 1 *----------------------------------------------------------- NewOp; ESCEntry[EI]; * Enable Interrupts * IF WDC=0 THEN InterruptError[]; WDC ← WDC-1; *----------------------------------------------------------- T← T-T-1, RBase← RBase[WDC]; * T← -1 * Actually, should test for WP#0 only if WDC=0 now, but incrementing WDC * more than once is uncommon, as is having WP#0 just at the moment DI is * executed; and MesaReschedTrap will sort this out anyway. AdjustWDC: PD← WP; * Any interrupt requests in? T← (WDC)+T, Branch[.+2, ALU=0]; PD← T, Reschedule; * Yes, cause a reschedule trap Branch[.+2, ALU<0]; * WDC wrapped around? WDC← T, NextOpcode; * Normal exit T← sInterruptError, Branch[SavePCAndTrap]; * Over-incremented/decremented *----------------------------------------------------------- MesaFault: * Fault handling * Suspend current process and invoke fault handler * Enter: T = FaultVector offset = 2*FaultIndex for fault handler * FaultParam0, FaultParam1 = fault parameters, if any * RBase = MesaRegsMain * Exit: Puts current process on fault queue, awakens handler, and * performs a Reschedule. *----------------------------------------------------------- TopLevel; KnowRBase[MesaRegsMain]; MemBase← LF, XferFlags, Branch[.+2, R<0]; * Branch if xf.invalidContext RestoreStkP; * LF is the truth, not LFShadow! GFShadow← DummyRef← 0S; MemBase← MLBR; LFShadow← VALo; * Requeue[dst: @PDA.fault[fi].queue, src: @PDA.ready, psb: PSB]; RTemp0← T+(PDA.fault); RTemp1← PDAHi; RTemp0← (BRLo← RTemp0)+1; * MLBR← @PDA.fault[fi].queue BRHi← RTemp1; T← PSB, 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 Process← T, MemBase← PDA, Call[Requeue]; * Move PSB 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 Branch[NoMoreInts]; * Go preempt current process *----------------------------------------------------------- MesaReschedTrap: * Reschedule trap *----------------------------------------------------------- TopLevel; DontKnowRBase; * If a broken instruction was being executed, that instruction is now complete, * so reset the Break byte. (A Reschedule is deliberately requested during * dispatch of the broken opcode to cause this to occur.) Break← A0, At[MesaTrapBase, 14]; * 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 a NextOpcode. RBase← RBase[WDC]; PD← WDC, NoReschedule; T← NOT (PCX'), Branch[MesaIntDisabled, ALU#0]; T← WP, PCF← T; * WP#0 means interrupt pending MesaReschedTest: WP← (WP) AND NOT T, * Clear interrupts we will process RestoreStkP, * Needed if came from InterruptLongRunningOpcode Branch[MesaInterrupt, ALU#0]; NextOpcode; * No interrupt, resume execution MesaIntDisabled: T← A0, PCF← T, Branch[MesaReschedTest]; *----------------------------------------------------------- InterruptLongRunningOpcode: * Called when the Reschedule condition is found true during a long-running opcode. * Caller should restore the stack to a valid intermediate state before calling. * Enter: RBase undefined * Exit: If an interrupt is not actually pending, turns off Reschedule and returns; * RBase = MesaRegsMain, T clobbered. * Otherwise, aborts the current instruction, initiates the interrupt, and * does not return. *----------------------------------------------------------- Subroutine; RBase← RBase[WDC]; T← WP, NoReschedule; PD← WDC, Branch[NoIntPending, ALU=0]; * If WP#0 and WDC=0 then allow the interrupt to take. * Note that we do NOT clear the Break byte in this case, since we want it * to be saved as part of the interrupted process state. Branch[.+2, ALU#0]; PD← T, Branch[MesaReschedTest]; Nop; * There is not really any interrupt pending after all. It is likely (but not * guaranteed) that the Reschedule was initiated because the current instruction * is a broken opcode from which we are proceeding. For this eventuality, we * turn off Reschedule and turn on RescheduleNow. This forces a Reschedule trap * to occur at the end of execution of the current opcode; but the RescheduleNow * condition is NOT visible to the Reschedule branch condition. This means that * the current opcode will now run to completion (unless some legitimate interrupt * comes along) and THEN the Reschedule trap will occur to reset the Break byte. NoIntPending: RBase← RBase[MesaRegsMain]; RescheduleNow, Return; *----------------------------------------------------------- MesaInterrupt: * Interrupt handling * Enter: T = interrupt request mask * PSB#0 if interrupted from normal emulation; PSB=0 if from idle loop. * RBase = MesaRegsAlt *----------------------------------------------------------- TopLevel; Nop; * Placement RBase← RBase[MesaRegsMain]; * Test for an interrupt occurring on the 60 hz timer channel (channel 0), * and do CheckForTimeouts when one occurs. The following instruction preserves * the interrupt requests in XferFlags with channel 0 masked off, but puts the * unmasked requests through the ALU. Masking off bit 0 is important because * it corresponds to xf.invalidContext. Claim: if PSB#0 and an interrupt occurs * then the context is always valid; if PSB=0 then xf.invalidContext is irrelevant. * (Reason for using XferFlags is that CheckForTimeouts is terribly short of registers.) XferFlags← LDF[T, 17, 0]; DQBRReg← A0, MemBase← PDA, * No requeue done yet 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 XferFlags is used to shift the * interrupt request bits. * wakeups: PACKED ARRAY InterruptLevel OF BIT ← WP; * FOR level: InterruptLevel DECREASING IN InterruptLevel DO * IF wakeups[level]#0 THEN * requeue ← requeue OR NotifyWakeup[@PDA.interrupt[level].condition]; * ENDLOOP; CheckNakedCVs: RTemp3← Add[PDA.interrupt!, 40]C, * @PDA.interrupt[LAST[InterruptLevel]+1] Branch[CheckNakedCVLoop]; NakedCVWakeup: BRLo← RTemp3, Call[NotifyWakeup]; * Returns with ALU#0 CheckNakedCVLoop: RTemp3← (RTemp3)-(2C), Branch[NoMoreInts, ALU=0]; XferFlags← (XferFlags) RSH 1, Branch[.-1, R even]; MemBase← CVBR; * Notify this CV T← PDAhi; BRHi← T, Branch[NakedCVWakeup]; * All interrupt requests have been processed, and XferFlags is zero. * If we were interrupted from normal emulation (PSB#0), do a Reschedule iff * the interrupt actually caused any processes to be requeued. * If we were interrupted from the idle loop (PSB=0), go reconsider the ReadyQ. NoMoreInts: PD← PSB, MemBase← PDA; RTemp1← T-T-1, DblBranch[MesaReschedule1, IdleReschedule, ALU#0]; *----------------------------------------------------------- CheckForTimeouts: * Check for process timeouts * This is invoked every time an interrupt is requested on the 60 hz timer channel. * Enter: MemBase = PDA * Exit: MemBase = PDA * RBase = MesaRegsMain * Exits to CheckNakedCVs * Clobbers T, Q, Cnt, RTemp0-3, Process, DQBRReg, SQBRReg, TrapParam *----------------------------------------------------------- * Scan PSBs only every third tick RBase← RBase[MesaRegsAlt]; TickCount← (TickCount)-1; T← (Fetch← PDA.count)+1, Branch[.+2, ALU<0]; RBase← RBase[MesaRegsMain], Branch[EndTimeoutScan]; * requeue: BOOLEAN ← FALSE; count: CARDINAL ← Fetch[@PDA.count]↑; * vector: POINTER TO TimeoutVector ← Fetch[@PDA.timeout]↑; * FOR psb: PsbIndex IN [StartPsb..StartPsb+count) DO * timeout: Ticks ← FetchPda[@vector[psb]]↑; * IF timeout#0 AND timeout=PTC THEN { * flags: PsbFlags ← Fetch[@PDA.block[psb].flags]↑; flags.waiting ← FALSE; * Store[@PDA.block[psb].flags]↑ ← flags; StorePda[@vector[psb]]↑ ← 0; * Requeue[src: NIL, dst: @PDA.ready, psb: psb]}; * ENDLOOP; KnowRBase[MesaRegsAlt]; TickCount← Fetch← T, T← MD; * TickCount← 2; fetch PDA.timeout; * know PDA.timeout = PDA.count+1 = 2 Cnt← T; * Count of PSBs involved in scan T← PTC← (PTC)+1, RBase← RBase[MesaRegsMain]; RTemp3← (Sub[StartPsb!, 1]S)+MD+1; * @vector[StartPsb] RTemp3← (Fetch← RTemp3)+1; PD← A0, Q← T; * RTemp3 = address of timeout word; MD = timeout from previous iteration; * TrapParam = ALU = timeout from 2 iterations ago; Q = PTC. * PrincOps deviation: this fetches 2 words beyond the end of the timeout vector. * This might cause a page fault if the end of the vector is page-aligned! TimeoutScanLoop: RTemp3← (Fetch← RTemp3)+1, T← MD, Branch[NonzeroTimeout, ALU#0]; TrapParam← T, Branch[.-1, Cnt#0&-1]; EndTimeoutScan: Branch[CheckNakedCVs]; NonzeroTimeout: PD← (TrapParam)#Q; * vector[psb] = PTC? TrapParam← T, Branch[DoTimeout, ALU=0]; ResumeTimeoutScan: PD← TrapParam, DblBranch[TimeoutScanLoop, EndTimeoutScan, Cnt#0&-1]; * This process has timed out. RTemp3 = @vector[psb+3], where psb is the PsbIndex * of the process which has timed out. DoTimeout: Fetch← PDA.timeout; RTemp3← (RTemp3)-(3C); T← (RTemp3)-MD; * T← psb index of timed-out process T← LSH[T, logSizePSB]; Process← T+(LShift[StartPsb!, logSizePSB]C)+1; * @PDA.block[psb].flags T← A0, Fetch← Process; * Fetch PSB.flags; know PDA.flags = 1 SQBRReg← NOT (DBuf← T), * SQBRReg=-1 => src=NIL Store← RTemp3, T← MD; * vector[psb] ← 0 T← T AND (Not[PSBF.waiting!]C); DQBRReg← BRConst[RQBR]; * dst = ReadyQ Process← (Store← Process)-1, DBuf← T, * Store PSB.flags, fix psb pointer Call[Requeue]; * Now get back in sync with the timeout scan. * RTemp3 = @vector[psb], where psb is the index of the process which just timed out. * TrapParam = vector[psb+1]. Resume by fetching vector[psb+2] and testing vector[psb+1]. RTemp3← (RTemp3)+(2C); RTemp3← (Fetch← RTemp3)+1, Branch[ResumeTimeoutScan]; *----------------------------------------------------------- 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. *----------------------------------------------------------- RTemp1← (ID)-1; * RTemp1← instruction length -1 * Here RTemp1 = -1 if came from MesaInterrupt or MesaFault; = IL-1 if came from * process/monitor opcode. Thus "RTemp1<0" is synonymous with "preemption". * IF ~reschedule THEN RETURN; MesaReschedule1: MemBase← PDA, DQBRReg, Branch[.+2, R<0]; NextOpcode; * No Requeue, resume normal execution *----------------------------------------------------------- * Save state of current process. * Can get here due to a Fault, after requeueing CurrentPSB onto a fault queue. * RTemp1 = -1 if preempted, instruction length -1 otherwise. *----------------------------------------------------------- * link: PsbLink ← Fetch[@PDA.block[PSB].link]↑; * context: PsbContext ← Fetch[@PDA.block[PSB].context]↑; * IF ValidContext[] THEN StoreMds[@LocalBase[LF].pc]↑ ← PC; * IF (link.preempted ← preemption) THEN { * state: StateHandle ← AllocState[link.priority]; SaveStack[state]; * Store[@state.frame]↑ ← LF; Store[@PDA.block[PSB].context]↑ ← state} * ELSE Store[@PDA.block[PSB].context]↑ ← LF; * Store[@PDA.block[PSB].link]↑ ← link; * Validity of context is indicated by XferFlags[xf.invalidContext], which * is checked by SavePCInFrame. Fetch← PSB; * Know PSB.link is word 0 T← MD, RTemp1← (RTemp1)-(PCX'), * PCX or PCX+IL Branch[NotPreemption, R>=0]; * Here to preempt current process. * AllocState: PROCEDURE [priority: Priority] RETURNS [state: StateHandle] = {. * state ← Fetch[@PDA.state[priority]]↑; IF state=NIL THEN ERROR; * Store[@PDA.state[priority]]↑ ← Fetch[state]↑; * Careful not to clobber TrapParam (=FaultParam0) and RTemp3 (=FaultParam1) until * they are stored. * Must call SaveStack before removing StateVector from list, because SaveStack * might generate a StackError trap (during interrupts only, never during faults). RTemp2← T OR (PSBL.preempted); RTemp4← LDF[T, 3, 14]; * Extract link.priority RTemp4← (RTemp4)+(PDA.state); Fetch← RTemp4, * Fetch head of state list Call[SavePCInFrame]; RTemp0← PD← MD, MemBase← PDA; * RTemp0← head StateVector T← (Fetch← RTemp0)+(SV.data), * Fetch successor Branch[NoStateVector, ALU=0]; * Branch if list empty * The following is really part of FaultTwo rather than SaveProcess: * Store[@state.data[0]]↑ ← LowHalf[parameter]; -- = FaultParam0 * Store[@state.data[1]]↑ ← HighHalf[parameter]; -- = FaultParam1 * Note: must not store frame until after SaveStack, because SaveStack may clobber * state.frame if the stack is completely full. T← (Store← T)+1, DBuf← FaultParam0; Store← T, DBuf← FaultParam1, Call[SaveStack]; * Save stack, StkP, Break; doesn't clobber MD; * returns T = @state.frame Store← T, DBuf← LFShadow, T← MD; * state.frame ← LF Store← RTemp4, DBuf← T, * Store successor as head 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 LF as context. NotPreemption: RTemp2← T AND NOT (PSBL.preempted); Call[SavePCInFrame]; * Returns LF in T RTemp0← T, MemBase← PDA; * Together again here. * Now RTemp0 = context (frame or state vector); RTemp2 = updated PSB.link. EndSaveProcess: T← PSB; Store← T, DBuf← RTemp2; T← (PSB)+(PSB.context); Store← T, DBuf← RTemp0; *----------------------------------------------------------- * Check ready queue for new process to run. * Can get here after saving current process (above), or after processing * 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. *----------------------------------------------------------- * link: PsbLink; psb: PsbIndex; queue: Queue; * 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; * psb ← PSB; PC ← savedPC ← 0; LF ← LoadProcess[]; running ← TRUE; * Xfer[dst: LF, src: 0]; * EXITS BusyWait => * {IF ~InterruptsEnabled[] THEN RescheduleError[]; running ← FALSE; RETURN}; IdleReschedule: Fetch← PDA.ready; * Fetch queue RTemp0← Fetch← MD; * Fetch link word of tail PSB T← (IndexMask) AND MD, FreezeBC; FindRunnableProcess: PSB← Fetch← T, Q← T, * Fetch link word of next PSB Branch[BusyWait, ALU=0]; * Branch if queue empty or hit tail T← MD, XferFlags← xf.invalidContext; * Set up for subsequent Xfer T← LDF[T, 3, 14], RTemp2← MD; * T← link.priority RTemp2← T+(PDA.state), * See if StateVector is available Branch[LoadPreempted, R odd]; * Branch if link.preempted=TRUE Fetch← RTemp2, RTemp2← T← B← MD; T← (IndexMask) AND T; * T← next PSB PD← MD; PD← (RTemp0)-Q, * See if PSB = 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: RBase← RBase[MesaRegsAlt]; PD← WDC, RBase← RBase[MesaRegsMain]; T← sRescheduleError, Branch[.+2, ALU=0]; Branch[MTrap]; * It's an error to be idle with WDC#0 Call[InterruptLongRunningOpcode]; * Does not return if an interrupt occurs Branch[.-1]; * Reschedule (cont'd) *----------------------------------------------------------- LoadPreempted: * Run preempted process. * PSB = process to run; RTemp2 = @PDA.state[link.priority]. *----------------------------------------------------------- * This is part of LoadProcess: * state: StateHandle ← Fetch[@PDA.block[PSB].context]↑ * LoadStack[state]; LF ← Fetch[@state.frame]; FreeState[link.priority, state]; T← (PSB)+(PSB.context); RTemp1← (Fetch← T)+1; * PSB.context = StateVector; RTemp1← @PSB.mds RTemp0← MD, Call[LoadStack]; * Load stack, StkP, Break T← (RTemp0)+(SV.frame); * DLink← state.frame Fetch← T, T← RTemp2; * FreeState: PROCEDURE [priority: Priority, state: StateHandle] = { * Store[state]↑ ← Fetch[@PDA.state[priority]]↑; Store[@PDA.state[priority]]↑ ← state}; * RTemp0 = state; RTemp2 = T = @PDA.state[link.priority]. Fetch← T, DLink← MD; Store← T, DBuf← RTemp0, T← MD; Store← RTemp0, DBuf← T, Branch[XferToNewProcess]; *----------------------------------------------------------- LoadNonPreempted: * Run non-preempted process. * PSB = Q = process to run; RTemp2 = PSB.link. *----------------------------------------------------------- * This is part of LoadProcess: * LF ← Fetch[@PDA.block[PSB].context]; * IF link.failed THEN { * Push[FALSE]; link.failed ← FALSE; Store[@PDA.block[psb].link]↑ ← link}; T← (PSB)+(PSB.context); RTemp1← (Fetch← T)+1; * PSB.context = frame; RTemp1← @PSB.mds 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. * mds ← Fetch[@PDA.block[PSB].mds]↑; Xfer[dst: LF, src: NIL]; * DLink = new local frame; RTemp1 = @PSB.mds * Set the new local frame now so that if a fault occurs during the * subsequent Xfer, the correct context is saved. *----------------------------------------------------------- XferToNewProcess: Fetch← RTemp1; T← MD, MemBase← GF, Call[BRHiGetsT]; T← DLink, MemBase← LF; LFShadow← BRLo← T, T← MD, Call[BRHiGetsT]; SLink← A0, MemBase← MDS; BRHi← T, Branch[Xfer]; *----------------------------------------------------------- Pop2MinimalStack: * Prepare 2 long pointer arguments * Pop two long pointers, load base registers, and check for minimal stack. * 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) *----------------------------------------------------------- Subroutine; BRHi← Stack&-1; BRLo← Stack&-3; DQBRReg← T← TIOA&StkP; PD← T, StkP+2, FlipMemBase, Branch[Pop1Tail]; *----------------------------------------------------------- Pop1MinimalStack: * Prepare 1 long pointer argument * Pop one long pointer, load base register, and check for minimal stack. * 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) *----------------------------------------------------------- Subroutine; T← TIOA&StkP, StkP-2; * Reads StkP after modification DQBRReg← T, StkP+2; Pop1Tail: T← BRHi← Stack&-1, Branch[MinimalStackErr, ALU#0]; T← (BRLo← Stack&-1) OR T, Return; * Opcode was not called with minimal stack; generate stack error trap. TopLevel; MinimalStackErr: Branch[StackError]; *----------------------------------------------------------- FetchCPFlags: * Fetch current process's flags. * Enter: MemBase = PDA * Exit: RTemp1 = PSB.flags * T = pointer to PSB.flags * Process = PSB *----------------------------------------------------------- Subroutine; T← (PSB)+1; * Know PSB.flags is word 1 Process← (Fetch← T)-1, Branch[.+2]; *----------------------------------------------------------- FetchCPLink: * Fetch current process's link * Enter: MemBase = PDA * Exit: RTemp1 = PSB.link * T = pointer to PSB.link * Process = PSB *----------------------------------------------------------- Subroutine; T← Process← Fetch← PSB; * Know that link is word 0 RTemp1← MD, Return; *----------------------------------------------------------- TestMonitorLock: * Test and set monitor lock * Enter: current BR = @monitor * MD = monitor * Exit: if monitor is locked, goes to EnterFailed; otherwise: * RTemp1 = Q = monitor 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; *----------------------------------------------------------- EnterFailed: * Handle failure to enter monitor * EnterFailed: PROCEDURE [m: LONG POINTER TO monitor] = { * link: PsbLink ← Fetch[@PDA.block[PSB].link]↑; link.failed ← TRUE; * Store[@PDA.block[PSB].link]↑ ← link; * Requeue[src: @PDA.ready, dst: m, psb: PSB]; Reschedule[]}; * Enter: MLBR points to monitor lock * Exit: Does not return but rather goes directly to MesaReschedule *----------------------------------------------------------- TopLevel; 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 = PSB Branch[Requeue&Resched]; *----------------------------------------------------------- ExitMonitor: * Exit from monitor * Unlocks monitor, and moves process at head of monitor lock queue to * ready queue, if there is one. * ExitMonitor: PROCEDURE [m: LONG POINTER TO Monitor] RETURNS [requeue: BOOLEAN] = { * mon: Monitor ← Fetch[m]↑; mon.locked ← FALSE; Store[m]↑ ← mon; * IF requeue ← (mon.tail # PsbNull) THEN { * link: PsbLink ← Fetch[@PDA.block[mon.tail].link]↑; * Requeue[src: m, dst: @PDA.ready, psb: link.next]}}; * Enter: MLBR = long pointer to monitor * MemBase = MLBR * MD = monitor * Clobbers T, RTemp0, RTemp1, RTemp2, Process, MemBase *----------------------------------------------------------- Subroutine; RTemp1← MD, T← (IndexMask) AND MD; RTemp1← (RTemp1) AND NOT (ML.locked), Branch[.+2, ALU#0]; Store← 0S, DBuf← RTemp1, Return; * Queue is empty * Move head process to ready queue. Store← 0S, DBuf← RTemp1; MemBase← PDA; Fetch← T, DQBRReg← BRConst[RQBR]; * Fetch link of tail PSB; dst = ReadyQ SQBRReg← BRConst[MLBR]; * src = ML T← (IndexMask) AND MD; Process← Fetch← T, Branch[Requeue1]; * Requeue and return *----------------------------------------------------------- NotifyWakeup: * Perform naked notify * 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. * NotifyWakeup: PROCEDURE [c: LONG POINTER TO Condition] RETURNS [requeue: BOOLEAN] = { * cond: Condition; requeue: BOOLEAN ← FALSE; * CleanupCondition[c]; * IF (cond ← Fetch[c]↑).tail = PsbNull THEN {cond.wakeup ← TRUE; Store[c]↑ ← cond} * ELSE {WakeHead[c]; requeue ← TRUE}}; * Enter: CVBR = pointer to CV * Exit: ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg, MemBase *----------------------------------------------------------- Subroutine; RTemp2← Link, Global; TopLevel; 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 and return Link← RTemp2; * CV queue empty, set wakeup waiting bit Subroutine; Store← 0S, PD← DBuf← T, Return; *----------------------------------------------------------- WakeHead: * Awaken head process on CV queue * Wakes the process at the head of the CV queue (there must be one). * WakeHead: PROCEDURE [c: LONG POINTER TO Condition] = { * vector: POINTER TO TimeoutVector ← Fetch[@PDA.timeout]↑; * cond: Condition ← Fetch[c]↑; link ← Fetch[@PDA.block[cond.tail].link]↑; * flags ← Fetch[@PDA.block[link.next].flags]↑; flags.waiting ← FALSE; * Store[@PDA.block[link.next].flags]↑ ← flags; * Store[@vector[link.next]]↑ ← 0; * Requeue[src: c, dst: @PDA.ready, psb: link.next]}; * Enter: CVBR = pointer to CV * Process = CV.tail * Exit: MemBase = PDA * ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg *----------------------------------------------------------- Subroutine; T← Process, MemBase← PDA; Fetch← T, * PSB.link is word 0 DQBRReg← BRConst[RQBR]; * dst = ReadyQ Fetch← PDA.timeout, T← MD; Process← (IndexMask) AND T; * Process← successor of tail T← RSH[Process, 2]; * Convert to PsbIndex RTemp0← T+MD; * RTemp0← @vector[psb] T← (Process)+1; * Know PSB.flags=1 Fetch← T, SQBRReg← BRConst[CVBR]; * src = CV Store← RTemp0, DBuf← 0C, RTemp0← MD; * vector[psb] ← 0; RTemp0← flags RTemp0← (RTemp0) AND NOT (PSBF.waiting); Store← T, DBuf← RTemp0; Fetch← Process, Branch[Requeue1]; * Call Requeue and return *----------------------------------------------------------- Requeue: * Move process from one queue to another * 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 * Exit: MemBase = PDA * ALU#0 (for benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2; resets ShC to StdShC *----------------------------------------------------------- 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 { * 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}; 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 { * flags ← Fetch[@PDA.block[psb].flags]↑; flags.cleanup ← link.next; * Store[@PDA.block[psb].flags]↑ ← flags} * ELSE IF queue.tail = psb THEN{queue.tail ← prev; Store[src]↑ ← queue}; 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 { * link.next ← psb; Store[@PDA.block[psb].link]↑ ← link; * queue.tail ← psb; Store[dst]↑ ← queue} ... 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 { * prev ← queue.tail; temp ← Fetch[@PDA.block[prev].link]↑; * IF temp.priority >= link.priority THEN {queue.tail ← psb; Store[dst]↑ ← queue} ... 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}; 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← StdShC; T← ShMDBothMasks[RTemp0], ShC← T; PD← Store← Process, DBuf← T, Return; * ALU#0 *----------------------------------------------------------- CleanUpCVQueue: * Clean up condition queue * 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. * 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. * DO IF flags.cleanup = psb THEN { * cond.wakeup ← FALSE; cond.tail ← PsbNull; Store[c]↑ ← cond; RETURN}; * 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}}; T← Process; StoreCVQueue: RTemp0← (RTemp0) OR T, MemBase← CVBR; Store← 0S, DBuf← RTemp0, Branch[CleanupCVReturn];