441 lines
16 KiB
C#
441 lines
16 KiB
C#
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: MinScheduler.cs
|
|
//
|
|
// Note: Minimal round-robin style without priorities scheduler.
|
|
//
|
|
// MinScheduler 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, MinScheduler
|
|
// will schedule threads from the unblockedThread queue, without
|
|
// reseting the timer. When the timer finally fires, MinScheduler
|
|
// moves all unblockedThreads to the end of the runnableThreads
|
|
// queue and schedules the next runnableThread.
|
|
//
|
|
|
|
//#define DEBUG_DISPATCH
|
|
//#define DEBUG_TIMER
|
|
|
|
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;
|
|
using Microsoft.Singularity.V1.Threads;
|
|
|
|
namespace Microsoft.Singularity.Scheduling
|
|
{
|
|
/// <summary>
|
|
/// Summary description for MinScheduler.
|
|
/// </summary>
|
|
[NoCCtor]
|
|
[CLSCompliant(false)]
|
|
[Mixin(typeof(Scheduler))]
|
|
public class MinScheduler : Scheduler
|
|
{
|
|
// List of recently runnable, but unscheduled threads.
|
|
private static ThreadQueue unblockedThreads;
|
|
|
|
// List of runnable, but unscheduled threads.
|
|
private static ThreadQueue runnableThreads;
|
|
|
|
// List of blocked threads, sorted by wait time.
|
|
private static ThreadQueue blockedThreads;
|
|
|
|
// List of frozen threads (unsorted)
|
|
private static ThreadQueue frozenThreads;
|
|
|
|
// set to something large to debug!
|
|
private static TimeSpan minSlice;
|
|
private static TimeSpan idleSlice;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
[MixinExtend("Initialize")]
|
|
public static void Initialize(Process idleProcess) {
|
|
Scheduler.Initialize();
|
|
|
|
unblockedThreads = new ThreadQueue();
|
|
runnableThreads = new ThreadQueue();
|
|
blockedThreads = new ThreadQueue();
|
|
frozenThreads = new ThreadQueue();
|
|
|
|
minSlice = TimeSpan.FromMilliseconds(10);
|
|
// If busy, don't run for more than 10ms on same task.
|
|
idleSlice = TimeSpan.FromDays(30);
|
|
// If idle, wake up once a month whether we need to or not.
|
|
|
|
// Create the idle threads and put them on the idleThreads loop.
|
|
for (int i = 0; i < Processor.processorTable.Length; i++) {
|
|
Thread idle = Thread.CreateIdleThread(Processor.processorTable[i]);
|
|
Processor.processorTable[i].IdleThread = idle;
|
|
|
|
ThreadHandle handle;
|
|
idleProcess.AddThread(idle,out handle);
|
|
|
|
DispatchLock();
|
|
idleThreads.EnqueueTail(idle.schedulerEntry);
|
|
DispatchRelease();
|
|
}
|
|
|
|
bool iflag = Processor.DisableInterrupts();
|
|
Processor.CurrentProcessor.SetNextTimerInterrupt(
|
|
SchedulerTime.MinValue + TimeSpan.FromMilliseconds(5)
|
|
);
|
|
Processor.RestoreInterrupts(iflag);
|
|
}
|
|
|
|
[MixinExtend("Finalize")]
|
|
new public static void Finalize()
|
|
{
|
|
Scheduler.Finalize();
|
|
}
|
|
|
|
/////////////////////////////////////////// Scheduling Event Handlers.
|
|
//
|
|
// Each of these should be replaced by the scheduler mixin.
|
|
//
|
|
|
|
[MixinOverride]
|
|
new public static void OnThreadStateInitialize(Thread thread, bool constructorCalled)
|
|
{
|
|
// Only initialize thread-local state! No locks held and interrupts on.
|
|
}
|
|
|
|
[MixinOverride]
|
|
new public static void OnThreadStart(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
DebugStub.Assert(thread.freezeCount == 0);
|
|
EnqueueThreadToRun(unblockedThreads, thread);
|
|
}
|
|
|
|
[MixinOverride]
|
|
new public static Thread OnThreadBlocked(Thread thread, SchedulerTime stop)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
// First, put the thread on the blocked queue.
|
|
EnqueueBlockedThread(thread, stop);
|
|
|
|
// Now, find a runnable thread
|
|
return NextRunnableThread();
|
|
}
|
|
|
|
[MixinOverride]
|
|
[NoHeapAllocation]
|
|
new public static void OnThreadUnblocked(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.WriteLine("OnThreadUnblocked: thread={0:x8} [before]",
|
|
__arglist(Kernel.AddressOf(thread)));
|
|
//#if DEBUG_DISPATCH
|
|
//DumpBlockedThreads();
|
|
#endif // DEBUG_DISPATCH
|
|
|
|
ThreadEntry entry = thread.schedulerEntry;
|
|
DebugStub.Assert(entry.queue == blockedThreads
|
|
|| entry.queue == frozenThreads);
|
|
entry.RemoveFromQueue();
|
|
EnqueueThreadToRun(unblockedThreads, thread);
|
|
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.WriteLine("OnThreadUnblocked: after={0:x8} [before]",
|
|
__arglist(Kernel.AddressOf(thread)));
|
|
DumpBlockedThreads();
|
|
#endif // DEBUG_DISPATCH
|
|
}
|
|
|
|
[MixinOverride]
|
|
[NoHeapAllocation]
|
|
new public static Thread OnThreadYield(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
Thread target = NextRunnableThread();
|
|
if (target == null) {
|
|
return thread;
|
|
}
|
|
else {
|
|
EnqueueThreadToRun(runnableThreads, thread);
|
|
return target;
|
|
}
|
|
}
|
|
|
|
[MixinOverride]
|
|
new public static Thread OnThreadStop(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
return NextRunnableThread();
|
|
}
|
|
|
|
[MixinOverride]
|
|
new public static void OnThreadFreezeIncrement(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
thread.freezeCount++;
|
|
if (thread.ActiveProcessor == null) {
|
|
ThreadEntry entry = thread.schedulerEntry;
|
|
entry.RemoveFromQueue();
|
|
frozenThreads.EnqueueTail(entry);
|
|
}
|
|
// (If thread.ActiveProcessor != null, thread will move
|
|
// to frozenThreads when it is descheduled.)
|
|
}
|
|
|
|
[MixinOverride]
|
|
new public static void OnThreadFreezeDecrement(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
DebugStub.Assert(thread.freezeCount > 0);
|
|
thread.freezeCount--;
|
|
if (thread.freezeCount == 0) {
|
|
ThreadEntry entry = thread.schedulerEntry;
|
|
frozenThreads.Remove(entry);
|
|
EnqueueThread(thread);
|
|
}
|
|
}
|
|
|
|
[MixinOverride]
|
|
[CLSCompliant(false)]
|
|
[NoHeapAllocation]
|
|
new public static Thread OnTimerInterrupt(Thread thread, SchedulerTime now)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
ThreadEntry entry;
|
|
|
|
// Move the recently unblocked threads to the runnable queue.
|
|
while ((entry = unblockedThreads.DequeueHead()) != null) {
|
|
EnqueueThreadToRun(runnableThreads, entry.Thread);
|
|
}
|
|
|
|
// Quantum is up, move current thread back to the runnable list.
|
|
if (thread != null) {
|
|
EnqueueThreadToRun(runnableThreads, thread);
|
|
}
|
|
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.WriteLine("OnTimerInterrupt : thread={0:x8} cpu={1} [before]",
|
|
__arglist(Kernel.AddressOf(thread),
|
|
Processor.GetCurrentProcessorId()));
|
|
//#if DEBUG_DISPATCH
|
|
//DumpBlockedThreads();
|
|
#endif // DEBUG_DISPATCH
|
|
|
|
// Now, unblock any threads whose timers have elapsed.
|
|
while ((entry = blockedThreads.Head) != null &&
|
|
entry.Thread.BlockedUntil <= now) {
|
|
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.WriteLine("OnTimerInterrupt : thread={0:x8} cpu={1} [will remove]",
|
|
__arglist(Kernel.AddressOf(entry.Thread),
|
|
Processor.GetCurrentProcessorId()));
|
|
#endif // DEBUG_DISPATCH
|
|
|
|
blockedThreads.Remove(entry);
|
|
entry.Thread.WaitFail();
|
|
EnqueueThreadToRun(runnableThreads, entry.Thread);
|
|
}
|
|
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.WriteLine("OnTimerInterrupt : thread={0:x8} cpu={1} [after]",
|
|
__arglist(Kernel.AddressOf(thread),
|
|
Processor.GetCurrentProcessorId()));
|
|
DumpBlockedThreads();
|
|
#endif // DEBUG_DISPATCH
|
|
|
|
DebugStub.Assert(minSlice.Ticks != 0); // Initialization failure
|
|
DebugStub.Assert(idleSlice.Ticks != 0); // Initialization failure
|
|
|
|
// Choose a default timeout depending on whether their are
|
|
// runnable threads or not.
|
|
SchedulerTime stop;
|
|
if (runnableThreads.IsEmpty()) {
|
|
stop = new SchedulerTime(now.Ticks + idleSlice.Ticks);
|
|
}
|
|
else {
|
|
stop = new SchedulerTime(now.Ticks + minSlice.Ticks);
|
|
}
|
|
|
|
// Then adjust the timeout so that we wake up sooner if
|
|
// there is a thread waiting on a timer.
|
|
if (entry != null && entry.Thread.BlockedUntil < stop) {
|
|
stop = entry.Thread.BlockedUntil;
|
|
}
|
|
Processor.CurrentProcessor.SetNextTimerInterrupt(stop);
|
|
|
|
#if DEBUG_TIMER
|
|
DebugStub.WriteLine("Next Timer: {0}ms",
|
|
__arglist((stop.Ticks - now.Ticks) / TimeSpan.TicksPerMillisecond));
|
|
#endif // DEBUG_TIMER
|
|
|
|
VTable.Assert(unblockedThreads.Head == null);
|
|
return runnableThreads.DequeueHeadThread();
|
|
}
|
|
|
|
[MixinOverride]
|
|
[NoHeapAllocation]
|
|
new public static Thread OnIoInterrupt(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
if (thread != null) {
|
|
EnqueueThreadToRun(unblockedThreads, thread);
|
|
}
|
|
return unblockedThreads.DequeueHeadThread();
|
|
}
|
|
|
|
[MixinOverride]
|
|
[NoHeapAllocation]
|
|
new public static void OnProcessorShutdown(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
|
|
if (thread != null) {
|
|
EnqueueThreadToRun(unblockedThreads, thread);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////// Helper Methods
|
|
//
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
private static Thread NextRunnableThread() {
|
|
Thread target = unblockedThreads.DequeueHeadThread();
|
|
if (target == null) {
|
|
target = runnableThreads.DequeueHeadThread();
|
|
}
|
|
return target;
|
|
}
|
|
|
|
private static void EnqueueThread(Thread thread)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
if (thread.blocked) {
|
|
EnqueueBlockedThread(thread, thread.BlockedUntil);
|
|
}
|
|
else {
|
|
EnqueueThreadToRun(runnableThreads, thread);
|
|
}
|
|
}
|
|
|
|
// Enqueue ready-to-run thread in queue
|
|
// (where queue==runnableThreads or queue==unblockedThreads).
|
|
// Frozen threads are rerouted to the frozen queue.
|
|
[Inline]
|
|
[NoHeapAllocation]
|
|
private static void EnqueueThreadToRun(ThreadQueue queue,
|
|
Thread thread)
|
|
{
|
|
if (thread.freezeCount == 0) {
|
|
queue.EnqueueTail(thread.schedulerEntry);
|
|
}
|
|
else {
|
|
frozenThreads.EnqueueTail(thread.schedulerEntry);
|
|
}
|
|
}
|
|
|
|
[Inline]
|
|
private static void EnqueueBlockedThread(Thread thread, SchedulerTime stop)
|
|
{
|
|
Scheduler.AssertDispatchLockHeld();
|
|
// Put the thread on the blocked queue.
|
|
bool updateStop = ((blockedThreads.Head == null) ||
|
|
(blockedThreads.Head.Thread.BlockedUntil > stop));
|
|
|
|
if (updateStop && stop <= SchedulerTime.Now) {
|
|
// Optimization: don't bother setting a timer for a thread
|
|
// whose blocking has already timed out. Wake it immediately.
|
|
thread.WaitFail();
|
|
EnqueueThreadToRun(runnableThreads, thread);
|
|
return;
|
|
}
|
|
|
|
if (stop == SchedulerTime.MaxValue) {
|
|
blockedThreads.EnqueueTail(thread.schedulerEntry);
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.Print("OnThreadBlocked : thread={0:x8} cpu={1} EnqueueTail\n",
|
|
__arglist(Kernel.AddressOf(thread),
|
|
Processor.GetCurrentProcessorId()));
|
|
#endif // DEBUG_DISPATCH
|
|
}
|
|
else {
|
|
ThreadEntry entry = blockedThreads.Head;
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.Print("OnThreadBlocked : thread={0:x8} cpu={1} stop={2}\n",
|
|
__arglist(Kernel.AddressOf(thread),
|
|
Processor.GetCurrentProcessorId(),
|
|
stop.Ticks));
|
|
#endif // DEBUG_DISPATCH
|
|
while (entry != null && entry.Thread.BlockedUntil <= stop) {
|
|
// Loop until we find the first thread with a later stop.
|
|
entry = entry.Next;
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.Print("OnThreadBlocked : BlockedUntil={0}\n",
|
|
__arglist(entry.Thread.BlockedUntil.Ticks));
|
|
#endif // DEBUG_DISPATCH
|
|
}
|
|
|
|
blockedThreads.InsertBefore(entry, thread.schedulerEntry);
|
|
}
|
|
|
|
VTable.Assert(blockedThreads.IsEnqueued(thread.schedulerEntry));
|
|
|
|
|
|
if (updateStop) {
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.Print("OnThreadBlocked : SetNextTimerInterrupt({0})\n",
|
|
__arglist(stop.Ticks));
|
|
#endif // DEBUG_DISPATCH
|
|
Processor.CurrentProcessor.SetNextTimerInterrupt(stop);
|
|
}
|
|
else {
|
|
#if DEBUG_DISPATCH
|
|
DebugStub.Print("OnThreadBlocked : No timer update needed\n");
|
|
#endif // DEBUG_DISPATCH
|
|
}
|
|
|
|
DumpBlockedThreads();
|
|
}
|
|
|
|
/////////////////////////////////// Diagnostics and Debugging Support.
|
|
//
|
|
[Conditional("DEBUG_DISPATCH")]
|
|
public static void DumpBlockedThreads()
|
|
{
|
|
// First, put the thread on the blocked queue.
|
|
ThreadEntry entry = blockedThreads.Head;
|
|
for (; entry != null; entry = entry.Next) {
|
|
DebugStub.Print(" thread={0:x8} stop= {1}\n",
|
|
__arglist(
|
|
Kernel.AddressOf(entry.Thread),
|
|
entry.Thread.BlockedUntil.Ticks));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
}
|
|
}
|