diff options
author | gdkchan <gab.dark.100@gmail.com> | 2020-12-09 19:20:05 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-09 19:20:05 -0300 |
commit | 48278905d1470f89be31668c738397f569af156a (patch) | |
tree | 2e35b0695b33c8eb723f5948e3f6f040d84cfe76 /Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | |
parent | 3484265d37732b32951709e5abfa52a260db349d (diff) |
Rewrite scheduler context switch code (#1786)
* Rewrite scheduler context switch code
* Fix race in UnmapIpcRestorePermission
* Fix thread exit issue that could leave the scheduler in a invalid state
* Change context switch method to not wait on guest thread, remove spin wait, use SignalAndWait to pass control
* Remove multi-core setting (it is always on now)
* Re-enable assert
* Remove multicore from default config and schema
* Fix race in KTimeManager
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs')
-rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 413 |
1 files changed, 142 insertions, 271 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index f523cb9c..b95b1e8e 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -4,8 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Numerics; using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Threading @@ -14,17 +13,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { public const int MaxWaitSyncObjects = 64; - private int _hostThreadRunning; + private ManualResetEvent _schedulerWaitEvent; + + public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent; public Thread HostThread { get; private set; } public ARMeilleure.State.ExecutionContext Context { get; private set; } + public KThreadContext ThreadContext { get; private set; } + + public int DynamicPriority { get; set; } public long AffinityMask { get; set; } public long ThreadUid { get; private set; } - public long TotalTimeRunning { get; set; } + private long _totalTimeRunning; + + public long TotalTimeRunning => _totalTimeRunning; public KSynchronizationObject SignaledObj { get; set; } @@ -32,6 +38,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private ulong _entrypoint; private ThreadStart _customThreadStart; + private bool _forcedUnschedulable; + + public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable; public ulong MutexAddress { get; set; } @@ -65,11 +74,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KernelResult ObjSyncResult { get; set; } - public int DynamicPriority { get; set; } - public int CurrentCore { get; set; } public int BasePriority { get; set; } public int PreferredCore { get; set; } + public int CurrentCore { get; set; } + public int ActiveCore { get; set; } + private long _affinityMaskOverride; private int _preferredCoreOverride; #pragma warning disable CS0649 @@ -86,26 +96,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading set => _shallBeTerminated = value ? 1 : 0; } + public bool TerminationRequested => ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending; + public bool SyncCancelled { get; set; } public bool WaitingSync { get; set; } - private bool _hasExited; + private int _hasExited; private bool _hasBeenInitialized; private bool _hasBeenReleased; public bool WaitingInArbitration { get; set; } - private KScheduler _scheduler; - - private KSchedulingData _schedulingData; - public long LastPc { get; set; } public KThread(KernelContext context) : base(context) { - _scheduler = KernelContext.Scheduler; - _schedulingData = KernelContext.Scheduler.SchedulingData; - WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; WaitSyncHandles = new int[MaxWaitSyncObjects]; @@ -119,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ulong argsPtr, ulong stackTop, int priority, - int defaultCpuCore, + int cpuCore, KProcess owner, ThreadType type, ThreadStart customThreadStart = null) @@ -129,20 +134,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading throw new ArgumentException($"Invalid thread type \"{type}\"."); } - PreferredCore = defaultCpuCore; + ThreadContext = new KThreadContext(); - AffinityMask |= 1L << defaultCpuCore; + PreferredCore = cpuCore; + AffinityMask |= 1L << cpuCore; SchedFlags = type == ThreadType.Dummy ? ThreadSchedState.Running : ThreadSchedState.None; - CurrentCore = PreferredCore; - + ActiveCore = cpuCore; + ObjSyncResult = KernelResult.ThreadNotStarted; DynamicPriority = priority; BasePriority = priority; - - ObjSyncResult = KernelResult.ThreadNotStarted; + CurrentCore = cpuCore; _entrypoint = entrypoint; _customThreadStart = customThreadStart; @@ -179,41 +184,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading Context = CpuContext.CreateExecutionContext(); - bool isAarch32 = !Owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit); - - Context.IsAarch32 = isAarch32; + Context.IsAarch32 = !is64Bits; Context.SetX(0, argsPtr); - if (isAarch32) + if (is64Bits) { - Context.SetX(13, (uint)stackTop); + Context.SetX(31, stackTop); } else { - Context.SetX(31, stackTop); + Context.SetX(13, (uint)stackTop); } Context.CntfrqEl0 = 19200000; Context.Tpidr = (long)_tlsAddress; - owner.SubscribeThreadEventHandlers(Context); - ThreadUid = KernelContext.NewThreadUid(); - HostThread.Name = $"HLE.HostThread.{ThreadUid}"; + HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}"; _hasBeenInitialized = true; if (owner != null) { + owner.SubscribeThreadEventHandlers(Context); owner.AddThread(this); if (owner.IsPaused) { KernelContext.CriticalSection.Enter(); - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + if (TerminationRequested) { KernelContext.CriticalSection.Leave(); @@ -237,7 +239,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { KernelContext.CriticalSection.Enter(); - if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + if (!TerminationRequested) { _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; @@ -253,20 +255,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (!ShallBeTerminated) { - KThread currentThread = KernelContext.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); - while (SchedFlags != ThreadSchedState.TerminationPending && - currentThread.SchedFlags != ThreadSchedState.TerminationPending && - !currentThread.ShallBeTerminated) + while (SchedFlags != ThreadSchedState.TerminationPending && (currentThread == null || !currentThread.TerminationRequested)) { if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) { result = KernelResult.InvalidState; - break; } - if (currentThread._forcePauseFlags == ThreadSchedState.None) + if (currentThread == null || currentThread._forcePauseFlags == ThreadSchedState.None) { if (Owner != null && _forcePauseFlags != ThreadSchedState.None) { @@ -275,8 +274,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading SetNewSchedFlags(ThreadSchedState.Running); - result = KernelResult.Success; + StartHostThread(); + result = KernelResult.Success; break; } else @@ -299,28 +299,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return result; } - public void Exit() - { - // TODO: Debug event. - - if (Owner != null) - { - Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); - - _hasBeenReleased = true; - } - - KernelContext.CriticalSection.Enter(); - - _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; - - ExitImpl(); - - KernelContext.CriticalSection.Leave(); - - DecrementReferenceCount(); - } - public ThreadSchedState PrepareForTermination() { KernelContext.CriticalSection.Enter(); @@ -387,9 +365,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading do { - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + if (TerminationRequested) { - KernelContext.Scheduler.ExitThread(this); Exit(); // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here. @@ -398,7 +375,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading KernelContext.CriticalSection.Enter(); - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + if (TerminationRequested) { state = ThreadSchedState.TerminationPending; } @@ -416,200 +393,74 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } while (state == ThreadSchedState.TerminationPending); } - private void ExitImpl() + public void Exit() { - KernelContext.CriticalSection.Enter(); - - SetNewSchedFlags(ThreadSchedState.TerminationPending); - - _hasExited = true; + // TODO: Debug event. - Signal(); + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); - KernelContext.CriticalSection.Leave(); - } + _hasBeenReleased = true; + } - public KernelResult Sleep(long timeout) - { KernelContext.CriticalSection.Enter(); - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) - { - KernelContext.CriticalSection.Leave(); + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; - return KernelResult.ThreadTerminating; - } + bool decRef = ExitImpl(); - SetNewSchedFlags(ThreadSchedState.Paused); - - if (timeout > 0) - { - KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout); - } + Context.StopRunning(); KernelContext.CriticalSection.Leave(); - if (timeout > 0) + if (decRef) { - KernelContext.TimeManager.UnscheduleFutureInvocation(this); + DecrementReferenceCount(); } - - return 0; } - public void Yield() + private bool ExitImpl() { KernelContext.CriticalSection.Enter(); - if (SchedFlags != ThreadSchedState.Running) - { - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); - - return; - } + SetNewSchedFlags(ThreadSchedState.TerminationPending); - if (DynamicPriority < KScheduler.PrioritiesCount) - { - // Move current thread to the end of the queue. - _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); - } + bool decRef = Interlocked.Exchange(ref _hasExited, 1) == 0; - _scheduler.ThreadReselectionRequested = true; + Signal(); KernelContext.CriticalSection.Leave(); - KernelContext.Scheduler.ContextSwitch(); + return decRef; } - public void YieldWithLoadBalancing() + public KernelResult Sleep(long timeout) { KernelContext.CriticalSection.Enter(); - if (SchedFlags != ThreadSchedState.Running) + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) { KernelContext.CriticalSection.Leave(); - KernelContext.Scheduler.ContextSwitch(); - - return; - } - - int prio = DynamicPriority; - int core = CurrentCore; - - KThread nextThreadOnCurrentQueue = null; - - if (DynamicPriority < KScheduler.PrioritiesCount) - { - // Move current thread to the end of the queue. - _schedulingData.Reschedule(prio, core, this); - - Func<KThread, bool> predicate = x => x.DynamicPriority == prio; - - nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); - } - - IEnumerable<KThread> SuitableCandidates() - { - foreach (KThread thread in _schedulingData.SuggestedThreads(core)) - { - int srcCore = thread.CurrentCore; - - if (srcCore >= 0) - { - KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread; - - if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2)) - { - continue; - } - } - - // If the candidate was scheduled after the current thread, then it's not worth it, - // unless the priority is higher than the current one. - if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime || - nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority) - { - yield return thread; - } - } + return KernelResult.ThreadTerminating; } - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); - - if (dst != null) - { - _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); - - _scheduler.ThreadReselectionRequested = true; - } + SetNewSchedFlags(ThreadSchedState.Paused); - if (this != nextThreadOnCurrentQueue) + if (timeout > 0) { - _scheduler.ThreadReselectionRequested = true; + KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout); } KernelContext.CriticalSection.Leave(); - KernelContext.Scheduler.ContextSwitch(); - } - - public void YieldAndWaitForLoadBalancing() - { - KernelContext.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); - - return; - } - - int core = CurrentCore; - - _schedulingData.TransferToCore(DynamicPriority, -1, this); - - KThread selectedThread = null; - - if (!_schedulingData.ScheduledThreads(core).Any()) - { - foreach (KThread thread in _schedulingData.SuggestedThreads(core)) - { - if (thread.CurrentCore < 0) - { - continue; - } - - KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault(); - - if (firstCandidate == thread) - { - continue; - } - - if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) - { - _schedulingData.TransferToCore(thread.DynamicPriority, core, thread); - - selectedThread = thread; - } - - break; - } - } - - if (selectedThread != this) + if (timeout > 0) { - _scheduler.ThreadReselectionRequested = true; + KernelContext.TimeManager.UnscheduleFutureInvocation(this); } - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); + return 0; } public void SetPriority(int priority) @@ -751,17 +602,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (oldAffinityMask != newAffinityMask) { - int oldCore = CurrentCore; + int oldCore = ActiveCore; - if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) + if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0) { if (PreferredCore < 0) { - CurrentCore = HighestSetCore(AffinityMask); + ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask); } else { - CurrentCore = PreferredCore; + ActiveCore = PreferredCore; } } @@ -774,19 +625,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return KernelResult.Success; } - private static int HighestSetCore(long mask) - { - for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--) - { - if (((mask >> core) & 1) != 0) - { - return core; - } - } - - return -1; - } - private void CombineForcePauseFlags() { ThreadSchedState oldFlags = SchedFlags; @@ -995,92 +833,112 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return; } + if (!IsSchedulable) + { + // Ensure our thread is running and we have an event. + StartHostThread(); + + // If the thread is not schedulable, we want to just run or pause + // it directly as we don't care about priority or the core it is + // running on in this case. + if (SchedFlags == ThreadSchedState.Running) + { + _schedulerWaitEvent.Set(); + } + else + { + _schedulerWaitEvent.Reset(); + } + + return; + } + if (oldFlags == ThreadSchedState.Running) { // Was running, now it's stopped. - if (CurrentCore >= 0) + if (ActiveCore >= 0) { - _schedulingData.Unschedule(DynamicPriority, CurrentCore, this); + KernelContext.PriorityQueue.Unschedule(DynamicPriority, ActiveCore, this); } for (int core = 0; core < KScheduler.CpuCoresCount; core++) { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) { - _schedulingData.Unsuggest(DynamicPriority, core, this); + KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this); } } } else if (SchedFlags == ThreadSchedState.Running) { // Was stopped, now it's running. - if (CurrentCore >= 0) + if (ActiveCore >= 0) { - _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this); } for (int core = 0; core < KScheduler.CpuCoresCount; core++) { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) { - _schedulingData.Suggest(DynamicPriority, core, this); + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); } } } - _scheduler.ThreadReselectionRequested = true; + KernelContext.ThreadReselectionRequested = true; } private void AdjustSchedulingForNewPriority(int oldPriority) { - if (SchedFlags != ThreadSchedState.Running) + if (SchedFlags != ThreadSchedState.Running || !IsSchedulable) { return; } // Remove thread from the old priority queues. - if (CurrentCore >= 0) + if (ActiveCore >= 0) { - _schedulingData.Unschedule(oldPriority, CurrentCore, this); + KernelContext.PriorityQueue.Unschedule(oldPriority, ActiveCore, this); } for (int core = 0; core < KScheduler.CpuCoresCount; core++) { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) { - _schedulingData.Unsuggest(oldPriority, core, this); + KernelContext.PriorityQueue.Unsuggest(oldPriority, core, this); } } // Add thread to the new priority queues. - KThread currentThread = _scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); - if (CurrentCore >= 0) + if (ActiveCore >= 0) { if (currentThread == this) { - _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); + KernelContext.PriorityQueue.SchedulePrepend(DynamicPriority, ActiveCore, this); } else { - _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this); } } for (int core = 0; core < KScheduler.CpuCoresCount; core++) { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) { - _schedulingData.Suggest(DynamicPriority, core, this); + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); } } - _scheduler.ThreadReselectionRequested = true; + KernelContext.ThreadReselectionRequested = true; } private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore) { - if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount || !IsSchedulable) { return; } @@ -1092,11 +950,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { if (core == oldCore) { - _schedulingData.Unschedule(DynamicPriority, core, this); + KernelContext.PriorityQueue.Unschedule(DynamicPriority, core, this); } else { - _schedulingData.Unsuggest(DynamicPriority, core, this); + KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this); } } } @@ -1106,18 +964,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { if (((AffinityMask >> core) & 1) != 0) { - if (core == CurrentCore) + if (core == ActiveCore) { - _schedulingData.Schedule(DynamicPriority, core, this); + KernelContext.PriorityQueue.Schedule(DynamicPriority, core, this); } else { - _schedulingData.Suggest(DynamicPriority, core, this); + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); } } } - _scheduler.ThreadReselectionRequested = true; + KernelContext.ThreadReselectionRequested = true; } public void SetEntryArguments(long argsPtr, int threadHandle) @@ -1141,17 +999,32 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n"); } - public void Execute() + public void AddCpuTime(long ticks) + { + Interlocked.Add(ref _totalTimeRunning, ticks); + } + + public void StartHostThread() { - if (Interlocked.CompareExchange(ref _hostThreadRunning, 1, 0) == 0) + if (_schedulerWaitEvent == null) { - HostThread.Start(); + var schedulerWaitEvent = new ManualResetEvent(false); + + if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null) + { + HostThread.Start(); + } + else + { + schedulerWaitEvent.Dispose(); + } } } private void ThreadStart() { - KernelStatic.SetKernelContext(KernelContext); + _schedulerWaitEvent.WaitOne(); + KernelStatic.SetKernelContext(KernelContext, this); if (_customThreadStart != null) { @@ -1162,20 +1035,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading Owner.Context.Execute(Context, _entrypoint); } - KernelContext.Scheduler.ExitThread(this); - KernelContext.Scheduler.RemoveThread(this); - Context.Dispose(); + _schedulerWaitEvent.Dispose(); } - public bool IsCurrentHostThread() + public void MakeUnschedulable() { - return Thread.CurrentThread == HostThread; + _forcedUnschedulable = true; } public override bool IsSignaled() { - return _hasExited; + return _hasExited != 0; } protected override void Destroy() |