//------------------------------------------------------------------------------ // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // Description: $filename$ // // Minimal round-robin style without priorities scheduler. // // AffinityScheduler favors thread that have recently become unblocked // and tries to avoid reading the clock or reseting the timer as // much as possible. // // The minimal scheduler maintains two queues of threads that can // be scheduled. The unblockedThreads queue contains threads which // have become unblocked during this scheduling quantum; mostly, // these are threads that were unblocked by the running thread. // The runnableThreads queue contains all other threads that are // currently runnable. If the current thread blocks, AffinityScheduler // will schedule threads from the unblockedThread queue, without // reseting the timer. When the timer finaly fires, AffinityScheduler // moves all unblockedThreads to the end of the runnableThreads // queue and schedules the next runnableThread. //------------------------------------------------------------------------------ using System; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Bartok.Options; using Microsoft.Singularity; using Microsoft.Singularity.Io; using Microsoft.Singularity.Scheduling; namespace Microsoft.Singularity.Scheduling { /// /// /// Summary description for AffinityScheduler. /// /// [NoCCtor] [CLSCompliant(false)] public class AffinityScheduler : Scheduler { public AffinityScheduler() { } /// /// /// Initialize min scheduler /// /// public override void Initialize() { // HACK use 30ms for now to give time for debug output. // If busy, don't run for more than 10ms on same task. TimeSpan minSlice = TimeSpan.FromMilliseconds(10); schedulers = new BaseScheduler [Processor.processorTable.Length]; // Create the idle threads and put them on the idleThreads loop. for (int i = 0; i < Processor.processorTable.Length; i++) { // Create scheduler schedulers[i] = BaseSchedulerManager.CreateScheduler(minSlice, i); } // Wait until first dispatcher will call into the scheduler numberOfActiveDispatchers = 0; } /// /// /// Finalize scheduler object /// /// public override void Finalize() { } /// /// /// Notify scheduler about new dispatcher /// /// public override void OnDispatcherInitialize(int dispatcherId) { // Increment number of active dispatchers int dispatchers = Interlocked.Increment (ref numberOfActiveDispatchers); // Check if a now we really MP enabled if (dispatchers == Processor.ExpectedProcessors) { isMPEnabled = true; } } /// /// /// Attach thread to scheduler: thread specific initializtion /// /// /// Thread to attach /// Have we called thread constructor /// public override void OnThreadStateInitialize(Thread thread, bool constructorCalled) { // Only initialize thread-local state! No locks held and interrupts on. } /// /// /// Start thread - put a thread on unblocked queue so it can be run /// /// /// Thread to start /// public override void OnThreadStart(Thread thread) { // TODO: this needs to check whether the processor is running yet. int threadID = thread.GetThreadId(); // NOTE Affinity may be legally set to any int. NoAffinity(-1) is special. // All other ints must be converted to a legal index into the processor table int initial = thread.Affinity == Thread.NoAffinity ? threadID : Math.Abs(thread.Affinity); int cpu = isMPEnabled ? initial % numberOfActiveDispatchers : 0; thread.Affinity = cpu; schedulers[cpu].OnThreadStart(thread); } /// /// /// Block thread - put a thread on block queue and retrieve next thread to run /// /// /// Thread that blocks /// Amount of time for the thread to be blocked /// public override void OnThreadBlocked(Thread thread, SchedulerTime stop) { schedulers[thread.Affinity].OnThreadBlocked(thread, stop); } /// /// /// Unblock thread - resume thread by putting it on unblock queue. This method /// can be invoked by threads running on other processors /// /// /// Thread to resume /// [NoHeapAllocation] public override void OnThreadUnblocked(Thread thread) { schedulers[thread.Affinity].OnThreadUnblocked(thread); } /// /// /// Yield thread - suspend thread based on time slice /// /// /// Thread that is yielding /// [NoHeapAllocation] public override void OnThreadYield(Thread thread) { schedulers[thread.Affinity].OnThreadYield(thread); } /// /// /// Stop thread execution /// /// /// Thread that is stopping /// public override void OnThreadStop(Thread thread) { schedulers[thread.Affinity].OnThreadStop(thread); } /// /// /// Increment frozen counter /// /// /// Thread for which to increment freeze counter /// public override void OnThreadFreezeIncrement(Thread thread) { schedulers[thread.Affinity].OnThreadFreezeIncrement(thread); } /// /// /// Decrement frozen counter /// /// /// Thread for which to update freeze counter /// public override void OnThreadFreezeDecrement(Thread thread) { schedulers[thread.Affinity].OnThreadFreezeDecrement(thread); } /// /// /// Dispatch timer interrupt /// /// /// Processor affinity /// Current time /// /// Queue used to put threads to be waken up. Latter on dispatcher will add threads /// to scheduler's runnable queue /// /// [CLSCompliant(false)] [NoHeapAllocation] public override TimeSpan OnTimerInterrupt(int affinity, SchedulerTime now) { return schedulers[affinity].OnTimerInterrupt(affinity, now); } /// /// /// Add thread to runnable queue. This methods is called by dispatcher at the /// point when both dispatcher lock and interrupts are disabled /// /// /// Thread to add to runnable queue /// Place to where enqueue thread in a runnable queue /// [Inline] [NoHeapAllocation] public override void AddRunnableThread(Thread thread) { schedulers[thread.Affinity].AddRunnableThread(thread); } /// /// /// Run scheduling policy to decide the next thread to run /// /// /// The affinity of the scheduler /// The running thread returned should be /// set to this affinity. It's same as schedulerAffinity except during /// thread migration /// /// the thread currently running /// thread state to change to for the current thread /// Current system time /// [NoHeapAllocation] public override Thread RunPolicy( int schedulerAffinity, int runningThreadAffinity, Thread currentThread, ThreadState schedulerAction, SchedulerTime currentTime) { // Assert preconditions: current thread can be either NULL or its base scheduler // should be the same as specified by affinity VTable.Assert(currentThread == null || currentThread.Affinity == schedulerAffinity); // Use affinity to derive actual scheduler return schedulers[schedulerAffinity].RunPolicy( schedulerAffinity, runningThreadAffinity, currentThread, schedulerAction, currentTime); } /// /// /// Suspend thread and wait until it is suspended. /// /// /// Thread to suspend /// [NoHeapAllocation] public override void Suspend(Thread thread) { schedulers[thread.Affinity].Suspend(thread); } /// /// /// Resume thread from being suspended /// /// /// Thread to resume /// [NoHeapAllocation] public override void Resume(Thread thread) { schedulers[thread.Affinity].Resume(thread); } /// /// /// Retrieve scheduler lock - used by dispatcher to protect scheduler state /// /// /// Affinity of dispatcher making actual call /// [CLSCompliant(false)] [NoHeapAllocation] internal override SchedulerLock RetrieveSchedulerLock(int affinity) { // Use the our defualt scheder's to return return schedulers[affinity].MyLock; } /// An array of schedulers private static BaseScheduler [] schedulers; /// A number of active dispatchers private static int numberOfActiveDispatchers; /// Flag showing whether multi processing is enabled private static bool isMPEnabled; } }