////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: LaxityScheduler.cs
//
// Note:
//
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using Microsoft.Singularity;
using Microsoft.Singularity.Scheduling;
namespace Microsoft.Singularity.Scheduling
{
#if !SIMULATOR
public class SystemScheduler
{
static public void RegisterScheduler()
{
Laxity.LaxityScheduler.RegisterScheduler();
}
}
#endif
}
namespace Microsoft.Singularity.Scheduling.Laxity
{
///
/// Summary description for LaxityScheduler.
///
public class LaxityScheduler : ICpuScheduler
{
public static readonly TimeSpan ContextSwitch = new TimeSpan(200);
public static readonly TimeSpan MinSlice = new TimeSpan(10 * ContextSwitch.Ticks);
public static readonly TimeSpan AFewSlice = new TimeSpan(3 * MinSlice.Ticks);
public static readonly TimeSpan RobinSlice = new TimeSpan(10 * TimeSpan.TicksPerMillisecond);
static public void RegisterScheduler()
{
CpuResource.RegisterSystemScheduler(new LaxityScheduler());
}
public override ISchedulerProcessor CreateSchedulerProcessor(Processor processor)
{
return new LaxityProcessor(processor);
}
public override ISchedulerThread CreateSchedulerThread(Thread thread)
{
return new LaxityThread(thread);
}
public override ISchedulerActivity CreateSchedulerActivity()
{
return new LaxityActivity();
}
public override ISchedulerCpuReservation ReserveCpu(ISchedulerActivity activity, CpuResourceAmount amount, TimeSpan period)
{
return null;
}
public bool ReserveRecurringCpu(Activity activity, ref TimeSpan amount, ref TimeSpan period)
{
return true;
}
public override bool ShouldReschedule()
{
LaxityThread currentThread = GetCurrentThread();
if (Scheduler.TimerInterruptedFlag && currentThread != null) {
//SchedulerClock.CheckInterrupt();
}
if (currentThread == null || Scheduler.YieldFlag || currentThread.EnclosingThread.IsWaiting() || currentThread.EnclosingThread.IsStopped()) {
Reschedule();
}
return NeedToReschedule || Scheduler.TimerInterruptedFlag;
// || timer should have fired but didn't?
}
// DI -- this is somehow similar to EnableInterrupts
public override bool NextThread(out Thread nextThread)
{
Debug.Assert(!Processor.InterruptsDisabled());
bool iflag = Processor.DisableInterrupts();
bool halted = false;
LaxityThread currentThread = GetCurrentThread();
SchedulerClock.CheckInterrupt();
if (ShouldReschedule()) {
//Debug.Print("Calling RescheduleInterrupt()\n");
halted = RescheduleInterrupt(); //TODO: RescheduleInterrupt returns true if the processor needs to be halted.
currentThread = GetCurrentThread();
}
else {
//Debug.Print("No call to RescheduleInterrupt()\n");
}
if (currentThread != null) {
nextThread = currentThread.EnclosingThread;
}
else {
Debug.Assert(halted);
nextThread = null;
}
((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).NeedToReschedule = false;
// printf("-------------------------currentThread-Id %d\n", currentThread.Id);
//return currentThread;
Processor.RestoreInterrupts(iflag);
return halted;
}
//public static void Yield();
#region Constants
// Maximum Scheduling Period is 1 sec (i.e. in 100s of nanoseconds).
static readonly TimeSpan AmountCpu = CpuResource.MaxPeriod; //(MaxPeriod * 100); //TODO: TICKS vs. TIME_SPAN vs DATE_TIME
#endregion
#region Static data members
static LaxityActivity CircularActivityList = null;
///
/// This must ALWAYS (when lock not held) point to a current resource
/// container in the queue.
///
static internal LaxityActivity NextActivity;
//moved to be processor specific
//static LaxityActivity LaxityActivityNext = null;
//static LaxityActivity LaxityActivityCurrent = null; // activity currently executing from
private static OneShotReservation ReservationFreePool;
private static TimeSpan IdleTime = new TimeSpan(0);
private static bool Idle
{
get { return GetCurrentProcessor().Idle; }
}
//moved to be processor specific
//static LaxityThread CurrentThreadGlobal = null;
//static TimeSpan LaxitySliceLeft = LaxityScheduler.LaxitySlice; //new TimeSpan(0); // the round robin list, if any, and its CPU slice
//public static DateTime SchedulingTime;
//static bool NeedToReschedule = false;
public static TimeSpan LA_Time = LaxityScheduler.MinSlice;
public static DateTime SchedulingTime
{
get {
return ((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).SchedulingTime;
}
}
public static bool NeedToReschedule
{
get {
return ((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).NeedToReschedule;
}
}
#endregion
public LaxityScheduler()
{
}
public override void Initialize()
{
LaxityScheduler.InitializeScheduler();
}
public static void InitializeScheduler()
{ //TODO: Should all the null's be added in here?
for (int i = 0; i < Processor.processorTable.Length; i++) {
((LaxityProcessor)Processor.processorTable[i].SchedulerProcessor).SchedulingTime = SystemClock.GetKernelTime();
}
//
// TimeSpan currentSliceLeft = LaxitySliceLeft;
bool iflag = Processor.DisableInterrupts();
SchedulerClock.SetNextInterrupt(SchedulingTime + LaxityScheduler.RobinSlice);
Processor.RestoreInterrupts(iflag);
}
#region ISystemScheduler Members
public override void BeginDelayedConstraint(Hashtable resourceEstimates, TimeSpan relativeDeadline, ISchedulerTask taskToEnd, out ISchedulerTask schedulerTask)
{
TimeConstraint timeConstraint = new TimeConstraint();
timeConstraint.Estimate = CpuResource.Provider().CpuToTime((CpuResourceAmount)resourceEstimates[CpuResource.Provider().ResourceString]);
timeConstraint.Start = new DateTime(0); //Start now.
timeConstraint.RelativeDeadline = relativeDeadline;
timeConstraint.Deadline = new DateTime(0); //A 0-deadline means relative instead.
schedulerTask = Thread.CurrentThread.SchedulerThread.PrepareDelayedTask(taskToEnd, ref timeConstraint, SystemClock.GetKernelTime());
}
public override bool BeginConstraint(Hashtable resourceEstimates, DateTime deadline, ISchedulerTask taskToEnd, out ISchedulerTask schedulerTask)
{
DateTime timeNow = SystemClock.GetKernelTime();
LaxityThread thread = GetCurrentThread();
ulong start;
ulong stop;
Debug.Assert(!Processor.InterruptsDisabled());
thread.IpcCheckFreeConstraint();
start = Processor.CycleCount;
Debug.Assert(taskToEnd == null || taskToEnd == thread.ReservationStack);
bool end_previous = (taskToEnd != null);
TimeConstraint constraint = new TimeConstraint();
constraint.Deadline = deadline;
constraint.Estimate = CpuResource.Provider().CpuToTime((CpuResourceAmount)resourceEstimates[CpuResource.Provider().ResourceString]);
constraint.Start = timeNow;
bool ok = thread.BeginConstraintBeforeWaitValidate(end_previous, ref constraint, timeNow);
schedulerTask = thread.PendingReservation;
if (ok) {
OneShotReservation.BeginConstraintBeforeWait(thread, end_previous, constraint, timeNow);
bool iflag = Processor.DisableInterrupts();
ok = OneShotReservation.ResolveConstraint(thread);
Processor.RestoreInterrupts(iflag);
}
stop = Processor.CycleCount;
Scheduler.LogBeginConstraint(thread.EnclosingThread, ok, start, stop);
//TODO: Check if this is a necessity, or if it's only for sim time. Call in wrapper if necessary.
//NextThread();
return ok;
}
public override bool EndConstraint(ISchedulerTask taskToEnd)
{
Debug.Assert(!Processor.InterruptsDisabled());
Debug.Assert(taskToEnd == GetCurrentThread().ReservationStack);
bool iflag = Processor.DisableInterrupts();
bool ok = OneShotReservation.EndPreviousConstraint(GetCurrentThread(), SystemClock.GetKernelTime());
//
// need to reschedule only if on a reserved slot and the top of EarliestDeadlineFirst doesn't
// belong to this task
//
if ((OneShotReservation.CurrentReservation != null) &&
(OneShotReservation.TopGuaranteedReservation != null) &&
(OneShotReservation.TopGuaranteedReservation.ReservTask != (GetCurrentThread()))) {
Reschedule();
}
Processor.RestoreInterrupts(iflag);
Scheduler.LogEndConstraint();
//TODO: In the system wrapper for BeginConstraint -- it needs to call Yield/NextThread/MaybeYield
//NextThread();
return ok;
}
public static bool ReserveRecurringCpu(LaxityActivity activity,
ref TimeSpan amount,
ref TimeSpan period)
{
return true;
}
#endregion
#region OneShotCpuReservation related functions
// OneShotCpuReservation related functions begin:
public static int ReleaseReservationProtected(OneShotReservation reservation)
{
int newrefcnt;
Debug.Assert(Processor.InterruptsDisabled());
Debug.Assert(reservation.ReferenceCount > 0);
newrefcnt = Interlocked.Decrement(ref reservation.ReferenceCount);
if (newrefcnt == 0) {
// Estimate may be positive when reservation reaches
// its Deadline w/o using its Estimate
Debug.Assert((reservation.Next == null) && (reservation.Previous == null));
if (reservation.OriginalThread != null) {
reservation.OriginalThread.ReleaseProtected();
//Debug.Assert(reservation.ActiveThread != null);
//reservation.ActiveThread.ReleaseProtected();
}
else {
Debug.Assert(reservation.AssociatedActivity != null);
ActivityReleaseProtected(reservation.AssociatedActivity);
}
Debug.Assert(reservation.Next == null);
CacheFreeReservation(reservation);
}
return newrefcnt;
}
public static OneShotReservation AllocateReservation()
{
OneShotReservation reservation;
// DisableInterrupts();
reservation = IpcAllocateReservation();
// EnableInterrupts();
if (reservation == null) {
reservation = new OneShotReservation(); //(PRESERVATION)malloc(sizeof(struct OneShotReservation)); // to be done while on the I-stack!
reservation.Clear();
}
return reservation;
}
static void FreeReservation(OneShotReservation reservation)
{
reservation = null; //free(reservation);
}
// Garbage-collect unused Reservations.
public static int ReleaseReservation(OneShotReservation reservation)
{
// Debug.Assert(!Processor.InterruptsDisabled());
Debug.Assert(reservation.ReferenceCount > 0);
int newrefcnt = reservation.ReferenceCount - 1;
reservation.ReferenceCount = newrefcnt;
if (newrefcnt == 0) {
// Estimate may be positive when reservation reaches its Deadline w/o using its Estimate
Debug.Assert((reservation.Next == null) && (reservation.Previous == null));
if (reservation.OriginalThread != null) {
reservation.OriginalThread.Release();
//Debug.Assert(reservation.ActiveThread != null);
//reservation.ActiveThread.Release();
}
else if (reservation.AssociatedActivity != null) {
ActivityObjRelease(reservation.AssociatedActivity);
}
CacheFreeReservation(reservation);
}
return newrefcnt;
}
// Like CheckFreeConstraint, but callable from the IPC path.
// Because we can't call AllocateReservation, we use the helper thread
// if there aren't any free Reservations.
static void CacheFreeReservation(OneShotReservation reservation)
{
if (reservation.OriginalThread != null &&
reservation.OriginalThread.FreeReservation == null) {
reservation.OriginalThread.FreeReservation = reservation;
}
else {
IpcFreeReservation(reservation);
}
}
// Callable with preemption disabled. Allocates a OneShotReservation
// from the global free list.
public static OneShotReservation IpcAllocateReservation()
{
OneShotReservation reservation;
if ((reservation = ReservationFreePool) != null) {
ReservationFreePool = reservation.FreeListNext;
}
return reservation;
}
// Callable with preemption disabled. Frees a OneShotReservation
// to the global free list.
public static void IpcFreeReservation(OneShotReservation reservation)
{
Debug.Assert( reservation != null);
Debug.Assert(reservation.ReferenceCount == 0);
reservation.FreeListNext = ReservationFreePool;
ReservationFreePool = reservation;
}
//////////////////////////////////////////////////////////////////////
public static void AddRefReservation(OneShotReservation reservation)
{
Debug.Assert(reservation.ReferenceCount >= 0);
reservation.ReferenceCount++;
}
#endregion
#region Potpourri (Unregioned functions)
public static LaxityThread GetCurrentThread()
{
return ((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).RunningThread;
}
public static void IChangeCurrentThread(LaxityThread thread)
{
((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).RunningThread = thread;
}
// Auxiliary Routines.
// TODO: Revisit the necessity of these functions.
public static TimeSpan minInterval(TimeSpan TimeInterv0, TimeSpan TimeInterv1)
{
return (TimeInterv0 < TimeInterv1? TimeInterv0: TimeInterv1);
}
public static DateTime minTime(DateTime Time0, DateTime Time1)
{
return (Time0 < Time1? Time0: Time1);
}
static TimeSpan maxInterval(TimeSpan TimeInterv0, TimeSpan TimeInterv1)
{
return (TimeInterv0 < TimeInterv1? TimeInterv1: TimeInterv0);
}
static DateTime maxTime(DateTime Time0, DateTime Time1)
{
return (Time0 < Time1? Time1: Time0);
}
internal static LaxityProcessor GetCurrentProcessor()
{
return (LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor;
}
// Add Activity in the last Round-Robin position.
public static void EnqueueActivity(LaxityActivity activity)
{
Debug.Assert(Processor.InterruptsDisabled());
if (CircularActivityList == null) {
NextActivity = CircularActivityList /*= GetCurrentProcessor().CurrentActivity */=
activity.Next = activity.Previous = activity;
//Here modifying other processor specific data rather than global.
// for (int i=0; i NewTimerTimeout) {
return false;
}
Debug.Assert(Processor.InterruptsDisabled());
// Debug.Assert(CurrentThread()->GetState() == Thread.ThreadState.ThreadRunning);
Debug.Assert(NewTimerTimeout > SchedulingTime);
// Debug.Assert(SleepTimeout > SchedulingTime);
//TODO: Need LA_Time?
// if (NewTimerTimeout > SleepTimeout - LA_Time)
// NewTimerTimeout = SleepTimeout - LA_Time;
if (NewTimerTimeout > LaxityThread.GetSleepTimeout()) {
NewTimerTimeout = LaxityThread.GetSleepTimeout();
}
//DebugStub.Print("Setting Next Interrupt for: {0} ....\n",
//__arglist(NewTimerTimeout.Ticks));
bool success = SchedulerClock.SetNextInterrupt(NewTimerTimeout); //TODO: Perhaps only call this if the time changed.
//DebugStub.Print(success?"SUCCESS\n":"FAILED\n");
return success;
}
public static void UpdateSchedulingStatus()
{
TimeSpan timeRun;
LaxityThread currentThread = GetCurrentThread();
DateTime newSchedulingTime = SystemClock.GetKernelTime();
timeRun = newSchedulingTime - SchedulingTime;
LaxityProcessor processor = (LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor;
processor.SchedulingTime = newSchedulingTime;
if (Idle) {
// UpdateIdleTime
IdleTime += timeRun;
return;
}
// If IDLE compute CurrentPlanNode & set currentThread to null.
if (currentThread != null) {
// Unless thread just exited,
// Update Thread & Activity execution times.
currentThread.AddExecutionTime(timeRun);
Scheduler.CurrentTask().AddResourceAmountUsed(CpuResource.Provider().ResourceString, CpuResource.Provider().TimeToCpu(timeRun));
// if (currentThread.AssociatedActivity != null) {
// currentThread.AssociatedActivity.MyRecurringCpuReservation.EnclosingCpuReservation.AddTimeUsed(timeRun);
// }
}
if (OneShotReservation.CurrentReservation != null) {
// Slice used for a reservation.
OneShotReservation.CurrentReservation.Estimate -= timeRun;
}
else if (processor.CurrentActivity != null) {
// Slice used for round robin.
processor.SliceLeft -= timeRun;
processor.CurrentActivity = null;
}
}
internal static void CheckInvariants()
{
if (CircularActivityList != null) {
bool listNextFound = (CircularActivityList == NextActivity);
LaxityActivity start = CircularActivityList, current = CircularActivityList.Next, previous = CircularActivityList;
while (current != start) {
//perhaps check thread invariants here too.
Debug.Assert(current != null, "resource container list is not circular");
Debug.Assert(current.Previous == previous, "back pointer doesn't match forward pointer");
if (current == NextActivity) {
listNextFound = true;
}
previous = current;
current = current.Next;
}
Debug.Assert(listNextFound, "NextActivity isn't in the loop!");
Debug.Assert(current.Previous == previous, "Head doesn't point to tail");
}
}
// Always called when another thread needs to be scheduled.
static bool RescheduleInterrupt()
{
CheckInvariants();
Debug.Assert(Processor.InterruptsDisabled());
Debug.Assert(!Processor.InterruptsDisabled());
LaxityThread currentThread = GetCurrentThread();
Debug.Assert(currentThread == null || currentThread.ActiveProcessor == GetCurrentProcessor());
if (currentThread != null) {
currentThread.ActiveProcessor = null;
}
DateTime nextStart;
LaxityThread previousThread;
RescheduleAgain: // !!!!!!!!!! SIM ONLY !!!!!!!!!
if (Idle) {
currentThread = null;
}
previousThread = currentThread;
UpdateSchedulingStatus();
// if thread was the head of the runnable threads Q:
// Advance the runnable threads Q.
if ((currentThread != null) &&
(currentThread.AssociatedActivity != null) &&
(currentThread.AssociatedActivity.RunnableThreads == currentThread)) {
currentThread.AssociatedActivity.RunnableThreads = currentThread.Next;
Debug.Assert(currentThread.AssociatedActivity.RunnableThreads != null);
}
OneShotReservation.UpdateCurrentReservation();
OneShotReservation.ClearCurrentReservation();
currentThread = null;
// Finished first stage, i.e. updated state.
// Start second stage: wakeup threads & select Next CPU slice.
LaxityThread.WakeThreads();
// NOTE: In the original Laxity Simulator Code (& MMOSA code)
// The call to DrainDeferredConditions() was made here.
// In Singularity, this will basically be replaced with a
// queue of wait-events to fix.
OneShotReservation.FreshenReservationQueues();
TimeSpan currentNodeSliceLeft;
LaxityProcessor currentProcessor = (LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor;
if (currentProcessor.CurrentActivity != null && currentProcessor.SliceLeft >= LaxityScheduler.MinSlice) {
currentThread = currentProcessor.CurrentActivity.GetRunnableThread();
if (currentThread != null) {
goto exit;
}
}
// Find runnable one-shot reservation if any.
OneShotReservation.FindRunnableReservation(ref currentThread);
if (currentThread != null) {
goto exit;
}
// Find next runnable Activity.
currentProcessor.SliceLeft = LaxityScheduler.RobinSlice;
currentProcessor.CurrentActivity = NextActivity; // !!!!!!!!!!!!!SIM ONLY !!!!!!!!!!
while ((currentThread = NextActivity.GetRunnableThread()) == null) {
NextActivity = NextActivity.Next;
//currentProcessor.SliceLeft = LaxityScheduler.LaxitySlice;
if (NextActivity == currentProcessor.CurrentActivity) {
// !!!!!!!!SIM ONLY !!!!!!!
// in the real scheduler, execute halt
if (OneShotReservation.IdleReservations != null) {
// reuse nextStart
nextStart = minTime(LaxityThread.GetSleepTimeout(), OneShotReservation.IdleReservations.Start);
}
else {
nextStart = LaxityThread.GetSleepTimeout();
DebugStub.Print("Idle, sleeping until {0} cf maxvalue {1}\n",
__arglist(nextStart.Ticks,
DateTime.MaxValue.Ticks));
}
if (nextStart == DateTime.MaxValue) {
Scheduler.StopSystem();
}
if (! ResetTimerTimeout(nextStart)) {
//Error setting timer. Try scheduling again.
DebugStub.Print("Thought Idle, failed to set interrupt.\n");
goto RescheduleAgain;
}
GetCurrentProcessor().Idle = true;
// !!!!!!!!SIM ONLY !!!!!!!
currentThread = null; // !!!!!!!!SIM ONLY !!!!!!!
OneShotReservation.ClearCurrentReservation();
currentProcessor.CurrentActivity = null;
if (DateTime.MaxValue != nextStart) {
//Scheduler.LogTimeJump();
}
//DebugStub.Print("Halted.\n");
IChangeCurrentThread(null);
return true; // !!!!!!!!SIM ONLY !!!!!!!
}
// !!!!!!!!SIM ONLY !!!!!!!
}
//DebugStub.Print("Running Round Robin Resource Container\n");
currentProcessor.CurrentActivity = NextActivity; // we probably need only one of the two variables
NextActivity = NextActivity.Next;
exit:
currentNodeSliceLeft = currentProcessor.SliceLeft;
Debug.Assert(currentThread != null);
if (currentThread != previousThread) {
Scheduler.LogContextSwitch(); // Context Switch statistics
}
if (OneShotReservation.IdleReservations != null) {
// reuse nextStart
nextStart = minTime(LaxityThread.GetSleepTimeout(), OneShotReservation.IdleReservations.Start);
}
else {
nextStart = LaxityThread.GetSleepTimeout();
}
if (SchedulingTime + currentNodeSliceLeft /* CurrentSlice */ > nextStart) {
currentNodeSliceLeft /* CurrentSlice */ = nextStart - currentProcessor.SchedulingTime;
}
Scheduler.LogReschedule();
if (!ResetTimerTimeout(currentProcessor.SchedulingTime + currentNodeSliceLeft) || Scheduler.TimerInterruptedFlag) {
//TODO: What do we REALLY want here?
currentThread = null; // !!!!!!!!SIM ONLY !!!!!!!
OneShotReservation.ClearCurrentReservation();
currentProcessor.CurrentActivity = null;
goto RescheduleAgain;
}
Debug.Assert(currentThread.ActiveProcessor == null);
currentThread.ActiveProcessor = GetCurrentProcessor();
Debug.Assert(currentThread.ActiveProcessor != null);
if (currentThread != previousThread) {
IChangeCurrentThread(currentThread);
}
GetCurrentProcessor().Idle = false;
// Not necessarily true: Debug.Assert(!Scheduler.TimerInterruptedFlag);
CheckInvariants();
return false;
}
#endregion
#region Laxity Changes
#endregion
#region Constraint feasibility analysis support functions
// Node can provide more than time than necessary between start and deadline; come shorter deadline:
// add a new pointer to a reservation to a node and clean the ordered array
#endregion
public static void Reschedule()
{
((LaxityProcessor)Processor.CurrentProcessor.SchedulerProcessor).Reschedule();
}
public static void ActivityObjAddRef(LaxityActivity activity)
{
activity.ReferenceCount++;
}
public static void ActivityObjRelease(LaxityActivity activity)
{
Debug.Assert(activity.ReferenceCount >= 1);
activity.ReleaseReference();
}
public static void ActivityReleaseProtected(LaxityActivity activity)
{
Debug.Assert(activity.ReferenceCount >= 1);
Debug.Assert(Processor.InterruptsDisabled(), "Interrupts Not Disabled!");
activity.ReleaseReference();
}
}
}