//------------------------------------------------------------------------------ // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // This file contains an implementation of processor dispatcher. There is one processor // Dispatcher per processor //------------------------------------------------------------------------------ #if DEBUG #define SHOW_TWIDDLE #endif using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.GCs; using System.Threading; using Microsoft.Singularity; using Microsoft.Singularity.Hal; using Microsoft.Singularity.Io; using Microsoft.Singularity.Isal; namespace Microsoft.Singularity.Scheduling { /// /// /// A Processor Dispatcher is responsible for handling processor runnable queue, /// timer and i/o interrupts as well as forwarding a dispatching event to a proper scheduler /// if required /// /// /// [CLSCompliant(false)] [AccessedByRuntime("referenced from halidt.asm")] public class ProcessorDispatcher { /// /// /// Initialize processor dispatcher /// /// /// Scheduler to use /// [NoHeapAllocation] public static void StaticInitialize(Scheduler schedulerToUse) { // Initialize scheduler scheduler = schedulerToUse; } /// /// /// Constructor /// /// internal ProcessorDispatcher() { this.id = Interlocked.Increment(ref numberOfDispatchers) - 1; #if SHOW_TWIDDLE this.color = (ushort)((this.id == 0) ? 0x3a00 : 0x2a00); #endif // Create stopped thread queue for processing stopped thread this.stoppedThreadQueue = new ThreadQueue(null); // Create scavanged thread queue for processing stopped thread that ready to be cleaned up this.scavengerThreadQueue = new ThreadQueue(null); } /// /// /// First part of the initialization of processor dispatcher /// /// /// Processor dispatcher belongs to /// public void Initialize(Processor myProcessor) { // TODO: This is the wrong place for this. InitializeTwiddle(); // Mark this dispatcher as active this.isDispatcherInUse = true; // Initalize procssor this.myProcessor = myProcessor; // Create an idle thread for this processor this.idleThread = Thread.CreateIdleThread(this.myProcessor); // Create a scavenger thread this.scavengerThread = Thread.CreateIdleThread(this.myProcessor); // Notify scheduler about new dispatcher scheduler.OnDispatcherInitialize(myProcessor.Id); } /// /// /// Finalize method /// /// public void Finalize() { } /// /// /// Check if thread index maps to idle thread /// /// /// Thread index to check if it is actually idle or not /// [Inline] [NoHeapAllocation] public static bool IsIdleThread(int threadIndex) { // Map index to thread Thread thread = Thread.threadTable[threadIndex]; // Check if thread is indeed idle thread return IsIdleThread(thread); } /// /// /// Check if thread is an idle thread /// /// /// Thread to check if it is idle /// [Inline] [NoHeapAllocation] internal static bool IsIdleThread(Thread thread) { return (thread != null) ? thread.IsIdle : false; } /// /// /// Check if thread is an idle thread /// /// /// Thread to check if it is idle /// [Inline] [NoHeapAllocation] internal bool IsMyIdleThread(Thread thread) { return thread == this.idleThread; } /// /// /// Check if thread is one of a dispatcher's threads /// /// /// Thread to check if it is idle /// [Inline] [NoHeapAllocation] internal bool IsOneOfMyDispatcherThreads(Thread thread) { return thread == this.scavengerThread || thread == this.idleThread; } /// /// /// Handle I/O interrupt and perform context switch if required /// /// [NoHeapAllocation] public void HandleIOReschedule() { Thread currentThread = Processor.GetCurrentThread(); SchedulerTime currentTime = SchedulerTime.Now; // Validate preconditions ValidateInvariantsOnInterruptEnter(currentThread); // Let scheduler decide if we should run different thread RunScheduler(currentThread, ThreadState.Running, currentTime); // Validate post conditions ValidateInvariantsOnInterruptExit(); } /// /// /// Handle timer interrupt and perform context switch if required /// /// /// /// Interrupts disabled /// Thread is not running from processor point of view /// /// [NoHeapAllocation] public void HandlePreemptionReschedule(HalTimer timer) { Thread currentThread = Thread.CurrentThread; SchedulerTime currentTime = SchedulerTime.Now; // Validate preconditions ValidateInvariantsOnInterruptEnter(currentThread); // Twiddle the thread spinner. Twiddle(); // Process timer interrupt if (isDispatcherInUse) { TimeSpan delta = timer.MaxInterruptInterval; if (!isActingForEntireSystem) { // This is the normal case, the processor is in use, notify // the scheduler about timer interrupt on this processor delta = NotifyTimerInterrupt(myProcessor.Id, currentTime); } else { // This happens only during system shutdown where the boot processor // simulates all processors, we need to notify scheduler about the // timer interrupt on all virtual processors. for (int id = 0; id < Processor.processorTable.Length; id++) { TimeSpan next = NotifyTimerInterrupt(id, currentTime); if (delta > next) { delta = next; } } } TimeSpan start = delta; if (delta < timer.MinInterruptInterval) { delta = timer.MinInterruptInterval; } if (delta > timer.MaxInterruptInterval) { delta = timer.MaxInterruptInterval; } #if false DebugStub.WriteLine("-- NextTimer(delta={0}, start={1} [min={2},max={3})", __arglist(delta.Ticks, start.Ticks, timer.MinInterruptInterval.Ticks, timer.MaxInterruptInterval.Ticks)); #endif timer.SetNextInterrupt(delta); } // Let scheduler decide if we should run different thread RunScheduler(currentThread, ThreadState.Running, currentTime); // Validate post conditions ValidateInvariantsOnInterruptExit(); } /// /// /// Actual implementation of notifying the scheduler about the timer interrupt /// /// /// Id of the processor, correspond to affinity of the threads. /// Note during system shutdown this is virtualized. /// [NoHeapAllocation] private TimeSpan NotifyTimerInterrupt(int processorId, SchedulerTime now) { // We need to acquire scheduler lock SchedulerLock schedulerLock = scheduler.RetrieveSchedulerLock(processorId); schedulerLock.Acquire(); // No need to acquire scheduler lock - timer has its own lock TimeSpan delta = scheduler.OnTimerInterrupt(processorId, now); // We are done with this scheduler release its lock schedulerLock.Release(); return delta; } /// /// /// Add runnable thread to runnable queue /// /// /// Thread to add to runnable queue /// /// /// During this operation we have to keep interrupts disabled even though we might /// be adding a thread to different dispatcher - the problem is if we enable interrupts we /// might get reschedule to the dispatcher which lock we currently holding - that will /// cause definite deadlock /// /// [NoHeapAllocation] public static void AddRunnableThread(Thread threadToAdd) { // At this point we need to disable interrupts and acquire dispatcher lock: bool shouldEnable = Processor.DisableInterrupts(); ProcessorDispatcher dispatcher = Processor.CurrentProcessor.Dispatcher; Thread currentThread = Thread.CurrentThread; int affinity; int dispatcherIdxToActivate; Scheduler scheduler = Kernel.TheScheduler; SchedulerLock schedulerLock = scheduler.RetrieveSchedulerLock(threadToAdd.Affinity); // Validate pre conditions dispatcher.ValidateInvariantsOnAddRunnableThreadEnter(currentThread, threadToAdd); // Acquire scheduler lock schedulerLock.Acquire(currentThread); // We will use thread's affinity to find dispatcher to signal affinity = threadToAdd.Affinity; // We have protected runnable queue - now we need to call back into scheduler // to add thread to runnable queue scheduler.AddRunnableThread(threadToAdd); // Release scheduler lock schedulerLock.Release(currentThread); // Make sure that appropriate dispatchers are active if (affinity == (int)Affinity.All) { // Calculate next dispatcher to activate dispatcherIdxToActivate = GetNextDispatcherToActivate(); } else { dispatcherIdxToActivate = affinity; } // If we are running on a dispatcher we have been asked to activate don't do anything if (dispatcherIdxToActivate != dispatcher.myProcessor.Id) { ProcessorDispatcher dispatcherToActivate = Processor.processorTable[dispatcherIdxToActivate].Dispatcher; // During MP boot there could be race condition where the dispatcher is // not initialized. Activate it only if it's initialized. if (dispatcherToActivate != null) { // During MP system shutdown, if the dispatcher is not in use, then // activate the boot processor instead if (dispatcherToActivate.isDispatcherInUse) { dispatcherToActivate.ActivateDispatcher(); } else { Processor.processorTable[0].Dispatcher.ActivateDispatcher(); } } } // Validate post conditions: For now we can't call method when current thread is // the same as thread to add: It is possible that they are the same only when // the method is called from processor shutdown. if (currentThread != threadToAdd) { dispatcher.ValidateInvariantsOnAddRunnableThreadExit(currentThread, threadToAdd); } // Reenable interrupts Processor.RestoreInterrupts(shouldEnable); } /// /// /// Switch to the new thread context. /// /// [NoHeapAllocation] public static void SwitchContext(ThreadState schedulerAction) { // At this point we need to disable interrupts and acquire dispatcher lock: bool shouldEnable = Processor.DisableInterrupts(); ProcessorDispatcher dispatcher = Processor.CurrentProcessor.Dispatcher; // Retrieve current thread Thread currentThread = Thread.CurrentThread; // Validate pre conditions dispatcher.ValidateInvariantsOnSwitchContexEnter(currentThread); // We have protected runnable queue - now we need to call back into scheduler // Let scheduler decide if we should run different thread dispatcher.RunScheduler(currentThread, schedulerAction, SchedulerTime.MinValue); // STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! // DON'T TRY TO ACCESS DISPATCHERS AT THIS POINTER - WE CAN BE RETURNING // ON DIFFERENT. DISPATCHER LOCKS SHOULD BE RELEASED BY NOW! // Validate post conditions Processor.CurrentProcessor.Dispatcher.ValidateInvariantsOnSwitchContextExit(); Processor.RestoreInterrupts(shouldEnable); } /// /// /// Shutdown this processor as part of system shutdown. All running and runnable /// threads on this processor will be run on the boot processor after this. /// /// This must NOT be called on the boot processor. /// /// To avoid race conditions, the shutdown was done by letting the boot processor /// work with the scheduler to run all runnable threads on the system. The scheduler /// doesn't have any knowledge of the shutdown, ProcessorDispatcher takes care of all /// the necessary plumbing. /// /// See also comments on data members: isDispatcherInUse and isActingForEntireSystem. /// /// /// Thread currently running /// /// This method should be called with interrupt disabled /// [NoHeapAllocation] public void OnProcessorShutdown(Thread currentThread) { Scheduler scheduler; // Validate preconditions ValidateInvariantsOnInterruptEnter(currentThread); // This method must not be called by the boot processor VTable.Assert(myProcessor.Id != 0); // Turn off this processor dispatcher. Once isDispatcherInUse is false, // all interrupts and SwitchContext calls will go directly to idle loop // and halt the processor this.isDispatcherInUse = false; // Add the current running thread to its scheduler's runnable queue, // do so only if it's not idle or scavenger thread if (!IsOneOfMyDispatcherThreads(currentThread)) { AddRunnableThread(currentThread); } // Decrement the number of active dispatchers int currentNumberOfDispatchers = Interlocked.Decrement(ref numberOfDispatchers); if (currentNumberOfDispatchers == 1) { // If there is only a single dispatcher activate, let the boot processor know // that it should now act for the entire system. Processor.processorTable[0].Dispatcher.isActingForEntireSystem = true; // The boot processor may be halted, if so wake it up. Processor.processorTable[0].Dispatcher.ActivateDispatcher(); } } /// /// /// Retrieve Kernel scheduler /// /// public Processor Processor { [NoHeapAllocation] get { return this.myProcessor; } } /// /// /// Add threadToAdd to its scheduler's runnable queue if it's in the right state. /// It's possible that the thread is ready to be blocked, in which case this /// function call will change the thread's state to blocked and it will not be /// added to the runnable queue. /// /// /// Thread to add to runnable queue /// The state of the thread /// [NoHeapAllocation] private static void AddRunnableThreadIfRunnable( Thread threadToAdd, ThreadState schedulerAction) { ThreadState newState = ThreadState.Undefined; if (threadToAdd.ChangeSchedulerState(schedulerAction) == ThreadState.Runnable) { AddRunnableThread(threadToAdd); } } /// /// /// Run the scheduler to find a thread to run. /// /// /// Thread that was previously running /// Whether the previously running thread is blocked /// Current wall clock time. /// The thread to run next, null if there is no thread to run /// [NoHeapAllocation] private Thread FindSchedulerThreadToRun( Thread schedulerThread, ThreadState schedulerAction, SchedulerTime currentTime) { Thread threadSwitchTo = null; // Assert preconditions: At this point dispatcher has to be in use otherwise we in trouble VTable.Assert(this.isDispatcherInUse); // We need to consider if we are acting on behalf of all processors, so that in the process // of being shutdown we don't starve other schedulers if (!this.isActingForEntireSystem) { // We are running normally just try to find a thread to run threadSwitchTo = FindSchedulerThreadToRunInternal( schedulerThread, schedulerAction, myProcessor.Id, currentTime); } else { // This happens only during system shutdown where the boot processor needs // to simulates all processors. We need to walk all virtual processors // in round robin fashion to find a thread to run. Once the next thread is // found, this function returns, and our virtual processor position is kept // in nextVirtualProcessorId. for (int i = 0; i < Processor.processorTable.Length; i++) { // simulate next processor, wrap around if we walk across the boundary nextVirtualProcessorId++; if (nextVirtualProcessorId == Processor.processorTable.Length) { nextVirtualProcessorId = 0; } // Now call FindSchedulerThreadToRunInternal to find a thread to run for // the virtual processor. Note that we can must pass in the current // running thread exactly once. Thread threadToPassIn = i == 0 ? schedulerThread : null; if (threadToPassIn != null) { threadToPassIn.Affinity = nextVirtualProcessorId; } threadSwitchTo = FindSchedulerThreadToRunInternal( threadToPassIn, schedulerAction, nextVirtualProcessorId, currentTime); if (threadSwitchTo != null) { break; } } } return threadSwitchTo; } /// /// /// Actual implementation to run the scheduler to find a thread to run. /// /// /// Thread that was previously running /// Whether the previously running thread is blocked /// Id of the processor, correspond to affinity of the threads. /// Note during system shutdown this is virtualized. /// Current wall clock time. /// The thread to run next, null if there is no thread to run /// [NoHeapAllocation] private Thread FindSchedulerThreadToRunInternal( Thread schedulerThread, ThreadState schedulerAction, int processorId, SchedulerTime currentTime) { Thread threadSwitchTo = null; // Get to the scheduler lock and save it in dispatcher - we will be releasing it // latter on as a part of context switch - SchedulerLock schedulerLock = scheduler.RetrieveSchedulerLock(processorId); // currentThread can be null so use implicit version to lock dispatcher schedulerLock.Acquire(); // Run Scheduling policy threadSwitchTo = scheduler.RunPolicy( processorId, schedulerThread, schedulerAction, currentTime); // We are done working with this scheduler schedulerLock.Release(); return threadSwitchTo; } /// /// /// Run schedulers Helper method finds actual threads to run /// /// /// If currentThread is idle or scavenger, then /// schedulerThread is null, otherwise it's same as currentThread /// State of the currentThread /// Current wall clock time. /// [NoHeapAllocation] private Thread ChooseThreadOrScavengerToRun( Thread schedulerThread, ThreadState schedulerAction, SchedulerTime currentTime) { Thread threadSwitchTo = null; // Assert preconditions VTable.Assert(this.isDispatcherInUse); // Run scheduling policies and find thread to run // Do so only if the dispatcher is in use do { // Run the scavenger if the stopped queue isn't empty. if (!this.stoppedThreadQueue.IsEmpty()) { threadSwitchTo = this.scavengerThread; } else { // Find a thread from schedulers to run threadSwitchTo = FindSchedulerThreadToRun(schedulerThread, schedulerAction, currentTime); // We no longer need current scheduler's thread schedulerThread = null; // If we found a thread to run we still need to check if we // need to stop it right a way if (threadSwitchTo != null) { // Check if thread has to be stopped then stop it and get another one if (!threadSwitchTo.ShouldStop()) { // We found thread for running break; } else { // Enqueue stopped thread EnqueueStoppedThread(threadSwitchTo); // Need to retry once more threadSwitchTo = null; } } else { // Well we don't have any thread to run just quit break; } } } while (threadSwitchTo == null); return threadSwitchTo; } /// /// /// Run scheduler and retrieve a thread to run /// /// /// The current running thread /// State of the currentThread /// Current wall clock time. /// [NoHeapAllocation] private void RunScheduler( Thread currentThread, ThreadState schedulerAction, SchedulerTime currentTime) { Thread threadSwitchTo = null; // Check if we are currently running idle thread Thread schedulerThread = IsOneOfMyDispatcherThreads(currentThread) ? null : currentThread; // At this point current thread shouldn't be inside of context swictch - we don't allow // reentrancy and dispatcher has to be in use VTable.Assert(!currentThread.IsInsideOfContextSwitch()); VTable.Assert(this.isDispatcherInUse); // Before we proceed any further mark a thread as to be inside of context switch // When calling into the scheduler it is possible that it will put current thread into // runnable queue so that other dispatcher can pick it up. By marking the thread // we avoid possible race condition currentThread.TurnOnInsideOfContextSwitch(); // Check if we should stop current thread if (schedulerThread != null && (schedulerAction == ThreadState.Stopped || schedulerThread.ShouldStop())) { // Enqueue stopped thread EnqueueStoppedThread(schedulerThread); // We no longer need current thread schedulerThread = null; //Don't forget to mark state schedulerAction = ThreadState.Stopped; } // Run scheduler until we either have a thread to run, or choose // scavenger thread or go idle do { // Mark ourselves active before we try to find a thread this.MarkActive(); // Run scheduling policies to find out thread to run threadSwitchTo = ChooseThreadOrScavengerToRun(schedulerThread, schedulerAction, currentTime); // We can no longer use schedulerThread schedulerThread = null; // If we found a thread to run do special work to accomdate scavenger thread if (threadSwitchTo != null) { // If we are about to run scavenger try to transfer stopped threads if (threadSwitchTo == this.scavengerThread) { // Transfer threads only if scavenger is done with a previous set of stopped // threads - it is no longer running. Otherwise we in danger of scavenger being // context switched out and its list being in inconsistent state if (!this.isScavengerRunning) { // Stopped thread queue can't be empty VTable.Assert (!this.stoppedThreadQueue.IsEmpty()); // Transfer queue of stopped threads this.stoppedThreadQueue.DequeueAll(this.scavengerThreadQueue); this.stoppedThreadQueueLength = 0; } // Don't forget to indicate that scavenger thread is active this.isScavengerRunning = true; } } else { // Try to mark ourselves idle if we fail we have to retry // run scheduler: one of the scheudulers now has a thread // to run if (this.TryMarkIdle()) { // No choice left now but the idle thread threadSwitchTo = idleThread; } } } while(threadSwitchTo == null); // Update the twiddle display. #if SHOW_TWIDDLE if (threadSwitchTo != idleThread) { DisplayThread(color, threadSwitchTo.threadIndex); } Twiddle(); #endif // Perform context switch: Thread contex swich releases dispatcher lock SwitchThreadContextInternal(currentThread, threadSwitchTo); // STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! // DON'T TRY TO ACCESS THIS POINTER - WE CAN BE RETURNING ON DIFFERENT // DISPATCHER. DISPATCHER LOCKS SHOULD BE RELEASED BY NOW! } /// /// /// Switch to the new thread context. This works differently depending on /// whether we are in interrupt mode or not. /// /// /// Thread we are currently executing on /// Thread switch to /// [NoHeapAllocation] private void SwitchThreadContextInternal(Thread currentThread, Thread threadSwitchTo) { // Assert preconditions: currentThread and thread on the processor should be the same VTable.Assert(currentThread == Thread.CurrentThread); // If current thread is not the same as we are switching to: Proceed with the context // switch. if (threadSwitchTo != currentThread) { // Update statistic UpdateStats (currentThread, threadSwitchTo); // Bind dispatcher to thread and thread to dispatcher this.runningThread = threadSwitchTo; threadSwitchTo.Dispatcher = this; if (currentThread.context.IsRunning()) { // We are running outside of interrupt context. We cannot mark current thread // as exited context switch because we are still inside of context switch // SwitchTo will mark thread as outside of context switch // Life is good ... SwitchTo(threadSwitchTo); // STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! STOP! // DON'T ADD ANY CODE HERE, AS WE COULD BE RETURNING ON DIFFERENT DISPATCHER return; } else { // Move over to the new thread context TransferToThreadContext(ref currentThread.context, ref threadSwitchTo.context); } } else { // If we are switching to the same thread don't forget to turn off inside of context // switch bit currentThread.TurnOffInsideOfContextSwitch(); } // Assert post conditions: current thread of the processor shouldn't be inside of context switch... VTable.Assert(!threadSwitchTo.IsInsideOfContextSwitch()); } /// /// /// Transfer control from the current to a new thread context, obeying the /// synchronization requirements /// /// [NoHeapAllocation] internal unsafe static void TransferToThreadContext(ref ThreadContext from, ref ThreadContext to) { // NOTE: As soon as we call TurnOffInsideOfContextSwitch, // we will have no valid current thread to point to; the old thread may // be scheduled on another processor, and the new thread may not be ready to run yet. // As a stopgap, we set the current target thread record to be the cpu's boot thread // record. This will at least ensure that stack limit checks work. Note // that we are still in a dangerous situation because of many places in the code // where it casts the Isal.ThreadRecord to a ThreadContext, which will not be valid. Isa.SetCurrentThread(ref Isa.GetCurrentCpu()->bootThread); // Mark current thread that it is ready to be picked up for // running from.thread.TurnOffInsideOfContextSwitch(); // Wait until our thread is ready to be swapped in to.thread.WaitUntilReadyForContextSwitch(); // Set the current thread to our new context Isa.SetCurrentThread(ref to.threadRecord); } /// /// /// Switch processor to this thread /// /// [NoHeapAllocation] private void SwitchTo(Thread threadSwitchTo) { Thread currentThread = Thread.CurrentThread; bool needGC; // Upate processor statistics Processor.CurrentProcessor.NumContextSwitches++; Monitoring.Log(Monitoring.Provider.Thread, (ushort)ThreadEvent.SwitchTo, 0, (uint)threadSwitchTo.context.threadIndex, 0, 0, 0, 0); unsafe { needGC = Transitions.InMutatorState(Processor.GetCurrentThreadContext()); } if (needGC) { Processor.SwitchToThreadContext(ref currentThread.context, ref threadSwitchTo.context); } else { Processor.SwitchToThreadContextNoGC(ref threadSwitchTo.context); } } /// /// /// Try to mark dispather as idle we only can do it if we still in context switch state /// /// [NoHeapAllocation] public bool TryMarkIdle() { // Assert preconditions: VTable.Assert(this == Processor.CurrentProcessor.Dispatcher); return (int)State.Active == Interlocked.CompareExchange (ref this.currentState, (int)State.Idle, (int)State.Active); } /// /// /// Mark dispather as active - should be always called from the thread running on /// dispatcher /// /// [NoHeapAllocation] public int MarkActive() { return (int)Interlocked.Exchange(ref this.currentState, (int)State.Active); } /// /// /// Try to mark dispatcher as active with request - should be always called from the thread running on /// dispatcher /// /// [NoHeapAllocation] public int MarkActiveWithRequest() { return Interlocked.Exchange(ref this.currentState, (int)State.ActiveWithRequests); } /// /// /// Calculate next disaptcher to activate /// /// [NoHeapAllocation] public static int GetNextDispatcherToActivate() { int newIdx; int oldIdx; do { oldIdx = nextDispatcherToActivate; newIdx = oldIdx; if (++newIdx == numberOfDispatchers) { newIdx = 0; } // Activate disaptcher first } while ( oldIdx != Interlocked.CompareExchange(ref nextDispatcherToActivate, newIdx, oldIdx)); return newIdx; } /// /// /// Activate disaptcher - if it is idle send IPI /// /// [NoHeapAllocation] private void ActivateDispatcher() { // We only want to send IPI if previous dispatcher's state is idle if (MarkActiveWithRequest() == (int)State.Idle) { this.myProcessor.ActivateDispatcher(); } } /// /// /// Ilde thread loop /// /// [NoHeapAllocation] public static void IdleThreadLoop() { while (true) { // Check for a debug break. bool iflag = Processor.DisableInterrupts(); try { Processor.CurrentProcessor.NextSampleIsIdle(); // Check if we need to break into debugger if (DebugStub.PollForBreak()) { DebugStub.Print("Debugger breakin.\n"); DebugStub.Break(); } } finally { //Kernel.Waypoint(114); Processor.RestoreInterrupts(iflag); } // Halt processor until is interrupted Processor processor = Processor.CurrentProcessor; uint statusQuo = processor.NumInterrupts; while (processor.NumInterrupts == statusQuo) { // HaltUntilInterrupt may be a nop Processor.HaltUntilInterrupt(); } } } /// /// /// Scavenger thread loop /// /// public static void ScavengerThreadLoop() { ThreadEntry threadEntry; ProcessorDispatcher dispatcher = Processor.CurrentProcessor.Dispatcher; while (true) { // For now disable interrupts: Service thread has a lock that might // be problematic... bool iflag = Processor.DisableInterrupts(); // Now just go through all threads and service their stop request while ((threadEntry = dispatcher.scavengerThreadQueue.DequeueHead()) != null) { // It is possible that stopping thread is still in a process of running - wait // until it stops--ready for context switch--and then stop it threadEntry.Thread.WaitUntilReadyForContextSwitch(); // Process stopped call and go to the next one ThreadLocalServiceRequest.ThreadStopped(threadEntry.Thread); // Poll for possible debug break: if (DebugStub.PollForBreak()) { DebugStub.Print("Debugger breakin.\n"); DebugStub.Break(); } } // Reenable interrupts Processor.RestoreInterrupts(iflag); // We are done in this round. Let's see if there is more we have to do: we will be // rescheduled by dispather if there is nobody to run and there are more threads // to clean up dispatcher.isScavengerRunning = false; SwitchContext(ThreadState.Running); } } /// /// /// Enqueue stopped thread /// /// [NoHeapAllocation] private void EnqueueStoppedThread(Thread currentThread) { // Change current state to stopped state currentThread.ChangeSchedulerState(ThreadState.Stopped); // At this point thread can't be on any queue VTable.Assert(!currentThread.schedulerEntry.Enqueued); // Put it on a stopped queue this.stoppedThreadQueue.EnqueueHead(currentThread.schedulerEntry); // Update queue length this.stoppedThreadQueueLength++; } /// /// /// Validated common invariants that should hold true for every entry point /// to dispatcher, except for addrunnable thred /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateCommonInvariantsOnEnter(Thread currentThread) { // Validate processor invariants // Interrupts have to be disabled VTable.Assert(Processor.InterruptsDisabled()); // Validate dispatcher invariants // Thread should belong to this dispatcher or if thread is a main kernel thread it can be null VTable.Assert(currentThread.Dispatcher == this || currentThread.Dispatcher == null); // Validate thread invariants: // Thread can't be inside of context switch: VTable.Assert(!currentThread.IsInsideOfContextSwitch()); } /// /// /// Validated common invariant that should hold true for every exit point /// from dispatcher, except for addrunnable thred /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateCommonInvariantsOnExit(Thread currentThread) { // Validate processor invariants // Interrupts have to be disabled VTable.Assert(Processor.InterruptsDisabled()); // Validate dispatcher invariants // Thread should belong to this dispatcher or if thread is a first thread it might not // be bound to dispatcher even after interrupt, this can happen when after interrupt // thread picks up itself. VTable.Assert(currentThread.Dispatcher == this || currentThread.Dispatcher == null); // Validate thread invariants: // Thread can't be inside of context switch: VTable.Assert(!currentThread.IsInsideOfContextSwitch()); } /// /// /// Validate invariants that should hold true for every entry point /// to dispatcher on interrupt /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateInvariantsOnInterruptEnter(Thread currentThread) { // Validate common invariants ValidateCommonInvariantsOnEnter(currentThread); // Validate processor invariants // We should be on interrupt stack VTable.Assert(Isa.IsRunningOnInterruptStack); // Processor should be inside of interrupt context VTable.Assert(Processor.InInterruptContext); // Validate threads invariants // Thread's context can't be running from thread's context point of view VTable.Assert(!currentThread.context.IsRunning()); } /// /// /// Validate invariants that should hold true for every exit point /// from dispatcher's interrupt handler /// /// [NoHeapAllocation] private void ValidateInvariantsOnInterruptExit() { Thread currentThread = Thread.CurrentThread; // Validate common invariants ValidateCommonInvariantsOnExit(currentThread); // Validate processor invariants // We should be on interrupt stack VTable.Assert(Isa.IsRunningOnInterruptStack); // Processor should be inside of interrupt context VTable.Assert(Processor.InInterruptContext); // Validate threads invariants // Thread's context can't be running from thread's context point of view VTable.Assert(!currentThread.context.IsRunning()); } /// /// /// Validate invariants that should hold true for entry point /// to dispatcher through SwitchContext call /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateInvariantsOnSwitchContexEnter(Thread currentThread) { // Validate common invariants ValidateCommonInvariantsOnEnter(currentThread); // Thread's context should be running from thread's context point of view VTable.Assert(currentThread.context.IsRunning()); } /// /// /// Validate invariants that should hold true for exit point /// from dispatcher's SwitchContext call /// /// [NoHeapAllocation] private void ValidateInvariantsOnSwitchContextExit() { Thread currentThread = Thread.CurrentThread; // Validate common invariants ValidateCommonInvariantsOnExit(currentThread); // Thread's context should be running from thread's context point of view VTable.Assert(currentThread.context.IsRunning()); } /// /// /// Validate invariants that should hold true for entry point /// to dispatcher through AddRunnableThread call /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateInvariantsOnAddRunnableThreadEnter( Thread currentThread, Thread newThread) { // Validate processor invariants: // Interrupts have to be disabled VTable.Assert(Processor.InterruptsDisabled()); // Validate dispatcher invariants // Thread should belong to this dispatcher or if thread is a kernel thread it can be null VTable.Assert(currentThread.Dispatcher == this || currentThread.Dispatcher == null); // Validate thread invariants: // Threads can't be inside of context switch: VTable.Assert(!currentThread.IsInsideOfContextSwitch()); // New thread can't be a dispatcher thread VTable.Assert(!IsIdleThread(newThread)); } /// /// /// Validate invariants that should hold true for entry point /// from dispatcher's AddRunnableThread call. At this point we can't make any /// assumptions about new thread /// /// /// Thread dispatcher is currently bound to" /// [NoHeapAllocation] private void ValidateInvariantsOnAddRunnableThreadExit( Thread currentThread, Thread newThread) { // Interrupts have to be disabled VTable.Assert(Processor.InterruptsDisabled()); // Validate dispatcher invariants // Thread should belong to this dispatcher or if thread is a main kernel thread it can be null VTable.Assert(currentThread.Dispatcher == this || currentThread.Dispatcher == null); // Validate thread invariants: // Current thread can't be inside of context switch VTable.Assert(!currentThread.IsInsideOfContextSwitch()); } /// /// /// Assert that we are running on the processor we are thinking we are running on /// /// [Conditional("DEBUG")] [NoHeapAllocation] public void AssertCurrentProcessor(int cpu) { VTable.Assert(Processor.GetCurrentProcessorId() == cpu); } /// /// /// Record debugging event /// /// /// Current thread /// Thread we are switchint to /// [Inline] [NoHeapAllocation] private void UpdateStats(Thread currentThread, Thread threadSwitchTo) { #if THREAD_TIME_ACCOUNTING ulong now_a = this.mProcessor.CycleCount; currentThread.context.executionTime += now_a - currentThread.context.lastExecutionTimeUpdate; threadSwitchTo.context.lastExecutionTimeUpdate = now_a; #endif } private static unsafe void InitializeTwiddle() { #if SHOW_TWIDDLE if (Platform.ThePlatform.TwiddleSpinBase != 0) { // [pbar] Scheduler visualization hack // we use unsafe access to minimize the scheduler overhead. screenMem = IoMemory.MapPhysicalMemory(0xb8000, 80*50*2, true, true); screenPos = 0; screenPtr = (ushort *)screenMem.VirtualAddress; tpos = 0; } #endif } [Inline] [NoHeapAllocation] private static unsafe void DisplayThread(ushort color, int id) { #if SHOW_TWIDDLE if (screenPtr != null) { screenPtr[screenPos] = (ushort)(color | ('@' + id)); screenPos = (screenPos + 1) % 80; } #endif } [Inline] [NoHeapAllocation] private static unsafe void Twiddle() { #if SHOW_TWIDDLE if (screenPtr != null) { ushort twiddle; switch (tpos) { default: case 0: twiddle = '|'; break; case 1: twiddle = '/'; break; case 2: twiddle = '-'; break; case 3: twiddle = '\\'; break; } screenPtr[screenPos] = (ushort)(0xe000 | twiddle); tpos = ++tpos & 3; } #endif } /// /// /// Affinity consts states /// /// internal enum Affinity { All = -1 } /// /// /// Dispatcher State /// /// private enum State { Halted, Idle, ActiveWithRequests, Active }; /// /// True if the processor is in use, including halted during idle. /// False only if the processor is down during system shutdown. /// private bool isDispatcherInUse; /// /// True only on boot processor during shutdown when all other processors are down /// private bool isActingForEntireSystem; /// /// Only used when isActingForEntireSystem is true, the next processorId to simulate /// private int nextVirtualProcessorId; /// Number of dispatchers in the system private static int numberOfDispatchers = 0; /// Next dispatcher to activate private static int nextDispatcherToActivate; /// Threre is a single dispatch lock per processor dispatcher private static Scheduler scheduler; /// Dispatcher Id private int id; /// Processor object to which dispatcher is bound private Processor myProcessor; /// Thread queue is used to collect stopped threads private ThreadQueue stoppedThreadQueue; /// Length of the stopped thread queue private int stoppedThreadQueueLength = 0; /// Length of the stopped thread queue private const int maxStoppedThreadQueueLength = 64; /// Thread queue is used to scavanged stopped threads private ThreadQueue scavengerThreadQueue; /// A pool of idle thread used by dispatcher private Thread idleThread; /// A dispatcher helper thread that is performing scavanging and other types of work private Thread scavengerThread; /// A current thread currently running on the processor private Thread runningThread; /// State of the dispatcher - (idle, halted, running) private int currentState = (int) State.Active; /// State of scavenger thread private bool isScavengerRunning; #if SHOW_TWIDDLE //; Memory Interface to allocate physical memory private static IoMemory screenMem; /// Possition twiddle on the screen private static int screenPos; /// Virtual address of the screen memory private static unsafe ushort * screenPtr; /// Current possition inside of array private static int tpos; private ushort color; #endif } }