|
@@ -28,42 +28,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
|
|
|
|
|
private SchedulingState _state;
|
|
private SchedulingState _state;
|
|
|
|
|
|
|
|
- private AutoResetEvent _idleInterruptEvent;
|
|
|
|
|
- private readonly object _idleInterruptEventLock;
|
|
|
|
|
-
|
|
|
|
|
private KThread _previousThread;
|
|
private KThread _previousThread;
|
|
|
private KThread _currentThread;
|
|
private KThread _currentThread;
|
|
|
- private readonly KThread _idleThread;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ private int _coreIdleLock;
|
|
|
|
|
+ private bool _idleSignalled = true;
|
|
|
|
|
+ private bool _idleActive = true;
|
|
|
|
|
+ private long _idleTimeRunning;
|
|
|
|
|
|
|
|
public KThread PreviousThread => _previousThread;
|
|
public KThread PreviousThread => _previousThread;
|
|
|
public KThread CurrentThread => _currentThread;
|
|
public KThread CurrentThread => _currentThread;
|
|
|
public long LastContextSwitchTime { get; private set; }
|
|
public long LastContextSwitchTime { get; private set; }
|
|
|
- public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
|
|
|
|
|
|
|
+ public long TotalIdleTimeTicks => _idleTimeRunning;
|
|
|
|
|
|
|
|
public KScheduler(KernelContext context, int coreId)
|
|
public KScheduler(KernelContext context, int coreId)
|
|
|
{
|
|
{
|
|
|
_context = context;
|
|
_context = context;
|
|
|
_coreId = coreId;
|
|
_coreId = coreId;
|
|
|
|
|
|
|
|
- _idleInterruptEvent = new AutoResetEvent(false);
|
|
|
|
|
- _idleInterruptEventLock = new object();
|
|
|
|
|
-
|
|
|
|
|
- KThread idleThread = CreateIdleThread(context, coreId);
|
|
|
|
|
-
|
|
|
|
|
- _currentThread = idleThread;
|
|
|
|
|
- _idleThread = idleThread;
|
|
|
|
|
-
|
|
|
|
|
- idleThread.StartHostThread();
|
|
|
|
|
- idleThread.SchedulerWaitEvent.Set();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private KThread CreateIdleThread(KernelContext context, int cpuCore)
|
|
|
|
|
- {
|
|
|
|
|
- KThread idleThread = new(context);
|
|
|
|
|
-
|
|
|
|
|
- idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop);
|
|
|
|
|
-
|
|
|
|
|
- return idleThread;
|
|
|
|
|
|
|
+ _currentThread = null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public static ulong SelectThreads(KernelContext context)
|
|
public static ulong SelectThreads(KernelContext context)
|
|
@@ -237,39 +220,64 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
|
|
KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
|
|
|
|
|
|
|
|
// Request the thread running on that core to stop and reschedule, if we have one.
|
|
// Request the thread running on that core to stop and reschedule, if we have one.
|
|
|
- if (threadToSignal != context.Schedulers[coreToSignal]._idleThread)
|
|
|
|
|
- {
|
|
|
|
|
- threadToSignal.Context.RequestInterrupt();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ threadToSignal?.Context.RequestInterrupt();
|
|
|
|
|
|
|
|
// If the core is idle, ensure that the idle thread is awaken.
|
|
// If the core is idle, ensure that the idle thread is awaken.
|
|
|
- context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
|
|
|
|
|
|
|
+ context.Schedulers[coreToSignal].NotifyIdleThread();
|
|
|
|
|
|
|
|
scheduledCoresMask &= ~(1UL << coreToSignal);
|
|
scheduledCoresMask &= ~(1UL << coreToSignal);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void IdleThreadLoop()
|
|
|
|
|
|
|
+ private void ActivateIdleThread()
|
|
|
|
|
+ {
|
|
|
|
|
+ while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ Thread.SpinWait(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Thread.MemoryBarrier();
|
|
|
|
|
+
|
|
|
|
|
+ // Signals that idle thread is now active on this core.
|
|
|
|
|
+ _idleActive = true;
|
|
|
|
|
+
|
|
|
|
|
+ TryLeaveIdle();
|
|
|
|
|
+
|
|
|
|
|
+ Interlocked.Exchange(ref _coreIdleLock, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void NotifyIdleThread()
|
|
|
|
|
+ {
|
|
|
|
|
+ while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ Thread.SpinWait(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Thread.MemoryBarrier();
|
|
|
|
|
+
|
|
|
|
|
+ // Signals that the idle core may be able to exit idle.
|
|
|
|
|
+ _idleSignalled = true;
|
|
|
|
|
+
|
|
|
|
|
+ TryLeaveIdle();
|
|
|
|
|
+
|
|
|
|
|
+ Interlocked.Exchange(ref _coreIdleLock, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void TryLeaveIdle()
|
|
|
{
|
|
{
|
|
|
- while (_context.Running)
|
|
|
|
|
|
|
+ if (_idleSignalled && _idleActive)
|
|
|
{
|
|
{
|
|
|
_state.NeedsScheduling = false;
|
|
_state.NeedsScheduling = false;
|
|
|
Thread.MemoryBarrier();
|
|
Thread.MemoryBarrier();
|
|
|
- KThread nextThread = PickNextThread(_state.SelectedThread);
|
|
|
|
|
|
|
+ KThread nextThread = PickNextThread(null, _state.SelectedThread);
|
|
|
|
|
|
|
|
- if (_idleThread != nextThread)
|
|
|
|
|
|
|
+ if (nextThread != null)
|
|
|
{
|
|
{
|
|
|
- _idleThread.SchedulerWaitEvent.Reset();
|
|
|
|
|
- WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
|
|
|
|
|
|
|
+ _idleActive = false;
|
|
|
|
|
+ nextThread.SchedulerWaitEvent.Set();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- _idleInterruptEvent.WaitOne();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- lock (_idleInterruptEventLock)
|
|
|
|
|
- {
|
|
|
|
|
- _idleInterruptEvent.Dispose();
|
|
|
|
|
- _idleInterruptEvent = null;
|
|
|
|
|
|
|
+ _idleSignalled = false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -292,20 +300,37 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
// Wake all the threads that might be waiting until this thread context is unlocked.
|
|
// Wake all the threads that might be waiting until this thread context is unlocked.
|
|
|
for (int core = 0; core < CpuCoresCount; core++)
|
|
for (int core = 0; core < CpuCoresCount; core++)
|
|
|
{
|
|
{
|
|
|
- _context.Schedulers[core]._idleInterruptEvent.Set();
|
|
|
|
|
|
|
+ _context.Schedulers[core].NotifyIdleThread();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- KThread nextThread = PickNextThread(selectedThread);
|
|
|
|
|
|
|
+ KThread nextThread = PickNextThread(KernelStatic.GetCurrentThread(), selectedThread);
|
|
|
|
|
|
|
|
if (currentThread.Context.Running)
|
|
if (currentThread.Context.Running)
|
|
|
{
|
|
{
|
|
|
// Wait until this thread is scheduled again, and allow the next thread to run.
|
|
// Wait until this thread is scheduled again, and allow the next thread to run.
|
|
|
- WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (nextThread == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ActivateIdleThread();
|
|
|
|
|
+ currentThread.SchedulerWaitEvent.WaitOne();
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// Allow the next thread to run.
|
|
// Allow the next thread to run.
|
|
|
- nextThread.SchedulerWaitEvent.Set();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (nextThread == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ActivateIdleThread();
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ nextThread.SchedulerWaitEvent.Set();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// We don't need to wait since the thread is exiting, however we need to
|
|
// We don't need to wait since the thread is exiting, however we need to
|
|
|
// make sure this thread will never call the scheduler again, since it is
|
|
// make sure this thread will never call the scheduler again, since it is
|
|
@@ -319,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private KThread PickNextThread(KThread selectedThread)
|
|
|
|
|
|
|
+ private KThread PickNextThread(KThread currentThread, KThread selectedThread)
|
|
|
{
|
|
{
|
|
|
while (true)
|
|
while (true)
|
|
|
{
|
|
{
|
|
@@ -335,7 +360,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
// on the core, as the scheduled thread will handle the next switch.
|
|
// on the core, as the scheduled thread will handle the next switch.
|
|
|
if (selectedThread.ThreadContext.Lock())
|
|
if (selectedThread.ThreadContext.Lock())
|
|
|
{
|
|
{
|
|
|
- SwitchTo(selectedThread);
|
|
|
|
|
|
|
+ SwitchTo(currentThread, selectedThread);
|
|
|
|
|
|
|
|
if (!_state.NeedsScheduling)
|
|
if (!_state.NeedsScheduling)
|
|
|
{
|
|
{
|
|
@@ -346,15 +371,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- return _idleThread;
|
|
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// The core is idle now, make sure that the idle thread can run
|
|
// The core is idle now, make sure that the idle thread can run
|
|
|
// and switch the core when a thread is available.
|
|
// and switch the core when a thread is available.
|
|
|
- SwitchTo(null);
|
|
|
|
|
- return _idleThread;
|
|
|
|
|
|
|
+ SwitchTo(currentThread, null);
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_state.NeedsScheduling = false;
|
|
_state.NeedsScheduling = false;
|
|
@@ -363,12 +388,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void SwitchTo(KThread nextThread)
|
|
|
|
|
|
|
+ private void SwitchTo(KThread currentThread, KThread nextThread)
|
|
|
{
|
|
{
|
|
|
- KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
|
|
|
|
- KThread currentThread = KernelStatic.GetCurrentThread();
|
|
|
|
|
-
|
|
|
|
|
- nextThread ??= _idleThread;
|
|
|
|
|
|
|
+ KProcess currentProcess = currentThread?.Owner;
|
|
|
|
|
|
|
|
if (currentThread != nextThread)
|
|
if (currentThread != nextThread)
|
|
|
{
|
|
{
|
|
@@ -376,7 +398,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
long currentTicks = PerformanceCounter.ElapsedTicks;
|
|
long currentTicks = PerformanceCounter.ElapsedTicks;
|
|
|
long ticksDelta = currentTicks - previousTicks;
|
|
long ticksDelta = currentTicks - previousTicks;
|
|
|
|
|
|
|
|
- currentThread.AddCpuTime(ticksDelta);
|
|
|
|
|
|
|
+ if (currentThread == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ Interlocked.Add(ref _idleTimeRunning, ticksDelta);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ currentThread.AddCpuTime(ticksDelta);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
currentProcess?.AddCpuTime(ticksDelta);
|
|
currentProcess?.AddCpuTime(ticksDelta);
|
|
|
|
|
|
|
@@ -386,13 +415,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
{
|
|
{
|
|
|
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
|
|
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
|
|
|
}
|
|
}
|
|
|
- else if (currentThread == _idleThread)
|
|
|
|
|
|
|
+ else if (currentThread == null)
|
|
|
{
|
|
{
|
|
|
_previousThread = null;
|
|
_previousThread = null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (nextThread.CurrentCore != _coreId)
|
|
|
|
|
|
|
+ if (nextThread != null && nextThread.CurrentCore != _coreId)
|
|
|
{
|
|
{
|
|
|
nextThread.CurrentCore = _coreId;
|
|
nextThread.CurrentCore = _coreId;
|
|
|
}
|
|
}
|
|
@@ -645,11 +674,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|
|
|
|
|
|
|
public void Dispose()
|
|
public void Dispose()
|
|
|
{
|
|
{
|
|
|
- // Ensure that the idle thread is not blocked and can exit.
|
|
|
|
|
- lock (_idleInterruptEventLock)
|
|
|
|
|
- {
|
|
|
|
|
- _idleInterruptEvent?.Set();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // No resources to dispose for now.
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|