*----------------------------------------------------------- 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];