Refactor SVC handler (#540)
* Refactor SVC handler * Get rid of KernelErr * Split kernel code files into multiple folders
This commit is contained in:
parent
2534a7f10c
commit
0039bb6394
105 changed files with 1894 additions and 1982 deletions
9
Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
Normal file
9
Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
enum ArbitrationType
|
||||
{
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2
|
||||
}
|
||||
}
|
66
Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs
Normal file
66
Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class HleCoreManager
|
||||
{
|
||||
private class PausableThread
|
||||
{
|
||||
public ManualResetEvent Event { get; private set; }
|
||||
|
||||
public bool IsExiting { get; set; }
|
||||
|
||||
public PausableThread()
|
||||
{
|
||||
Event = new ManualResetEvent(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<Thread, PausableThread> _threads;
|
||||
|
||||
public HleCoreManager()
|
||||
{
|
||||
_threads = new ConcurrentDictionary<Thread, PausableThread>();
|
||||
}
|
||||
|
||||
public void Set(Thread thread)
|
||||
{
|
||||
GetThread(thread).Event.Set();
|
||||
}
|
||||
|
||||
public void Reset(Thread thread)
|
||||
{
|
||||
GetThread(thread).Event.Reset();
|
||||
}
|
||||
|
||||
public void Wait(Thread thread)
|
||||
{
|
||||
PausableThread pausableThread = GetThread(thread);
|
||||
|
||||
if (!pausableThread.IsExiting)
|
||||
{
|
||||
pausableThread.Event.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit(Thread thread)
|
||||
{
|
||||
GetThread(thread).IsExiting = true;
|
||||
}
|
||||
|
||||
private PausableThread GetThread(Thread thread)
|
||||
{
|
||||
return _threads.GetOrAdd(thread, (key) => new PausableThread());
|
||||
}
|
||||
|
||||
public void RemoveThread(Thread thread)
|
||||
{
|
||||
if (_threads.TryRemove(thread, out PausableThread pausableThread))
|
||||
{
|
||||
pausableThread.Event.Set();
|
||||
pausableThread.Event.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs
Normal file
149
Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs
Normal file
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
partial class KScheduler
|
||||
{
|
||||
private const int RoundRobinTimeQuantumMs = 10;
|
||||
|
||||
private int _currentCore;
|
||||
|
||||
public bool MultiCoreScheduling { get; set; }
|
||||
|
||||
public HleCoreManager CoreManager { get; private set; }
|
||||
|
||||
private bool _keepPreempting;
|
||||
|
||||
public void StartAutoPreemptionThread()
|
||||
{
|
||||
Thread preemptionThread = new Thread(PreemptCurrentThread);
|
||||
|
||||
_keepPreempting = true;
|
||||
|
||||
preemptionThread.Start();
|
||||
}
|
||||
|
||||
public void ContextSwitch()
|
||||
{
|
||||
lock (CoreContexts)
|
||||
{
|
||||
if (MultiCoreScheduling)
|
||||
{
|
||||
int selectedCount = 0;
|
||||
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
KCoreContext coreContext = CoreContexts[core];
|
||||
|
||||
if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false))
|
||||
{
|
||||
coreContext.ContextSwitch();
|
||||
}
|
||||
|
||||
if (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false)
|
||||
{
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCount == 0)
|
||||
{
|
||||
CoreManager.Reset(Thread.CurrentThread);
|
||||
}
|
||||
else if (selectedCount == 1)
|
||||
{
|
||||
CoreManager.Set(Thread.CurrentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Thread scheduled in more than one core!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KThread currentThread = CoreContexts[_currentCore].CurrentThread;
|
||||
|
||||
bool hasThreadExecuting = currentThread != null;
|
||||
|
||||
if (hasThreadExecuting)
|
||||
{
|
||||
//If this is not the thread that is currently executing, we need
|
||||
//to request an interrupt to allow safely starting another thread.
|
||||
if (!currentThread.Context.IsCurrentThread())
|
||||
{
|
||||
currentThread.Context.RequestInterrupt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CoreManager.Reset(currentThread.Context.Work);
|
||||
}
|
||||
|
||||
//Advance current core and try picking a thread,
|
||||
//keep advancing if it is null.
|
||||
for (int core = 0; core < 4; core++)
|
||||
{
|
||||
_currentCore = (_currentCore + 1) % CpuCoresCount;
|
||||
|
||||
KCoreContext coreContext = CoreContexts[_currentCore];
|
||||
|
||||
coreContext.UpdateCurrentThread();
|
||||
|
||||
if (coreContext.CurrentThread != null)
|
||||
{
|
||||
coreContext.CurrentThread.ClearExclusive();
|
||||
|
||||
CoreManager.Set(coreContext.CurrentThread.Context.Work);
|
||||
|
||||
coreContext.CurrentThread.Context.Execute();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//If nothing was running before, then we are on a "external"
|
||||
//HLE thread, we don't need to wait.
|
||||
if (!hasThreadExecuting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CoreManager.Wait(Thread.CurrentThread);
|
||||
}
|
||||
|
||||
private void PreemptCurrentThread()
|
||||
{
|
||||
//Preempts current thread every 10 milliseconds on a round-robin fashion,
|
||||
//when multi core scheduling is disabled, to try ensuring that all threads
|
||||
//gets a chance to run.
|
||||
while (_keepPreempting)
|
||||
{
|
||||
lock (CoreContexts)
|
||||
{
|
||||
KThread currentThread = CoreContexts[_currentCore].CurrentThread;
|
||||
|
||||
currentThread?.Context.RequestInterrupt();
|
||||
}
|
||||
|
||||
PreemptThreads();
|
||||
|
||||
Thread.Sleep(RoundRobinTimeQuantumMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitThread(KThread thread)
|
||||
{
|
||||
thread.Context.StopExecution();
|
||||
|
||||
CoreManager.Exit(thread.Context.Work);
|
||||
}
|
||||
|
||||
public void RemoveThread(KThread thread)
|
||||
{
|
||||
CoreManager.RemoveThread(thread.Context.Work);
|
||||
}
|
||||
}
|
||||
}
|
654
Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
Normal file
654
Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
Normal file
|
@ -0,0 +1,654 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KAddressArbiter
|
||||
{
|
||||
private const int HasListenersMask = 0x40000000;
|
||||
|
||||
private Horizon _system;
|
||||
|
||||
public List<KThread> CondVarThreads;
|
||||
public List<KThread> ArbiterThreads;
|
||||
|
||||
public KAddressArbiter(Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
|
||||
CondVarThreads = new List<KThread>();
|
||||
ArbiterThreads = new List<KThread>();
|
||||
}
|
||||
|
||||
public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
||||
{
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
currentThread.SignaledObj = null;
|
||||
currentThread.ObjSyncResult = KernelResult.Success;
|
||||
|
||||
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, mutexAddress, out int mutexValue))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
if (mutexValue != (ownerHandle | HasListenersMask))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(ownerHandle);
|
||||
|
||||
if (mutexOwner == null)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
currentThread.MutexAddress = mutexAddress;
|
||||
currentThread.ThreadHandleForUserMutex = requesterHandle;
|
||||
|
||||
mutexOwner.AddMutexWaiter(currentThread);
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.MutexOwner != null)
|
||||
{
|
||||
currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return (KernelResult)currentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
public KernelResult ArbitrateUnlock(ulong mutexAddress)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
(KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress);
|
||||
|
||||
if (result != KernelResult.Success && newOwnerThread != null)
|
||||
{
|
||||
newOwnerThread.SignaledObj = null;
|
||||
newOwnerThread.ObjSyncResult = result;
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public KernelResult WaitProcessWideKeyAtomic(
|
||||
ulong mutexAddress,
|
||||
ulong condVarAddress,
|
||||
int threadHandle,
|
||||
long timeout)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
currentThread.SignaledObj = null;
|
||||
currentThread.ObjSyncResult = KernelResult.TimedOut;
|
||||
|
||||
if (currentThread.ShallBeTerminated ||
|
||||
currentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.ThreadTerminating;
|
||||
}
|
||||
|
||||
(KernelResult result, _) = MutexUnlock(currentThread, mutexAddress);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
currentThread.MutexAddress = mutexAddress;
|
||||
currentThread.ThreadHandleForUserMutex = threadHandle;
|
||||
currentThread.CondVarAddress = condVarAddress;
|
||||
|
||||
CondVarThreads.Add(currentThread);
|
||||
|
||||
if (timeout != 0)
|
||||
{
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.UnscheduleFutureInvocation(currentThread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.MutexOwner != null)
|
||||
{
|
||||
currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
|
||||
}
|
||||
|
||||
CondVarThreads.Remove(currentThread);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return (KernelResult)currentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
private (KernelResult, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress)
|
||||
{
|
||||
KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count);
|
||||
|
||||
int mutexValue = 0;
|
||||
|
||||
if (newOwnerThread != null)
|
||||
{
|
||||
mutexValue = newOwnerThread.ThreadHandleForUserMutex;
|
||||
|
||||
if (count >= 2)
|
||||
{
|
||||
mutexValue |= HasListenersMask;
|
||||
}
|
||||
|
||||
newOwnerThread.SignaledObj = null;
|
||||
newOwnerThread.ObjSyncResult = KernelResult.Success;
|
||||
|
||||
newOwnerThread.ReleaseAndResume();
|
||||
}
|
||||
|
||||
KernelResult result = KernelResult.Success;
|
||||
|
||||
if (!KernelTransfer.KernelToUserInt32(_system, mutexAddress, mutexValue))
|
||||
{
|
||||
result = KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
return (result, newOwnerThread);
|
||||
}
|
||||
|
||||
public void SignalProcessWideKey(ulong address, int count)
|
||||
{
|
||||
Queue<KThread> signaledThreads = new Queue<KThread>();
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
IOrderedEnumerable<KThread> sortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority);
|
||||
|
||||
foreach (KThread thread in sortedThreads.Where(x => x.CondVarAddress == address))
|
||||
{
|
||||
TryAcquireMutex(thread);
|
||||
|
||||
signaledThreads.Enqueue(thread);
|
||||
|
||||
//If the count is <= 0, we should signal all threads waiting.
|
||||
if (count >= 1 && --count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (signaledThreads.TryDequeue(out KThread thread))
|
||||
{
|
||||
CondVarThreads.Remove(thread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
private KThread TryAcquireMutex(KThread requester)
|
||||
{
|
||||
ulong address = requester.MutexAddress;
|
||||
|
||||
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, address, out int mutexValue))
|
||||
{
|
||||
//Invalid address.
|
||||
currentProcess.CpuMemory.ClearExclusive(0);
|
||||
|
||||
requester.SignaledObj = null;
|
||||
requester.ObjSyncResult = KernelResult.InvalidMemState;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
|
||||
{
|
||||
if (mutexValue != 0)
|
||||
{
|
||||
//Update value to indicate there is a mutex waiter now.
|
||||
currentProcess.CpuMemory.WriteInt32((long)address, mutexValue | HasListenersMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
//No thread owning the mutex, assign to requesting thread.
|
||||
currentProcess.CpuMemory.WriteInt32((long)address, requester.ThreadHandleForUserMutex);
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
mutexValue = currentProcess.CpuMemory.ReadInt32((long)address);
|
||||
}
|
||||
|
||||
if (mutexValue == 0)
|
||||
{
|
||||
//We now own the mutex.
|
||||
requester.SignaledObj = null;
|
||||
requester.ObjSyncResult = KernelResult.Success;
|
||||
|
||||
requester.ReleaseAndResume();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
mutexValue &= ~HasListenersMask;
|
||||
|
||||
KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(mutexValue);
|
||||
|
||||
if (mutexOwner != null)
|
||||
{
|
||||
//Mutex already belongs to another thread, wait for it.
|
||||
mutexOwner.AddMutexWaiter(requester);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Invalid mutex owner.
|
||||
requester.SignaledObj = null;
|
||||
requester.ObjSyncResult = KernelResult.InvalidHandle;
|
||||
|
||||
requester.ReleaseAndResume();
|
||||
}
|
||||
|
||||
return mutexOwner;
|
||||
}
|
||||
|
||||
public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout)
|
||||
{
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.ShallBeTerminated ||
|
||||
currentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.ThreadTerminating;
|
||||
}
|
||||
|
||||
currentThread.SignaledObj = null;
|
||||
currentThread.ObjSyncResult = KernelResult.TimedOut;
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
if (currentValue == value)
|
||||
{
|
||||
if (timeout == 0)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.TimedOut;
|
||||
}
|
||||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
InsertSortedByPriority(ArbiterThreads, currentThread);
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.UnscheduleFutureInvocation(currentThread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
ArbiterThreads.Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return (KernelResult)currentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
public KernelResult WaitForAddressIfLessThan(
|
||||
ulong address,
|
||||
int value,
|
||||
bool shouldDecrement,
|
||||
long timeout)
|
||||
{
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.ShallBeTerminated ||
|
||||
currentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.ThreadTerminating;
|
||||
}
|
||||
|
||||
currentThread.SignaledObj = null;
|
||||
currentThread.ObjSyncResult = KernelResult.TimedOut;
|
||||
|
||||
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||
|
||||
//If ShouldDecrement is true, do atomic decrement of the value at Address.
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
if (shouldDecrement)
|
||||
{
|
||||
while (currentValue < value)
|
||||
{
|
||||
if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
|
||||
{
|
||||
currentProcess.CpuMemory.WriteInt32((long)address, currentValue - 1);
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
|
||||
}
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusive(0);
|
||||
|
||||
if (currentValue < value)
|
||||
{
|
||||
if (timeout == 0)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.TimedOut;
|
||||
}
|
||||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
InsertSortedByPriority(ArbiterThreads, currentThread);
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.UnscheduleFutureInvocation(currentThread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
ArbiterThreads.Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return (KernelResult)currentThread.ObjSyncResult;
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
private void InsertSortedByPriority(List<KThread> threads, KThread thread)
|
||||
{
|
||||
int nextIndex = -1;
|
||||
|
||||
for (int index = 0; index < threads.Count; index++)
|
||||
{
|
||||
if (threads[index].DynamicPriority > thread.DynamicPriority)
|
||||
{
|
||||
nextIndex = index;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIndex != -1)
|
||||
{
|
||||
threads.Insert(nextIndex, thread);
|
||||
}
|
||||
else
|
||||
{
|
||||
threads.Add(thread);
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult Signal(ulong address, int count)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
WakeArbiterThreads(address, count);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult SignalAndIncrementIfEqual(ulong address, int value, int count)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
while (currentValue == value)
|
||||
{
|
||||
if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
|
||||
{
|
||||
currentProcess.CpuMemory.WriteInt32((long)address, currentValue + 1);
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusive(0);
|
||||
|
||||
if (currentValue != value)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
WakeArbiterThreads(address, count);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult SignalAndModifyIfEqual(ulong address, int value, int count)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
int offset;
|
||||
|
||||
//The value is decremented if the number of threads waiting is less
|
||||
//or equal to the Count of threads to be signaled, or Count is zero
|
||||
//or negative. It is incremented if there are no threads waiting.
|
||||
int waitingCount = 0;
|
||||
|
||||
foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address))
|
||||
{
|
||||
if (++waitingCount > count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitingCount > 0)
|
||||
{
|
||||
offset = waitingCount <= count || count <= 0 ? -1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue))
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
while (currentValue == value)
|
||||
{
|
||||
if (currentProcess.CpuMemory.TestExclusive(0, (long)address))
|
||||
{
|
||||
currentProcess.CpuMemory.WriteInt32((long)address, currentValue + offset);
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusiveForStore(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.SetExclusive(0, (long)address);
|
||||
|
||||
currentValue = currentProcess.CpuMemory.ReadInt32((long)address);
|
||||
}
|
||||
|
||||
currentProcess.CpuMemory.ClearExclusive(0);
|
||||
|
||||
if (currentValue != value)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
WakeArbiterThreads(address, count);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
private void WakeArbiterThreads(ulong address, int count)
|
||||
{
|
||||
Queue<KThread> signaledThreads = new Queue<KThread>();
|
||||
|
||||
foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address))
|
||||
{
|
||||
signaledThreads.Enqueue(thread);
|
||||
|
||||
//If the count is <= 0, we should signal all threads waiting.
|
||||
if (count >= 1 && --count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (signaledThreads.TryDequeue(out KThread thread))
|
||||
{
|
||||
thread.SignaledObj = null;
|
||||
thread.ObjSyncResult = KernelResult.Success;
|
||||
|
||||
thread.ReleaseAndResume();
|
||||
|
||||
thread.WaitingInArbitration = false;
|
||||
|
||||
ArbiterThreads.Remove(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
Normal file
71
Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
static class KConditionVariable
|
||||
{
|
||||
public static void Wait(Horizon system, LinkedList<KThread> threadList, object mutex, long timeout)
|
||||
{
|
||||
KThread currentThread = system.Scheduler.GetCurrentThread();
|
||||
|
||||
system.CriticalSection.Enter();
|
||||
|
||||
Monitor.Exit(mutex);
|
||||
|
||||
currentThread.Withholder = threadList;
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
currentThread.WithholderNode = threadList.AddLast(currentThread);
|
||||
|
||||
if (currentThread.ShallBeTerminated ||
|
||||
currentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
threadList.Remove(currentThread.WithholderNode);
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Running);
|
||||
|
||||
currentThread.Withholder = null;
|
||||
|
||||
system.CriticalSection.Leave();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timeout > 0)
|
||||
{
|
||||
system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||
}
|
||||
|
||||
system.CriticalSection.Leave();
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
system.TimeManager.UnscheduleFutureInvocation(currentThread);
|
||||
}
|
||||
}
|
||||
|
||||
Monitor.Enter(mutex);
|
||||
}
|
||||
|
||||
public static void NotifyAll(Horizon system, LinkedList<KThread> threadList)
|
||||
{
|
||||
system.CriticalSection.Enter();
|
||||
|
||||
LinkedListNode<KThread> node = threadList.First;
|
||||
|
||||
for (; node != null; node = threadList.First)
|
||||
{
|
||||
KThread thread = node.Value;
|
||||
|
||||
threadList.Remove(thread.WithholderNode);
|
||||
|
||||
thread.Withholder = null;
|
||||
|
||||
thread.Reschedule(ThreadSchedState.Running);
|
||||
}
|
||||
|
||||
system.CriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
}
|
81
Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs
Normal file
81
Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using Ryujinx.Common;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KCoreContext
|
||||
{
|
||||
private KScheduler _scheduler;
|
||||
|
||||
private HleCoreManager _coreManager;
|
||||
|
||||
public bool ContextSwitchNeeded { get; private set; }
|
||||
|
||||
public long LastContextSwitchTime { get; private set; }
|
||||
|
||||
public long TotalIdleTimeTicks { get; private set; } //TODO
|
||||
|
||||
public KThread CurrentThread { get; private set; }
|
||||
public KThread SelectedThread { get; private set; }
|
||||
|
||||
public KCoreContext(KScheduler scheduler, HleCoreManager coreManager)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
_coreManager = coreManager;
|
||||
}
|
||||
|
||||
public void SelectThread(KThread thread)
|
||||
{
|
||||
SelectedThread = thread;
|
||||
|
||||
if (SelectedThread != CurrentThread)
|
||||
{
|
||||
ContextSwitchNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCurrentThread()
|
||||
{
|
||||
ContextSwitchNeeded = false;
|
||||
|
||||
LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
|
||||
|
||||
CurrentThread = SelectedThread;
|
||||
|
||||
if (CurrentThread != null)
|
||||
{
|
||||
long currentTime = PerformanceCounter.ElapsedMilliseconds;
|
||||
|
||||
CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
|
||||
CurrentThread.LastScheduledTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void ContextSwitch()
|
||||
{
|
||||
ContextSwitchNeeded = false;
|
||||
|
||||
LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
|
||||
|
||||
if (CurrentThread != null)
|
||||
{
|
||||
_coreManager.Reset(CurrentThread.Context.Work);
|
||||
}
|
||||
|
||||
CurrentThread = SelectedThread;
|
||||
|
||||
if (CurrentThread != null)
|
||||
{
|
||||
long currentTime = PerformanceCounter.ElapsedMilliseconds;
|
||||
|
||||
CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
|
||||
CurrentThread.LastScheduledTime = currentTime;
|
||||
|
||||
CurrentThread.ClearExclusive();
|
||||
|
||||
_coreManager.Set(CurrentThread.Context.Work);
|
||||
|
||||
CurrentThread.Context.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
Normal file
93
Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using ChocolArm64;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KCriticalSection
|
||||
{
|
||||
private Horizon _system;
|
||||
|
||||
public object LockObj { get; private set; }
|
||||
|
||||
private int _recursionCount;
|
||||
|
||||
public KCriticalSection(Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
|
||||
LockObj = new object();
|
||||
}
|
||||
|
||||
public void Enter()
|
||||
{
|
||||
Monitor.Enter(LockObj);
|
||||
|
||||
_recursionCount++;
|
||||
}
|
||||
|
||||
public void Leave()
|
||||
{
|
||||
if (_recursionCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool doContextSwitch = false;
|
||||
|
||||
if (--_recursionCount == 0)
|
||||
{
|
||||
if (_system.Scheduler.ThreadReselectionRequested)
|
||||
{
|
||||
_system.Scheduler.SelectThreads();
|
||||
}
|
||||
|
||||
Monitor.Exit(LockObj);
|
||||
|
||||
if (_system.Scheduler.MultiCoreScheduling)
|
||||
{
|
||||
lock (_system.Scheduler.CoreContexts)
|
||||
{
|
||||
for (int core = 0; core < KScheduler.CpuCoresCount; core++)
|
||||
{
|
||||
KCoreContext coreContext = _system.Scheduler.CoreContexts[core];
|
||||
|
||||
if (coreContext.ContextSwitchNeeded)
|
||||
{
|
||||
CpuThread currentHleThread = coreContext.CurrentThread?.Context;
|
||||
|
||||
if (currentHleThread == null)
|
||||
{
|
||||
//Nothing is running, we can perform the context switch immediately.
|
||||
coreContext.ContextSwitch();
|
||||
}
|
||||
else if (currentHleThread.IsCurrentThread())
|
||||
{
|
||||
//Thread running on the current core, context switch will block.
|
||||
doContextSwitch = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Thread running on another core, request a interrupt.
|
||||
currentHleThread.RequestInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
doContextSwitch = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Monitor.Exit(LockObj);
|
||||
}
|
||||
|
||||
if (doContextSwitch)
|
||||
{
|
||||
_system.Scheduler.ContextSwitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
Normal file
14
Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KEvent
|
||||
{
|
||||
public KReadableEvent ReadableEvent { get; private set; }
|
||||
public KWritableEvent WritableEvent { get; private set; }
|
||||
|
||||
public KEvent(Horizon system)
|
||||
{
|
||||
ReadableEvent = new KReadableEvent(system, this);
|
||||
WritableEvent = new KWritableEvent(this);
|
||||
}
|
||||
}
|
||||
}
|
64
Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
Normal file
64
Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KReadableEvent : KSynchronizationObject
|
||||
{
|
||||
private KEvent _parent;
|
||||
|
||||
private bool _signaled;
|
||||
|
||||
public KReadableEvent(Horizon system, KEvent parent) : base(system)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public override void Signal()
|
||||
{
|
||||
System.CriticalSection.Enter();
|
||||
|
||||
if (!_signaled)
|
||||
{
|
||||
_signaled = true;
|
||||
|
||||
base.Signal();
|
||||
}
|
||||
|
||||
System.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public KernelResult Clear()
|
||||
{
|
||||
_signaled = false;
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult ClearIfSignaled()
|
||||
{
|
||||
KernelResult result;
|
||||
|
||||
System.CriticalSection.Enter();
|
||||
|
||||
if (_signaled)
|
||||
{
|
||||
_signaled = false;
|
||||
|
||||
result = KernelResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
System.CriticalSection.Leave();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsSignaled()
|
||||
{
|
||||
return _signaled;
|
||||
}
|
||||
}
|
||||
}
|
234
Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
Normal file
234
Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
Normal file
|
@ -0,0 +1,234 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
partial class KScheduler : IDisposable
|
||||
{
|
||||
public const int PrioritiesCount = 64;
|
||||
public const int CpuCoresCount = 4;
|
||||
|
||||
private const int PreemptionPriorityCores012 = 59;
|
||||
private const int PreemptionPriorityCore3 = 63;
|
||||
|
||||
private Horizon _system;
|
||||
|
||||
public KSchedulingData SchedulingData { get; private set; }
|
||||
|
||||
public KCoreContext[] CoreContexts { get; private set; }
|
||||
|
||||
public bool ThreadReselectionRequested { get; set; }
|
||||
|
||||
public KScheduler(Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
|
||||
SchedulingData = new KSchedulingData();
|
||||
|
||||
CoreManager = new HleCoreManager();
|
||||
|
||||
CoreContexts = new KCoreContext[CpuCoresCount];
|
||||
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
CoreContexts[core] = new KCoreContext(this, CoreManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreemptThreads()
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
PreemptThread(PreemptionPriorityCores012, 0);
|
||||
PreemptThread(PreemptionPriorityCores012, 1);
|
||||
PreemptThread(PreemptionPriorityCores012, 2);
|
||||
PreemptThread(PreemptionPriorityCore3, 3);
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
private void PreemptThread(int prio, int core)
|
||||
{
|
||||
IEnumerable<KThread> scheduledThreads = SchedulingData.ScheduledThreads(core);
|
||||
|
||||
KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
|
||||
|
||||
//Yield priority queue.
|
||||
if (selectedThread != null)
|
||||
{
|
||||
SchedulingData.Reschedule(prio, core, selectedThread);
|
||||
}
|
||||
|
||||
IEnumerable<KThread> SuitableCandidates()
|
||||
{
|
||||
foreach (KThread thread in SchedulingData.SuggestedThreads(core))
|
||||
{
|
||||
int srcCore = thread.CurrentCore;
|
||||
|
||||
if (srcCore >= 0)
|
||||
{
|
||||
KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault();
|
||||
|
||||
if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (highestPrioSrcCore == thread)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//If the candidate was scheduled after the current thread, then it's not worth it.
|
||||
if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime)
|
||||
{
|
||||
yield return thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Select candidate threads that could run on this core.
|
||||
//Only take into account threads that are not yet selected.
|
||||
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
|
||||
|
||||
if (dst != null)
|
||||
{
|
||||
SchedulingData.TransferToCore(prio, core, dst);
|
||||
|
||||
selectedThread = dst;
|
||||
}
|
||||
|
||||
//If the priority of the currently selected thread is lower than preemption priority,
|
||||
//then allow threads with lower priorities to be selected aswell.
|
||||
if (selectedThread != null && selectedThread.DynamicPriority > prio)
|
||||
{
|
||||
Func<KThread, bool> predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority;
|
||||
|
||||
dst = SuitableCandidates().FirstOrDefault(predicate);
|
||||
|
||||
if (dst != null)
|
||||
{
|
||||
SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
|
||||
}
|
||||
}
|
||||
|
||||
ThreadReselectionRequested = true;
|
||||
}
|
||||
|
||||
public void SelectThreads()
|
||||
{
|
||||
ThreadReselectionRequested = false;
|
||||
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault();
|
||||
|
||||
CoreContexts[core].SelectThread(thread);
|
||||
}
|
||||
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
//If the core is not idle (there's already a thread running on it),
|
||||
//then we don't need to attempt load balancing.
|
||||
if (SchedulingData.ScheduledThreads(core).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
||||
|
||||
int srcCoresHighestPrioThreadsCount = 0;
|
||||
|
||||
KThread dst = null;
|
||||
|
||||
//Select candidate threads that could run on this core.
|
||||
//Give preference to threads that are not yet selected.
|
||||
foreach (KThread thread in SchedulingData.SuggestedThreads(core))
|
||||
{
|
||||
if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread)
|
||||
{
|
||||
dst = thread;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore;
|
||||
}
|
||||
|
||||
//Not yet selected candidate found.
|
||||
if (dst != null)
|
||||
{
|
||||
//Priorities < 2 are used for the kernel message dispatching
|
||||
//threads, we should skip load balancing entirely.
|
||||
if (dst.DynamicPriority >= 2)
|
||||
{
|
||||
SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
|
||||
|
||||
CoreContexts[core].SelectThread(dst);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
//All candiates are already selected, choose the best one
|
||||
//(the first one that doesn't make the source core idle if moved).
|
||||
for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
|
||||
{
|
||||
int srcCore = srcCoresHighestPrioThreads[index];
|
||||
|
||||
KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1);
|
||||
|
||||
if (src != null)
|
||||
{
|
||||
//Run the second thread on the queue on the source core,
|
||||
//move the first one to the current core.
|
||||
KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread;
|
||||
|
||||
CoreContexts[srcCore].SelectThread(src);
|
||||
|
||||
SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc);
|
||||
|
||||
CoreContexts[core].SelectThread(origSelectedCoreSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KThread GetCurrentThread()
|
||||
{
|
||||
lock (CoreContexts)
|
||||
{
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
if (CoreContexts[core].CurrentThread?.Context.IsCurrentThread() ?? false)
|
||||
{
|
||||
return CoreContexts[core].CurrentThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Current thread is not scheduled!");
|
||||
}
|
||||
|
||||
public KProcess GetCurrentProcess()
|
||||
{
|
||||
return GetCurrentThread().Owner;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_keepPreempting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs
Normal file
207
Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs
Normal file
|
@ -0,0 +1,207 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KSchedulingData
|
||||
{
|
||||
private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
|
||||
private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
|
||||
|
||||
private long[] _scheduledPrioritiesPerCore;
|
||||
private long[] _suggestedPrioritiesPerCore;
|
||||
|
||||
public KSchedulingData()
|
||||
{
|
||||
_suggestedThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
|
||||
_scheduledThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
|
||||
|
||||
for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++)
|
||||
{
|
||||
_suggestedThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
|
||||
_scheduledThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
|
||||
|
||||
for (int core = 0; core < KScheduler.CpuCoresCount; core++)
|
||||
{
|
||||
_suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
|
||||
_scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
|
||||
}
|
||||
}
|
||||
|
||||
_scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
|
||||
_suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
|
||||
}
|
||||
|
||||
public IEnumerable<KThread> SuggestedThreads(int core)
|
||||
{
|
||||
return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
|
||||
}
|
||||
|
||||
public IEnumerable<KThread> ScheduledThreads(int core)
|
||||
{
|
||||
return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
|
||||
}
|
||||
|
||||
private IEnumerable<KThread> Iterate(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||
{
|
||||
long prioMask = prios[core];
|
||||
|
||||
int prio = CountTrailingZeros(prioMask);
|
||||
|
||||
prioMask &= ~(1L << prio);
|
||||
|
||||
while (prio < KScheduler.PrioritiesCount)
|
||||
{
|
||||
LinkedList<KThread> list = listPerPrioPerCore[prio][core];
|
||||
|
||||
LinkedListNode<KThread> node = list.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
yield return node.Value;
|
||||
|
||||
node = node.Next;
|
||||
}
|
||||
|
||||
prio = CountTrailingZeros(prioMask);
|
||||
|
||||
prioMask &= ~(1L << prio);
|
||||
}
|
||||
}
|
||||
|
||||
private int CountTrailingZeros(long value)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (((value >> count) & 0xf) == 0 && count < 64)
|
||||
{
|
||||
count += 4;
|
||||
}
|
||||
|
||||
while (((value >> count) & 1) == 0 && count < 64)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void TransferToCore(int prio, int dstCore, KThread thread)
|
||||
{
|
||||
bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount;
|
||||
|
||||
int srcCore = thread.CurrentCore;
|
||||
|
||||
thread.CurrentCore = dstCore;
|
||||
|
||||
if (srcCore == dstCore || !schedulable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcCore >= 0)
|
||||
{
|
||||
Unschedule(prio, srcCore, thread);
|
||||
}
|
||||
|
||||
if (dstCore >= 0)
|
||||
{
|
||||
Unsuggest(prio, dstCore, thread);
|
||||
Schedule(prio, dstCore, thread);
|
||||
}
|
||||
|
||||
if (srcCore >= 0)
|
||||
{
|
||||
Suggest(prio, srcCore, thread);
|
||||
}
|
||||
}
|
||||
|
||||
public void Suggest(int prio, int core, KThread thread)
|
||||
{
|
||||
if (prio >= KScheduler.PrioritiesCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
|
||||
|
||||
_suggestedPrioritiesPerCore[core] |= 1L << prio;
|
||||
}
|
||||
|
||||
public void Unsuggest(int prio, int core, KThread thread)
|
||||
{
|
||||
if (prio >= KScheduler.PrioritiesCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedList<KThread> queue = SuggestedQueue(prio, core);
|
||||
|
||||
queue.Remove(thread.SiblingsPerCore[core]);
|
||||
|
||||
if (queue.First == null)
|
||||
{
|
||||
_suggestedPrioritiesPerCore[core] &= ~(1L << prio);
|
||||
}
|
||||
}
|
||||
|
||||
public void Schedule(int prio, int core, KThread thread)
|
||||
{
|
||||
if (prio >= KScheduler.PrioritiesCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
|
||||
|
||||
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
||||
}
|
||||
|
||||
public void SchedulePrepend(int prio, int core, KThread thread)
|
||||
{
|
||||
if (prio >= KScheduler.PrioritiesCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
|
||||
|
||||
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
||||
}
|
||||
|
||||
public void Reschedule(int prio, int core, KThread thread)
|
||||
{
|
||||
LinkedList<KThread> queue = ScheduledQueue(prio, core);
|
||||
|
||||
queue.Remove(thread.SiblingsPerCore[core]);
|
||||
|
||||
thread.SiblingsPerCore[core] = queue.AddLast(thread);
|
||||
}
|
||||
|
||||
public void Unschedule(int prio, int core, KThread thread)
|
||||
{
|
||||
if (prio >= KScheduler.PrioritiesCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedList<KThread> queue = ScheduledQueue(prio, core);
|
||||
|
||||
queue.Remove(thread.SiblingsPerCore[core]);
|
||||
|
||||
if (queue.First == null)
|
||||
{
|
||||
_scheduledPrioritiesPerCore[core] &= ~(1L << prio);
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedList<KThread> SuggestedQueue(int prio, int core)
|
||||
{
|
||||
return _suggestedThreadsPerPrioPerCore[prio][core];
|
||||
}
|
||||
|
||||
private LinkedList<KThread> ScheduledQueue(int prio, int core)
|
||||
{
|
||||
return _scheduledThreadsPerPrioPerCore[prio][core];
|
||||
}
|
||||
}
|
||||
}
|
136
Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
Normal file
136
Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
Normal file
|
@ -0,0 +1,136 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KSynchronization
|
||||
{
|
||||
private Horizon _system;
|
||||
|
||||
public KSynchronization(Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
}
|
||||
|
||||
public KernelResult WaitFor(KSynchronizationObject[] syncObjs, long timeout, out int handleIndex)
|
||||
{
|
||||
handleIndex = 0;
|
||||
|
||||
KernelResult result = KernelResult.TimedOut;
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
//Check if objects are already signaled before waiting.
|
||||
for (int index = 0; index < syncObjs.Length; index++)
|
||||
{
|
||||
if (!syncObjs[index].IsSignaled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
handleIndex = index;
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (timeout == 0)
|
||||
{
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
KThread currentThread = _system.Scheduler.GetCurrentThread();
|
||||
|
||||
if (currentThread.ShallBeTerminated ||
|
||||
currentThread.SchedFlags == ThreadSchedState.TerminationPending)
|
||||
{
|
||||
result = KernelResult.ThreadTerminating;
|
||||
}
|
||||
else if (currentThread.SyncCancelled)
|
||||
{
|
||||
currentThread.SyncCancelled = false;
|
||||
|
||||
result = KernelResult.Cancelled;
|
||||
}
|
||||
else
|
||||
{
|
||||
LinkedListNode<KThread>[] syncNodes = new LinkedListNode<KThread>[syncObjs.Length];
|
||||
|
||||
for (int index = 0; index < syncObjs.Length; index++)
|
||||
{
|
||||
syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread);
|
||||
}
|
||||
|
||||
currentThread.WaitingSync = true;
|
||||
currentThread.SignaledObj = null;
|
||||
currentThread.ObjSyncResult = result;
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
currentThread.WaitingSync = false;
|
||||
|
||||
if (timeout > 0)
|
||||
{
|
||||
_system.TimeManager.UnscheduleFutureInvocation(currentThread);
|
||||
}
|
||||
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
result = currentThread.ObjSyncResult;
|
||||
|
||||
handleIndex = -1;
|
||||
|
||||
for (int index = 0; index < syncObjs.Length; index++)
|
||||
{
|
||||
syncObjs[index].RemoveWaitingThread(syncNodes[index]);
|
||||
|
||||
if (syncObjs[index] == currentThread.SignaledObj)
|
||||
{
|
||||
handleIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SignalObject(KSynchronizationObject syncObj)
|
||||
{
|
||||
_system.CriticalSection.Enter();
|
||||
|
||||
if (syncObj.IsSignaled())
|
||||
{
|
||||
LinkedListNode<KThread> node = syncObj.WaitingThreads.First;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
KThread thread = node.Value;
|
||||
|
||||
if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
|
||||
{
|
||||
thread.SignaledObj = syncObj;
|
||||
thread.ObjSyncResult = KernelResult.Success;
|
||||
|
||||
thread.Reschedule(ThreadSchedState.Running);
|
||||
}
|
||||
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
_system.CriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
}
|
1030
Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
Normal file
1030
Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
Normal file
File diff suppressed because it is too large
Load diff
24
Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
Normal file
24
Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KWritableEvent
|
||||
{
|
||||
private KEvent _parent;
|
||||
|
||||
public KWritableEvent(KEvent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
_parent.ReadableEvent.Signal();
|
||||
}
|
||||
|
||||
public KernelResult Clear()
|
||||
{
|
||||
return _parent.ReadableEvent.Clear();
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
Normal file
9
Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
enum SignalType
|
||||
{
|
||||
Signal = 0,
|
||||
SignalAndIncrementIfEqual = 1,
|
||||
SignalAndModifyIfEqual = 2
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
Normal file
19
Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
enum ThreadSchedState : ushort
|
||||
{
|
||||
LowMask = 0xf,
|
||||
HighMask = 0xfff0,
|
||||
ForcePauseMask = 0x70,
|
||||
|
||||
ProcessPauseFlag = 1 << 4,
|
||||
ThreadPauseFlag = 1 << 5,
|
||||
ProcessDebugPauseFlag = 1 << 6,
|
||||
KernelInitPauseFlag = 1 << 8,
|
||||
|
||||
None = 0,
|
||||
Paused = 1,
|
||||
Running = 2,
|
||||
TerminationPending = 3
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
Normal file
10
Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
enum ThreadType
|
||||
{
|
||||
Dummy,
|
||||
Kernel,
|
||||
Kernel2,
|
||||
User
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue