//////////////////////////////////////////////////////////////////////////////// // // 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(); } } }