From 48278905d1470f89be31668c738397f569af156a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 9 Dec 2020 19:20:05 -0300 Subject: [PATCH] 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 --- .../Configuration/ConfigurationState.cs | 31 +- Ryujinx.Common/PerformanceCounter.cs | 4 +- Ryujinx.HLE/HOS/Horizon.cs | 23 +- Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 15 +- Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 137 ++-- .../HOS/Kernel/Common/KernelTransfer.cs | 10 +- Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 4 +- Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs | 6 +- Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs | 6 +- Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 3 +- Ryujinx.HLE/HOS/Kernel/KernelContext.cs | 39 +- Ryujinx.HLE/HOS/Kernel/KernelStatic.cs | 59 +- .../HOS/Kernel/Memory/KMemoryManager.cs | 16 +- .../HOS/Kernel/Memory/KTransferMemory.cs | 2 +- .../HOS/Kernel/Process/KHandleTable.cs | 4 +- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 38 +- .../HOS/Kernel/SupervisorCall/Syscall.cs | 173 +++-- .../Kernel/SupervisorCall/SyscallHandler.cs | 2 +- .../HOS/Kernel/Threading/HleCoreManager.cs | 66 -- .../HOS/Kernel/Threading/HleScheduler.cs | 150 ---- .../HOS/Kernel/Threading/KAddressArbiter.cs | 20 +- .../Kernel/Threading/KConditionVariable.cs | 2 +- .../HOS/Kernel/Threading/KCoreContext.cs | 79 --- .../HOS/Kernel/Threading/KCriticalSection.cs | 74 +- .../{KSchedulingData.cs => KPriorityQueue.cs} | 46 +- .../HOS/Kernel/Threading/KScheduler.cs | 652 ++++++++++++++---- .../HOS/Kernel/Threading/KSynchronization.cs | 2 +- Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 423 ++++-------- .../HOS/Kernel/Threading/KThreadContext.cs | 19 + Ryujinx.HLE/HOS/Services/ServerBase.cs | 44 +- .../SurfaceFlinger/BufferQueueCore.cs | 19 +- Ryujinx.HLE/Switch.cs | 5 - Ryujinx/Config.json | 3 +- Ryujinx/THIRDPARTY.md | 25 + Ryujinx/Ui/SettingsWindow.cs | 9 +- Ryujinx/Ui/SettingsWindow.glade | 18 - Ryujinx/_schema.json | 12 - 37 files changed, 1080 insertions(+), 1160 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs rename Ryujinx.HLE/HOS/Kernel/Threading/{KSchedulingData.cs => KPriorityQueue.cs} (87%) create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs index ef4a433722..94c9ac0e20 100644 --- a/Ryujinx.Common/Configuration/ConfigurationState.cs +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Configuration public ReactiveObject DevColumn { get; private set; } public ReactiveObject VersionColumn { get; private set; } public ReactiveObject TimePlayedColumn { get; private set; } - public ReactiveObject LastPlayedColumn { get; private set; } + public ReactiveObject LastPlayedColumn { get; private set; } public ReactiveObject FileExtColumn { get; private set; } public ReactiveObject FileSizeColumn { get; private set; } public ReactiveObject PathColumn { get; private set; } @@ -198,11 +198,6 @@ namespace Ryujinx.Configuration /// public ReactiveObject EnableDockedMode { get; private set; } - /// - /// Enables or disables multi-core scheduling of threads - /// - public ReactiveObject EnableMulticoreScheduling { get; private set; } - /// /// Enables or disables profiled translation cache persistency /// @@ -230,17 +225,16 @@ namespace Ryujinx.Configuration public SystemSection() { - Language = new ReactiveObject(); - Region = new ReactiveObject(); - TimeZone = new ReactiveObject(); - SystemTimeOffset = new ReactiveObject(); - EnableDockedMode = new ReactiveObject(); - EnableMulticoreScheduling = new ReactiveObject(); - EnablePtc = new ReactiveObject(); - EnableFsIntegrityChecks = new ReactiveObject(); - FsGlobalAccessLogMode = new ReactiveObject(); - AudioBackend = new ReactiveObject(); - IgnoreMissingServices = new ReactiveObject(); + Language = new ReactiveObject(); + Region = new ReactiveObject(); + TimeZone = new ReactiveObject(); + SystemTimeOffset = new ReactiveObject(); + EnableDockedMode = new ReactiveObject(); + EnablePtc = new ReactiveObject(); + EnableFsIntegrityChecks = new ReactiveObject(); + FsGlobalAccessLogMode = new ReactiveObject(); + AudioBackend = new ReactiveObject(); + IgnoreMissingServices = new ReactiveObject(); } } @@ -414,7 +408,6 @@ namespace Ryujinx.Configuration CheckUpdatesOnStart = CheckUpdatesOnStart, EnableVsync = Graphics.EnableVsync, EnableShaderCache = Graphics.EnableShaderCache, - EnableMulticoreScheduling = System.EnableMulticoreScheduling, EnablePtc = System.EnablePtc, EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, @@ -476,7 +469,6 @@ namespace Ryujinx.Configuration CheckUpdatesOnStart.Value = true; Graphics.EnableVsync.Value = true; Graphics.EnableShaderCache.Value = true; - System.EnableMulticoreScheduling.Value = true; System.EnablePtc.Value = false; System.EnableFsIntegrityChecks.Value = true; System.FsGlobalAccessLogMode.Value = 0; @@ -788,7 +780,6 @@ namespace Ryujinx.Configuration CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; - System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling; System.EnablePtc.Value = configurationFileFormat.EnablePtc; System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; diff --git a/Ryujinx.Common/PerformanceCounter.cs b/Ryujinx.Common/PerformanceCounter.cs index 1c2782e34b..97ee23a254 100644 --- a/Ryujinx.Common/PerformanceCounter.cs +++ b/Ryujinx.Common/PerformanceCounter.cs @@ -32,7 +32,7 @@ namespace Ryujinx.Common public static long TicksPerMillisecond { get; } /// - /// Gets the number of milliseconds elapsed since the system started. + /// Gets the number of ticks elapsed since the system started. /// public static long ElapsedTicks { @@ -76,7 +76,7 @@ namespace Ryujinx.Common TicksPerHour = TicksPerMinute * 60; TicksPerDay = TicksPerHour * 24; - _ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency; + _ticksToNs = 1000000000.0 / Stopwatch.Frequency; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 6902c899e7..16b3b80731 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -82,9 +82,6 @@ namespace Ryujinx.HLE.HOS public Keyset KeySet => Device.FileSystem.KeySet; -#pragma warning disable CS0649 - private bool _hasStarted; -#pragma warning restore CS0649 private bool _isDisposed; public bool EnablePtc { get; set; } @@ -300,22 +297,6 @@ namespace Ryujinx.HLE.HOS VsyncEvent.ReadableEvent.Signal(); } - public void EnableMultiCoreScheduling() - { - if (!_hasStarted) - { - KernelContext.Scheduler.MultiCoreScheduling = true; - } - } - - public void DisableMultiCoreScheduling() - { - if (!_hasStarted) - { - KernelContext.Scheduler.MultiCoreScheduling = false; - } - } - public void Dispose() { Dispose(true); @@ -346,9 +327,7 @@ namespace Ryujinx.HLE.HOS } // Exit ourself now! - KernelContext.Scheduler.ExitThread(terminationThread); - KernelContext.Scheduler.GetCurrentThread().Exit(); - KernelContext.Scheduler.RemoveThread(terminationThread); + KernelStatic.GetCurrentThread().Exit(); }); terminationThread.Start(); diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs index 812cbd304c..a94b280f34 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Common @@ -47,17 +48,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public void IncrementReferenceCount() { - Interlocked.Increment(ref _referenceCount); + int newRefCount = Interlocked.Increment(ref _referenceCount); + + Debug.Assert(newRefCount >= 2); } public void DecrementReferenceCount() { - if (Interlocked.Decrement(ref _referenceCount) == 0) + int newRefCount = Interlocked.Decrement(ref _referenceCount); + + Debug.Assert(newRefCount >= 0); + + if (newRefCount == 0) { Destroy(); } } - protected virtual void Destroy() { } + protected virtual void Destroy() + { + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs index 8273520fd1..f7a1c24adc 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -10,9 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { private class WaitingObject { - public IKFutureSchedulerObject Object { get; private set; } - - public long TimePoint { get; private set; } + public IKFutureSchedulerObject Object { get; } + public long TimePoint { get; } public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) { @@ -21,16 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Common } } - private List _waitingObjects; - + private readonly KernelContext _context; + private readonly List _waitingObjects; private AutoResetEvent _waitEvent; - private bool _keepRunning; - public KTimeManager() + public KTimeManager(KernelContext context) { + _context = context; _waitingObjects = new List(); - _keepRunning = true; Thread work = new Thread(WaitAndCheckScheduledObjects) @@ -45,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); - lock (_waitingObjects) + lock (_context.CriticalSection.Lock) { _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); } @@ -53,6 +51,57 @@ namespace Ryujinx.HLE.HOS.Kernel.Common _waitEvent.Set(); } + public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj) + { + lock (_context.CriticalSection.Lock) + { + _waitingObjects.RemoveAll(x => x.Object == schedulerObj); + } + } + + private void WaitAndCheckScheduledObjects() + { + using (_waitEvent = new AutoResetEvent(false)) + { + while (_keepRunning) + { + WaitingObject next; + + lock (_context.CriticalSection.Lock) + { + next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + } + + if (next != null) + { + long timePoint = PerformanceCounter.ElapsedMilliseconds; + + if (next.TimePoint > timePoint) + { + _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); + } + + bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; + + if (timeUp) + { + lock (_context.CriticalSection.Lock) + { + if (_waitingObjects.Remove(next)) + { + next.Object.TimeUp(); + } + } + } + } + else + { + _waitEvent.WaitOne(); + } + } + } + } + public static long ConvertNanosecondsToMilliseconds(long time) { time /= 1000000; @@ -70,77 +119,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Common return time * 1000000; } - public static long ConvertMillisecondsToTicks(long time) + public static long ConvertHostTicksToTicks(long time) { - return time * 19200; - } - - public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object) - { - lock (_waitingObjects) - { - _waitingObjects.RemoveAll(x => x.Object == Object); - } - } - - private void WaitAndCheckScheduledObjects() - { - using (_waitEvent = new AutoResetEvent(false)) - { - while (_keepRunning) - { - WaitingObject next; - - lock (_waitingObjects) - { - next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); - } - - if (next != null) - { - long timePoint = PerformanceCounter.ElapsedMilliseconds; - - if (next.TimePoint > timePoint) - { - _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); - } - - bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; - - if (timeUp) - { - lock (_waitingObjects) - { - timeUp = _waitingObjects.Remove(next); - } - } - - if (timeUp) - { - next.Object.TimeUp(); - } - } - else - { - _waitEvent.WaitOne(); - } - } - } + return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0); } public void Dispose() { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _keepRunning = false; - - _waitEvent?.Set(); - } + _keepRunning = false; + _waitEvent?.Set(); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs index d57ca481d2..3002b6a9dd 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs @@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { public static bool UserToKernelInt32(KernelContext context, ulong address, out int value) { - KProcess currentProcess = context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.CpuMemory.IsMapped(address) && currentProcess.CpuMemory.IsMapped(address + 3)) @@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static bool UserToKernelInt32Array(KernelContext context, ulong address, Span values) { - KProcess currentProcess = context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); for (int index = 0; index < values.Length; index++, address += 4) { @@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static bool UserToKernelString(KernelContext context, ulong address, int size, out string value) { - KProcess currentProcess = context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.CpuMemory.IsMapped(address) && currentProcess.CpuMemory.IsMapped(address + (ulong)size - 1)) @@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static bool KernelToUserInt32(KernelContext context, ulong address, int value) { - KProcess currentProcess = context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.CpuMemory.IsMapped(address) && currentProcess.CpuMemory.IsMapped(address + 3)) @@ -77,7 +77,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static bool KernelToUserInt64(KernelContext context, ulong address, long value) { - KProcess currentProcess = context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.CpuMemory.IsMapped(address) && currentProcess.CpuMemory.IsMapped(address + 7)) diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs index c3b7d1dd38..3f34793fcb 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { clientSession = null; - KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.ResourceLimit != null && !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) @@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { clientSession = null; - KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.ResourceLimit != null && !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs index d535bf82ba..a243a64579 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -25,13 +25,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc State = ChannelState.Open; - CreatorProcess = context.Scheduler.GetCurrentProcess(); + CreatorProcess = KernelStatic.GetCurrentProcess(); CreatorProcess.IncrementReferenceCount(); } public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) { - KThread currentThread = KernelContext.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize); @@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc public KernelResult SendAsyncRequest(KWritableEvent asyncEvent, ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) { - KThread currentThread = KernelContext.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs index e9c6127fa6..e97309d9e4 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -214,7 +214,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc public KernelResult Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) { - KThread serverThread = KernelContext.Scheduler.GetCurrentThread(); + KThread serverThread = KernelStatic.GetCurrentThread(); KProcess serverProcess = serverThread.Owner; KernelContext.CriticalSection.Enter(); @@ -594,7 +594,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc public KernelResult Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) { - KThread serverThread = KernelContext.Scheduler.GetCurrentThread(); + KThread serverThread = KernelStatic.GetCurrentThread(); KProcess serverProcess = serverThread.Owner; KernelContext.CriticalSection.Enter(); @@ -889,7 +889,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc private MessageHeader GetServerMessageHeader(Message serverMsg) { - KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); uint word0 = currentProcess.CpuMemory.Read(serverMsg.Address + 0); uint word1 = currentProcess.CpuMemory.Read(serverMsg.Address + 4); diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs index d614857c9a..13cf4b51bb 100644 --- a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -1,6 +1,5 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; -using System; namespace Ryujinx.HLE.HOS.Kernel.Ipc { @@ -13,6 +12,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc public KSession(KernelContext context, KClientPort parentPort = null) : base(context) { + IncrementReferenceCount(); + ServerSession = new KServerSession(context, this); ClientSession = new KClientSession(context, this, parentPort); diff --git a/Ryujinx.HLE/HOS/Kernel/KernelContext.cs b/Ryujinx.HLE/HOS/Kernel/KernelContext.cs index d19c43eefe..cacb7fb3b6 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelContext.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelContext.cs @@ -19,6 +19,8 @@ namespace Ryujinx.HLE.HOS.Kernel public bool KernelInitialized { get; } + public bool Running { get; private set; } + public Switch Device { get; } public MemoryBlock Memory { get; } public Syscall Syscall { get; } @@ -34,7 +36,8 @@ namespace Ryujinx.HLE.HOS.Kernel public KSlabHeap UserSlabHeapPages { get; } public KCriticalSection CriticalSection { get; } - public KScheduler Scheduler { get; } + public KScheduler[] Schedulers { get; } + public KPriorityQueue PriorityQueue { get; } public KTimeManager TimeManager { get; } public KSynchronization Synchronization { get; } public KContextIdManager ContextIdManager { get; } @@ -42,6 +45,8 @@ namespace Ryujinx.HLE.HOS.Kernel public ConcurrentDictionary Processes { get; } public ConcurrentDictionary AutoObjectNames { get; } + public bool ThreadReselectionRequested { get; set; } + private long _kipId; private long _processId; private long _threadUid; @@ -51,6 +56,8 @@ namespace Ryujinx.HLE.HOS.Kernel Device = device; Memory = memory; + Running = true; + Syscall = new Syscall(this); SyscallHandler = new SyscallHandler(this); @@ -70,12 +77,18 @@ namespace Ryujinx.HLE.HOS.Kernel KernelConstants.UserSlabHeapSize); CriticalSection = new KCriticalSection(this); - Scheduler = new KScheduler(this); - TimeManager = new KTimeManager(); + Schedulers = new KScheduler[KScheduler.CpuCoresCount]; + PriorityQueue = new KPriorityQueue(); + TimeManager = new KTimeManager(this); Synchronization = new KSynchronization(this); ContextIdManager = new KContextIdManager(); - Scheduler.StartAutoPreemptionThread(); + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + Schedulers[core] = new KScheduler(this, core); + } + + StartPreemptionThread(); KernelInitialized = true; @@ -86,6 +99,16 @@ namespace Ryujinx.HLE.HOS.Kernel _processId = KernelConstants.InitialProcessId; } + private void StartPreemptionThread() + { + void PreemptionThreadStart() + { + KScheduler.PreemptionThreadLoop(this); + } + + new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start(); + } + public long NewThreadUid() { return Interlocked.Increment(ref _threadUid) - 1; @@ -103,7 +126,13 @@ namespace Ryujinx.HLE.HOS.Kernel public void Dispose() { - Scheduler.Dispose(); + Running = false; + + for (int i = 0; i < KScheduler.CpuCoresCount; i++) + { + Schedulers[i].Dispose(); + } + TimeManager.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs b/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs index c7deadae95..2446ace4b5 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs @@ -1,6 +1,9 @@ -using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; -using System.Threading.Tasks; +using System.Threading; namespace Ryujinx.HLE.HOS.Kernel { @@ -9,30 +12,52 @@ namespace Ryujinx.HLE.HOS.Kernel [ThreadStatic] private static KernelContext Context; - public static void YieldUntilCompletion(Action action) + [ThreadStatic] + private static KThread CurrentThread; + + public static KernelResult StartInitialProcess( + KernelContext context, + ProcessCreationInfo creationInfo, + ReadOnlySpan capabilities, + int mainThreadPriority, + ThreadStart customThreadStart) { - YieldUntilCompletion(Task.Factory.StartNew(action)); - } + KProcess process = new KProcess(context); - public static void YieldUntilCompletion(Task task) - { - KThread currentThread = Context.Scheduler.GetCurrentThread(); + KernelResult result = process.Initialize( + creationInfo, + capabilities, + context.ResourceLimit, + MemoryRegion.Service, + null, + customThreadStart); - Context.CriticalSection.Enter(); - - currentThread.Reschedule(ThreadSchedState.Paused); - - task.ContinueWith((antecedent) => + if (result != KernelResult.Success) { - currentThread.Reschedule(ThreadSchedState.Running); - }); + return result; + } - Context.CriticalSection.Leave(); + process.DefaultCpuCore = 3; + + context.Processes.TryAdd(process.Pid, process); + + return process.Start(mainThreadPriority, 0x1000UL); } - internal static void SetKernelContext(KernelContext context) + internal static void SetKernelContext(KernelContext context, KThread thread) { Context = context; + CurrentThread = thread; + } + + internal static KThread GetCurrentThread() + { + return CurrentThread; + } + + internal static KProcess GetCurrentProcess() + { + return GetCurrentThread().Owner; } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs index 05ad9f617a..5a8e8e3b20 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs @@ -728,7 +728,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return KernelResult.OutOfMemory; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); lock (_blocks) { @@ -1225,7 +1225,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong remainingPages = remainingSize / PageSize; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.ResourceLimit != null && !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize)) @@ -1355,7 +1355,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory PhysicalMemoryUsage -= heapMappedSize; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize); @@ -1504,7 +1504,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory attributeMask | MemoryAttribute.Uncached, attributeExpected)) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); serverAddress = currentProcess.MemoryManager.GetDramAddressFromVa(serverAddress); @@ -2111,11 +2111,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } } } + + InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions); + + return KernelResult.Success; } - - InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions); - - return KernelResult.Success; } public KernelResult BorrowIpcBuffer(ulong address, ulong size) diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs index 96349452a5..7107d497a8 100644 --- a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory public KernelResult Initialize(ulong address, ulong size, KMemoryPermission permission) { - KProcess creator = KernelContext.Scheduler.GetCurrentProcess(); + KProcess creator = KernelStatic.GetCurrentProcess(); _creator = creator; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs index b733501a76..bcbb3b03aa 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -236,7 +236,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { if (handle == SelfThreadHandle) { - return _context.Scheduler.GetCurrentThread(); + return KernelStatic.GetCurrentThread(); } else { @@ -248,7 +248,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { if (handle == SelfProcessHandle) { - return _context.Scheduler.GetCurrentProcess(); + return KernelStatic.GetCurrentProcess(); } else { diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 8e914f1943..d19901695c 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -78,6 +78,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public bool IsPaused { get; private set; } + private long _totalTimeRunning; + + public long TotalTimeRunning => _totalTimeRunning; + private IProcessContextFactory _contextFactory; public IProcessContext Context { get; private set; } public IVirtualMemoryManager CpuMemory => Context.AddressSpace; @@ -112,11 +116,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process KPageList pageList, KResourceLimit resourceLimit, MemoryRegion memRegion, - IProcessContextFactory contextFactory) + IProcessContextFactory contextFactory, + ThreadStart customThreadStart = null) { ResourceLimit = resourceLimit; _memRegion = memRegion; _contextFactory = contextFactory ?? new ProcessContextFactory(); + _customThreadStart = customThreadStart; AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift); @@ -176,9 +182,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process throw new InvalidOperationException($"Invalid KIP Id {Pid}."); } - result = ParseProcessInfo(creationInfo); - - return result; + return ParseProcessInfo(creationInfo); } public KernelResult Initialize( @@ -192,6 +196,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process ResourceLimit = resourceLimit; _memRegion = memRegion; _contextFactory = contextFactory ?? new ProcessContextFactory(); + _customThreadStart = customThreadStart; ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion); @@ -299,8 +304,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process CleanUpForError(); } - _customThreadStart = customThreadStart; - return result; } @@ -751,8 +754,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private void InterruptHandler(object sender, EventArgs e) { - KernelContext.Scheduler.ContextSwitch(); - KernelContext.Scheduler.GetCurrentThread().HandlePostSyscall(); + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.IsSchedulable) + { + KernelContext.Schedulers[currentThread.CurrentCore].Schedule(); + } + + currentThread.HandlePostSyscall(); } public void IncrementThreadCount() @@ -828,6 +837,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return personalMmHeapPagesCount * KMemoryManager.PageSize; } + public void AddCpuTime(long ticks) + { + Interlocked.Add(ref _totalTimeRunning, ticks); + } + public void AddThread(KThread thread) { lock (_threadingLock) @@ -893,7 +907,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process if (shallTerminate) { - UnpauseAndTerminateAllThreadsExcept(KernelContext.Scheduler.GetCurrentThread()); + UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread()); HandleTable.Destroy(); @@ -929,7 +943,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process if (shallTerminate) { - UnpauseAndTerminateAllThreadsExcept(KernelContext.Scheduler.GetCurrentThread()); + UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread()); HandleTable.Destroy(); @@ -1058,7 +1072,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private bool InvalidAccessHandler(ulong va) { - KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace(); + KernelStatic.GetCurrentThread().PrintGuestStackTrace(); Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}."); @@ -1067,7 +1081,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e) { - KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace(); + KernelStatic.GetCurrentThread().PrintGuestStackTrace(); throw new UndefinedInstructionException(e.Address, e.OpCode); } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 5e32ca5805..418a02f088 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -26,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult GetProcessId(int handle, out long pid) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess process = currentProcess.HandleTable.GetKProcess(handle); @@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidThread; } - KHandleTable handleTable = _context.Scheduler.GetCurrentProcess().HandleTable; + KHandleTable handleTable = KernelStatic.GetCurrentProcess().HandleTable; KProcess process = new KProcess(_context); @@ -137,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize) { - KProcess process = _context.Scheduler.GetCurrentProcess().HandleTable.GetObject(handle); + KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); if (process == null) { @@ -198,7 +198,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.NotFound; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KernelResult result = currentProcess.HandleTable.ReserveHandle(out handle); @@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult SendSyncRequest(int handle) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KClientSession session = currentProcess.HandleTable.GetObject(handle); @@ -254,7 +254,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemState; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KernelResult result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize); @@ -303,7 +303,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemState; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KernelResult result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize); @@ -363,7 +363,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall serverSessionHandle = 0; clientSessionHandle = 0; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KResourceLimit resourceLimit = currentProcess.ResourceLimit; @@ -424,7 +424,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { sessionHandle = 0; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KServerPort serverPort = currentProcess.HandleTable.GetObject(portHandle); @@ -485,7 +485,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.MaximumExceeded; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); ulong copySize = (ulong)((long)handlesCount * 4); @@ -513,7 +513,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { handleIndex = 0; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length]; @@ -582,7 +582,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.MaximumExceeded; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); ulong copySize = (ulong)((long)handlesCount * 4); @@ -681,7 +681,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KPort port = new KPort(_context, maxSessions, isLight, (long)namePtr); - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); @@ -733,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KPort port = new KPort(_context, maxSessions, false, 0); - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); @@ -756,7 +756,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { clientSessionHandle = 0; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KClientPort clientPort = currentProcess.HandleTable.GetObject(clientPortHandle); @@ -814,7 +814,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidSize; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); return process.MemoryManager.SetHeapSize(size, out position); } @@ -843,7 +843,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KernelResult result = process.MemoryManager.SetMemoryAttribute( position, @@ -871,7 +871,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemState; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) { @@ -885,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); return process.MemoryManager.Map(dst, src, size); } @@ -907,7 +907,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemState; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) { @@ -921,14 +921,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); return process.MemoryManager.Unmap(dst, src, size); } public KernelResult QueryMemory(ulong infoPtr, ulong position, out ulong pageInfo) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KMemoryInfo blkInfo = process.MemoryManager.QueryMemory(position); @@ -968,7 +968,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidPermission; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); @@ -1009,7 +1009,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemState; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); @@ -1056,7 +1056,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidPermission; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KResourceLimit resourceLimit = process.ResourceLimit; @@ -1112,7 +1112,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) { @@ -1125,7 +1125,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); return process.MemoryManager.MapPhysicalMemory(address, size); } @@ -1147,7 +1147,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) { @@ -1160,7 +1160,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidMemRange; } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); return process.MemoryManager.UnmapPhysicalMemory(address, size); } @@ -1177,7 +1177,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidSize; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); @@ -1214,7 +1214,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidSize; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); @@ -1259,7 +1259,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidPermission; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); @@ -1285,7 +1285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult TerminateProcess(int handle) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); process = process.HandleTable.GetObject(handle); @@ -1293,7 +1293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall if (process != null) { - if (process == _context.Scheduler.GetCurrentProcess()) + if (process == KernelStatic.GetCurrentProcess()) { result = KernelResult.Success; process.DecrementToZeroWhileTerminatingCurrent(); @@ -1314,12 +1314,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public void ExitProcess() { - _context.Scheduler.GetCurrentProcess().TerminateCurrentProcess(); + KernelStatic.GetCurrentProcess().TerminateCurrentProcess(); } public KernelResult SignalEvent(int handle) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KWritableEvent writableEvent = process.HandleTable.GetObject(handle); @@ -1343,7 +1343,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { KernelResult result; - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KWritableEvent writableEvent = process.HandleTable.GetObject(handle); @@ -1363,14 +1363,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult CloseHandle(int handle) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return currentProcess.HandleTable.CloseHandle(handle) ? KernelResult.Success : KernelResult.InvalidHandle; } public KernelResult ResetSignal(int handle) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KReadableEvent readableEvent = currentProcess.HandleTable.GetObject(handle); @@ -1399,12 +1399,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public ulong GetSystemTick() { - return _context.Scheduler.GetCurrentThread().Context.CntpctEl0; + return KernelStatic.GetCurrentThread().Context.CntpctEl0; } public void Break(ulong reason) { - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); if ((reason & (1UL << 31)) == 0) { @@ -1429,7 +1429,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public void OutputDebugString(ulong strPtr, ulong size) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); string str = MemoryHelper.ReadAsciiString(process.CpuMemory, (long)strPtr, (long)size); @@ -1466,7 +1466,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess process = currentProcess.HandleTable.GetKProcess(handle); @@ -1537,7 +1537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - value = _context.Scheduler.GetCurrentProcess().Debug ? 1 : 0; + value = KernelStatic.GetCurrentProcess().Debug ? 1 : 0; break; } @@ -1554,7 +1554,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.ResourceLimit != null) { @@ -1581,14 +1581,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } - int currentCore = _context.Scheduler.GetCurrentThread().CurrentCore; + int currentCore = KernelStatic.GetCurrentThread().CurrentCore; if (subId != -1 && subId != currentCore) { return KernelResult.InvalidCombination; } - value = _context.Scheduler.CoreContexts[currentCore].TotalIdleTimeTicks; + value = KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks); break; } @@ -1605,8 +1605,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); - + KProcess currentProcess = KernelStatic.GetCurrentProcess(); value = currentProcess.RandomEntropy[subId]; @@ -1620,14 +1619,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidCombination; } - KThread thread = _context.Scheduler.GetCurrentProcess().HandleTable.GetKThread(handle); + KThread thread = KernelStatic.GetCurrentProcess().HandleTable.GetKThread(handle); if (thread == null) { return KernelResult.InvalidHandle; } - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); int currentCore = currentThread.CurrentCore; @@ -1636,13 +1635,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.Success; } - KCoreContext coreContext = _context.Scheduler.CoreContexts[currentCore]; + KScheduler scheduler = _context.Schedulers[currentCore]; - long timeDelta = PerformanceCounter.ElapsedMilliseconds - coreContext.LastContextSwitchTime; + long timeDelta = PerformanceCounter.ElapsedTicks - scheduler.LastContextSwitchTime; if (subId != -1) { - value = KTimeManager.ConvertMillisecondsToTicks(timeDelta); + value = KTimeManager.ConvertHostTicksToTicks(timeDelta); } else { @@ -1653,7 +1652,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall totalTimeRunning += timeDelta; } - value = KTimeManager.ConvertMillisecondsToTicks(totalTimeRunning); + value = KTimeManager.ConvertHostTicksToTicks(totalTimeRunning); } break; @@ -1669,7 +1668,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { KEvent Event = new KEvent(_context); - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KernelResult result = process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle); @@ -1701,7 +1700,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall if (maxCount != 0) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); ulong copySize = (ulong)maxCount * 8; @@ -1807,7 +1806,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { handle = 0; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (cpuCore == -2) { @@ -1844,7 +1843,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall if (result == KernelResult.Success) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); result = process.HandleTable.GenerateHandle(thread, out handle); } @@ -1860,7 +1859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult StartThread(int handle) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -1887,35 +1886,31 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public void ExitThread() { - KThread currentThread = _context.Scheduler.GetCurrentThread(); - - _context.Scheduler.ExitThread(currentThread); + KThread currentThread = KernelStatic.GetCurrentThread(); currentThread.Exit(); } public void SleepThread(long timeout) { - KThread currentThread = _context.Scheduler.GetCurrentThread(); - if (timeout < 1) { switch (timeout) { - case 0: currentThread.Yield(); break; - case -1: currentThread.YieldWithLoadBalancing(); break; - case -2: currentThread.YieldAndWaitForLoadBalancing(); break; + case 0: KScheduler.Yield(_context); break; + case -1: KScheduler.YieldWithLoadBalancing(_context); break; + case -2: KScheduler.YieldToAnyThread(_context); break; } } else { - currentThread.Sleep(timeout); + KernelStatic.GetCurrentThread().Sleep(timeout); } } public KernelResult GetThreadPriority(int handle, out int priority) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -1937,7 +1932,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { // TODO: NPDM check. - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -1953,7 +1948,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult GetThreadCoreMask(int handle, out int preferredCore, out long affinityMask) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -1975,7 +1970,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult SetThreadCoreMask(int handle, int preferredCore, long affinityMask) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (preferredCore == -2) { @@ -2009,7 +2004,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } } - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -2023,12 +2018,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public int GetCurrentProcessorNumber() { - return _context.Scheduler.GetCurrentThread().CurrentCore; + return KernelStatic.GetCurrentThread().CurrentCore; } public KernelResult GetThreadId(int handle, out long threadUid) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -2048,7 +2043,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult SetThreadActivity(int handle, bool pause) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetObject(handle); @@ -2062,7 +2057,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } - if (thread == _context.Scheduler.GetCurrentThread()) + if (thread == KernelStatic.GetCurrentThread()) { return KernelResult.InvalidThread; } @@ -2072,8 +2067,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult GetThreadContext3(ulong address, int handle) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + KThread currentThread = KernelStatic.GetCurrentThread(); KThread thread = currentProcess.HandleTable.GetObject(handle); @@ -2190,13 +2185,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.MaximumExceeded; } - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); var syncObjs = new Span(currentThread.WaitSyncObjects).Slice(0, handlesCount); if (handlesCount != 0) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (currentProcess.MemoryManager.AddrSpaceStart > handlesPtr) { @@ -2267,7 +2262,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult CancelSynchronization(int handle) { - KProcess process = _context.Scheduler.GetCurrentProcess(); + KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = process.HandleTable.GetKThread(handle); @@ -2293,7 +2288,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidAddress; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); } @@ -2310,7 +2305,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidAddress; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); } @@ -2331,7 +2326,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidAddress; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( mutexAddress, @@ -2342,7 +2337,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public KernelResult SignalProcessWideKey(ulong address, int count) { - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); currentProcess.AddressArbiter.SignalProcessWideKey(address, count); @@ -2361,7 +2356,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidAddress; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return type switch { @@ -2387,7 +2382,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidAddress; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); return type switch { diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs index 2d1a3eea4b..b4e7a0bf01 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs @@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall private void PostSvcHandler() { - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); currentThread.HandlePostSyscall(); } diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs deleted file mode 100644 index c25979904d..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs +++ /dev/null @@ -1,66 +0,0 @@ -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 _threads; - - public HleCoreManager() - { - _threads = new ConcurrentDictionary(); - } - - 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(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs deleted file mode 100644 index c4161d5420..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs +++ /dev/null @@ -1,150 +0,0 @@ -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) - { - Name = "HLE.PreemptionThread" - }; - - _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?.IsCurrentHostThread() ?? false)) - { - coreContext.ContextSwitch(); - } - - if (coreContext.CurrentThread?.IsCurrentHostThread() ?? 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.IsCurrentHostThread()) - { - currentThread.Context.RequestInterrupt(); - - return; - } - - CoreManager.Reset(currentThread.HostThread); - } - - // 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) - { - CoreManager.Set(coreContext.CurrentThread.HostThread); - - coreContext.CurrentThread.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.StopRunning(); - - CoreManager.Exit(thread.HostThread); - } - - public void RemoveThread(KThread thread) - { - CoreManager.RemoveThread(thread.HostThread); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs index 2922ee1ac2..3ddcffc1b6 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -25,14 +25,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) { - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); _context.CriticalSection.Enter(); currentThread.SignaledObj = null; currentThread.ObjSyncResult = KernelResult.Success; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!KernelTransfer.UserToKernelInt32(_context, mutexAddress, out int mutexValue)) { @@ -81,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); (KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); @@ -104,7 +104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); currentThread.SignaledObj = null; currentThread.ObjSyncResult = KernelResult.TimedOut; @@ -227,7 +227,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { ulong address = requester.MutexAddress; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!currentProcess.CpuMemory.IsMapped(address)) { @@ -293,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout) { - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); _context.CriticalSection.Enter(); @@ -368,7 +368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading bool shouldDecrement, long timeout) { - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); _context.CriticalSection.Enter(); @@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.SignaledObj = null; currentThread.ObjSyncResult = KernelResult.TimedOut; - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!KernelTransfer.UserToKernelInt32(_context, address, out int currentValue)) { @@ -483,7 +483,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!currentProcess.CpuMemory.IsMapped(address)) { @@ -544,7 +544,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading offset = 1; } - KProcess currentProcess = _context.Scheduler.GetCurrentProcess(); + KProcess currentProcess = KernelStatic.GetCurrentProcess(); if (!currentProcess.CpuMemory.IsMapped(address)) { diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs index dd7e67ae9a..d146bff01b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs @@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { public static void Wait(KernelContext context, LinkedList threadList, object mutex, long timeout) { - KThread currentThread = context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); context.CriticalSection.Enter(); diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs deleted file mode 100644 index 0aa12b0ddc..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs +++ /dev/null @@ -1,79 +0,0 @@ -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.HostThread); - } - - CurrentThread = SelectedThread; - - if (CurrentThread != null) - { - long currentTime = PerformanceCounter.ElapsedMilliseconds; - - CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; - CurrentThread.LastScheduledTime = currentTime; - - _coreManager.Set(CurrentThread.HostThread); - - CurrentThread.Execute(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs index b778c2a426..1d61f2f06f 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs @@ -5,21 +5,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading class KCriticalSection { private readonly KernelContext _context; - - public object LockObj { get; private set; } - + private readonly object _lock; private int _recursionCount; + public object Lock => _lock; + public KCriticalSection(KernelContext context) { _context = context; - - LockObj = new object(); + _lock = new object(); } public void Enter() { - Monitor.Enter(LockObj); + Monitor.Enter(_lock); _recursionCount++; } @@ -31,61 +30,34 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return; } - bool doContextSwitch = false; - if (--_recursionCount == 0) { - if (_context.Scheduler.ThreadReselectionRequested) + ulong scheduledCoresMask = KScheduler.SelectThreads(_context); + + Monitor.Exit(_lock); + + KThread currentThread = KernelStatic.GetCurrentThread(); + bool isCurrentThreadSchedulable = currentThread != null && currentThread.IsSchedulable; + if (isCurrentThreadSchedulable) { - _context.Scheduler.SelectThreads(); - } - - Monitor.Exit(LockObj); - - if (_context.Scheduler.MultiCoreScheduling) - { - lock (_context.Scheduler.CoreContexts) - { - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - KCoreContext coreContext = _context.Scheduler.CoreContexts[core]; - - if (coreContext.ContextSwitchNeeded) - { - KThread currentThread = coreContext.CurrentThread; - - if (currentThread == null) - { - // Nothing is running, we can perform the context switch immediately. - coreContext.ContextSwitch(); - } - else if (currentThread.IsCurrentHostThread()) - { - // Thread running on the current core, context switch will block. - doContextSwitch = true; - } - else - { - // Thread running on another core, request a interrupt. - currentThread.Context.RequestInterrupt(); - } - } - } - } + KScheduler.EnableScheduling(_context, scheduledCoresMask); } else { - doContextSwitch = true; + KScheduler.EnableSchedulingFromForeignThread(_context, scheduledCoresMask); + + // If the thread exists but is not schedulable, we still want to suspend + // it if it's not runnable. That allows the kernel to still block HLE threads + // even if they are not scheduled on guest cores. + if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running) + { + currentThread.SchedulerWaitEvent.WaitOne(); + } } } else { - Monitor.Exit(LockObj); - } - - if (doContextSwitch) - { - _context.Scheduler.ContextSwitch(); + Monitor.Exit(_lock); } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs similarity index 87% rename from Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs rename to Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs index 83c4a0791f..2c9d757427 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using System.Numerics; namespace Ryujinx.HLE.HOS.Kernel.Threading { - class KSchedulingData + class KPriorityQueue { private LinkedList[][] _scheduledThreadsPerPrioPerCore; private LinkedList[][] _suggestedThreadsPerPrioPerCore; @@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private long[] _scheduledPrioritiesPerCore; private long[] _suggestedPrioritiesPerCore; - public KSchedulingData() + public KPriorityQueue() { _suggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; _scheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; @@ -45,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { long prioMask = prios[core]; - int prio = CountTrailingZeros(prioMask); + int prio = BitOperations.TrailingZeroCount(prioMask); prioMask &= ~(1L << prio); @@ -62,42 +63,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading node = node.Next; } - prio = CountTrailingZeros(prioMask); + prio = BitOperations.TrailingZeroCount(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) + int srcCore = thread.ActiveCore; + if (srcCore == dstCore) { return; } + thread.ActiveCore = dstCore; + if (srcCore >= 0) { Unschedule(prio, srcCore, thread); @@ -168,13 +149,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _scheduledPrioritiesPerCore[core] |= 1L << prio; } - public void Reschedule(int prio, int core, KThread thread) + public KThread Reschedule(int prio, int core, KThread thread) { + if (prio >= KScheduler.PrioritiesCount) + { + return null; + } + LinkedList queue = ScheduledQueue(prio, core); queue.Remove(thread.SiblingsPerCore[core]); thread.SiblingsPerCore[core] = queue.AddLast(thread); + + return queue.First.Value; } public void Unschedule(int prio, int core, KThread thread) diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index c6da361d0a..e427f24dbf 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -1,7 +1,10 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; +using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Threading { @@ -10,130 +13,88 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public const int PrioritiesCount = 64; public const int CpuCoresCount = 4; - private const int PreemptionPriorityCores012 = 59; - private const int PreemptionPriorityCore3 = 63; + private const int RoundRobinTimeQuantumMs = 10; + + private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 }; private readonly KernelContext _context; + private readonly int _coreId; - public KSchedulingData SchedulingData { get; private set; } + private struct SchedulingState + { + public bool NeedsScheduling; + public KThread SelectedThread; + } - public KCoreContext[] CoreContexts { get; private set; } + private SchedulingState _state; - public bool ThreadReselectionRequested { get; set; } + private AutoResetEvent _idleInterruptEvent; + private readonly object _idleInterruptEventLock; - public KScheduler(KernelContext context) + private KThread _previousThread; + private KThread _currentThread; + private readonly KThread _idleThread; + + public KThread PreviousThread => _previousThread; + public long LastContextSwitchTime { get; private set; } + public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning; + + public KScheduler(KernelContext context, int coreId) { _context = context; + _coreId = coreId; - SchedulingData = new KSchedulingData(); + _idleInterruptEvent = new AutoResetEvent(false); + _idleInterruptEventLock = new object(); - CoreManager = new HleCoreManager(); + KThread idleThread = CreateIdleThread(context, coreId); - CoreContexts = new KCoreContext[CpuCoresCount]; + _currentThread = idleThread; + _idleThread = idleThread; + + idleThread.StartHostThread(); + idleThread.SchedulerWaitEvent.Set(); + } + + private KThread CreateIdleThread(KernelContext context, int cpuCore) + { + KThread idleThread = new KThread(context); + + idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop); + + return idleThread; + } + + public static ulong SelectThreads(KernelContext context) + { + if (context.ThreadReselectionRequested) + { + return SelectThreadsImpl(context); + } + else + { + return 0UL; + } + } + + private static ulong SelectThreadsImpl(KernelContext context) + { + context.ThreadReselectionRequested = false; + + ulong scheduledCoresMask = 0UL; for (int core = 0; core < CpuCoresCount; core++) { - CoreContexts[core] = new KCoreContext(this, CoreManager); - } - } + KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); - private void PreemptThreads() - { - _context.CriticalSection.Enter(); - - PreemptThread(PreemptionPriorityCores012, 0); - PreemptThread(PreemptionPriorityCores012, 1); - PreemptThread(PreemptionPriorityCores012, 2); - PreemptThread(PreemptionPriorityCore3, 3); - - _context.CriticalSection.Leave(); - } - - private void PreemptThread(int prio, int core) - { - IEnumerable scheduledThreads = SchedulingData.ScheduledThreads(core); - - KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); - - // Yield priority queue. - if (selectedThread != null) - { - SchedulingData.Reschedule(prio, core, selectedThread); - } - - IEnumerable 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 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); + scheduledCoresMask |= context.Schedulers[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()) + if (context.PriorityQueue.ScheduledThreads(core).Any()) { continue; } @@ -146,16 +107,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // 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)) + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) { - if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread) + if (suggested.ActiveCore < 0 || suggested != context.Schedulers[suggested.ActiveCore]._state.SelectedThread) { - dst = thread; - + dst = suggested; break; } - srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore; + srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore; } // Not yet selected candidate found. @@ -165,9 +125,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // threads, we should skip load balancing entirely. if (dst.DynamicPriority >= 2) { - SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); - CoreContexts[core].SelectThread(dst); + scheduledCoresMask |= context.Schedulers[core].SelectThread(dst); } continue; @@ -179,80 +139,480 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { int srcCore = srcCoresHighestPrioThreads[index]; - KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1); + KThread src = context.PriorityQueue.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; + KThread origSelectedCoreSrc = context.Schedulers[srcCore]._state.SelectedThread; - CoreContexts[srcCore].SelectThread(src); + scheduledCoresMask |= context.Schedulers[srcCore].SelectThread(src); - SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); + context.PriorityQueue.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); - CoreContexts[core].SelectThread(origSelectedCoreSrc); + scheduledCoresMask |= context.Schedulers[core].SelectThread(origSelectedCoreSrc); } } } + + return scheduledCoresMask; } - public KThread GetCurrentThread() + private ulong SelectThread(KThread nextThread) { - return GetCurrentThreadOrNull() ?? GetDummyThread(); - } + KThread previousThread = _state.SelectedThread; - public KThread GetCurrentThreadOrNull() - { - lock (CoreContexts) + if (previousThread != nextThread) { + if (previousThread != null) + { + previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks; + } + + _state.SelectedThread = nextThread; + _state.NeedsScheduling = true; + return 1UL << _coreId; + } + else + { + return 0UL; + } + } + + public static void EnableScheduling(KernelContext context, ulong scheduledCoresMask) + { + KScheduler currentScheduler = context.Schedulers[KernelStatic.GetCurrentThread().CurrentCore]; + + // Note that "RescheduleCurrentCore" will block, so "RescheduleOtherCores" must be done first. + currentScheduler.RescheduleOtherCores(scheduledCoresMask); + currentScheduler.RescheduleCurrentCore(); + } + + public static void EnableSchedulingFromForeignThread(KernelContext context, ulong scheduledCoresMask) + { + RescheduleOtherCores(context, scheduledCoresMask); + } + + private void RescheduleCurrentCore() + { + if (_state.NeedsScheduling) + { + Schedule(); + } + } + + private void RescheduleOtherCores(ulong scheduledCoresMask) + { + RescheduleOtherCores(_context, scheduledCoresMask & ~(1UL << _coreId)); + } + + private static void RescheduleOtherCores(KernelContext context, ulong scheduledCoresMask) + { + while (scheduledCoresMask != 0) + { + int coreToSignal = BitOperations.TrailingZeroCount(scheduledCoresMask); + + KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread; + + // Request the thread running on that core to stop and reschedule, if we have one. + if (threadToSignal != context.Schedulers[coreToSignal]._idleThread) + { + threadToSignal.Context.RequestInterrupt(); + } + + // If the core is idle, ensure that the idle thread is awaken. + context.Schedulers[coreToSignal]._idleInterruptEvent.Set(); + + scheduledCoresMask &= ~(1UL << coreToSignal); + } + } + + private void IdleThreadLoop() + { + while (_context.Running) + { + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + KThread nextThread = PickNextThread(_state.SelectedThread); + + if (_idleThread != nextThread) + { + _idleThread.SchedulerWaitEvent.Reset(); + WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent); + } + + _idleInterruptEvent.WaitOne(); + } + + lock (_idleInterruptEventLock) + { + _idleInterruptEvent.Dispose(); + _idleInterruptEvent = null; + } + } + + public void Schedule() + { + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + KThread currentThread = KernelStatic.GetCurrentThread(); + KThread selectedThread = _state.SelectedThread; + + // If the thread is already scheduled and running on the core, we have nothing to do. + if (currentThread == selectedThread) + { + return; + } + + currentThread.SchedulerWaitEvent.Reset(); + currentThread.ThreadContext.Unlock(); + + // Wake all the threads that might be waiting until this thread context is unlocked. + for (int core = 0; core < CpuCoresCount; core++) + { + _context.Schedulers[core]._idleInterruptEvent.Set(); + } + + KThread nextThread = PickNextThread(selectedThread); + + if (currentThread.Context.Running) + { + // Wait until this thread is scheduled again, and allow the next thread to run. + WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent); + } + else + { + // Allow the next thread to run. + nextThread.SchedulerWaitEvent.Set(); + + // 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 + // no longer assigned to a core. + currentThread.MakeUnschedulable(); + + // Just to be sure, set the core to a invalid value. + // This will trigger a exception if it attempts to call schedule again, + // rather than leaving the scheduler in a invalid state. + currentThread.CurrentCore = -1; + } + } + + private KThread PickNextThread(KThread selectedThread) + { + while (true) + { + if (selectedThread != null) + { + // Try to run the selected thread. + // We need to acquire the context lock to be sure the thread is not + // already running on another core. If it is, then we return here + // and the caller should try again once there is something available for scheduling. + // The thread currently running on the core should have been requested to + // interrupt so this is not expected to take long. + // The idle thread must also be paused if we are scheduling a thread + // on the core, as the scheduled thread will handle the next switch. + if (selectedThread.ThreadContext.Lock()) + { + SwitchTo(selectedThread); + + if (!_state.NeedsScheduling) + { + return selectedThread; + } + + selectedThread.ThreadContext.Unlock(); + } + else + { + return _idleThread; + } + } + else + { + // The core is idle now, make sure that the idle thread can run + // and switch the core when a thread is available. + SwitchTo(null); + return _idleThread; + } + + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + selectedThread = _state.SelectedThread; + } + } + + private void SwitchTo(KThread nextThread) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + KThread currentThread = KernelStatic.GetCurrentThread(); + + nextThread ??= _idleThread; + + if (currentThread == nextThread) + { + return; + } + + long previousTicks = LastContextSwitchTime; + long currentTicks = PerformanceCounter.ElapsedTicks; + long ticksDelta = currentTicks - previousTicks; + + currentThread.AddCpuTime(ticksDelta); + + if (currentProcess != null) + { + currentProcess.AddCpuTime(ticksDelta); + } + + LastContextSwitchTime = currentTicks; + + if (currentProcess != null) + { + _previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null; + } + else if (currentThread == _idleThread) + { + _previousThread = null; + } + + if (nextThread.CurrentCore != _coreId) + { + nextThread.CurrentCore = _coreId; + } + + _currentThread = nextThread; + } + + public static void PreemptionThreadLoop(KernelContext context) + { + while (context.Running) + { + context.CriticalSection.Enter(); + for (int core = 0; core < CpuCoresCount; core++) { - if (CoreContexts[core].CurrentThread?.IsCurrentHostThread() ?? false) + RotateScheduledQueue(context, core, PreemptionPriorities[core]); + } + + context.CriticalSection.Leave(); + + Thread.Sleep(RoundRobinTimeQuantumMs); + } + } + + private static void RotateScheduledQueue(KernelContext context, int core, int prio) + { + IEnumerable scheduledThreads = context.PriorityQueue.ScheduledThreads(core); + + KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); + KThread nextThread = null; + + // Yield priority queue. + if (selectedThread != null) + { + nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + if (suggestedCore >= 0) { - return CoreContexts[core].CurrentThread; + KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); + + if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2)) + { + continue; + } + } + + // If the candidate was scheduled after the current thread, then it's not worth it. + if (nextThread == selectedThread || + nextThread == null || + nextThread.LastScheduledTime >= suggested.LastScheduledTime) + { + yield return suggested; } } } - return null; - } + // 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); - private KThread _dummyThread; - - private KThread GetDummyThread() - { - if (_dummyThread != null) + if (dst != null) { - return _dummyThread; + context.PriorityQueue.TransferToCore(prio, core, dst); } - KProcess dummyProcess = new KProcess(_context); + // If the priority of the currently selected thread is lower or same as the preemption priority, + // then try to migrate a thread with lower priority. + KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); - dummyProcess.HandleTable.Initialize(1024); + if (bestCandidate != null && bestCandidate.DynamicPriority >= prio) + { + dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority); - KThread dummyThread = new KThread(_context); + if (dst != null) + { + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); + } + } - dummyThread.Initialize(0, 0, 0, 44, 0, dummyProcess, ThreadType.Dummy); - - return _dummyThread = dummyThread; + context.ThreadReselectionRequested = true; } - public KProcess GetCurrentProcess() + public static void Yield(KernelContext context) { - return GetCurrentThread().Owner; + KThread currentThread = KernelStatic.GetCurrentThread(); + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + KThread nextThread = context.PriorityQueue.Reschedule(currentThread.DynamicPriority, currentThread.ActiveCore, currentThread); + + if (nextThread != currentThread) + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); + } + + public static void YieldWithLoadBalancing(KernelContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + int prio = currentThread.DynamicPriority; + int core = currentThread.ActiveCore; + + // Move current thread to the end of the queue. + KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread); + + IEnumerable SuitableCandidates() + { + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + if (suggestedCore >= 0) + { + KThread selectedSuggestedCore = context.Schedulers[suggestedCore]._state.SelectedThread; + + if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 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 (suggested.LastScheduledTime <= nextThread.LastScheduledTime || + suggested.DynamicPriority < nextThread.DynamicPriority) + { + yield return suggested; + } + } + } + + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); + + if (dst != null) + { + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); + + context.ThreadReselectionRequested = true; + } + else if (currentThread != nextThread) + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); + } + + public static void YieldToAnyThread(KernelContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + int core = currentThread.ActiveCore; + + context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread); + + if (!context.PriorityQueue.ScheduledThreads(core).Any()) + { + KThread selectedThread = null; + + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + + if (suggestedCore < 0) + { + continue; + } + + KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); + + if (firstCandidate == suggested) + { + continue; + } + + if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) + { + context.PriorityQueue.TransferToCore(suggested.DynamicPriority, core, suggested); + } + + selectedThread = suggested; + break; + } + + if (currentThread != selectedThread) + { + context.ThreadReselectionRequested = true; + } + } + else + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); } public void Dispose() { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) + // Ensure that the idle thread is not blocked and can exit. + lock (_idleInterruptEventLock) { - _keepPreempting = false; + if (_idleInterruptEvent != null) + { + _idleInterruptEvent.Set(); + } } } } diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index 22610b2205..419f15368b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return result; } - KThread currentThread = _context.Scheduler.GetCurrentThread(); + KThread currentThread = KernelStatic.GetCurrentThread(); if (currentThread.ShallBeTerminated || currentThread.SchedFlags == ThreadSchedState.TerminationPending) diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index f523cb9cf0..b95b1e8e0a 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,17 +393,46 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } while (state == ThreadSchedState.TerminationPending); } - private void ExitImpl() + public void Exit() + { + // TODO: Debug event. + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); + + _hasBeenReleased = true; + } + + KernelContext.CriticalSection.Enter(); + + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + + bool decRef = ExitImpl(); + + Context.StopRunning(); + + KernelContext.CriticalSection.Leave(); + + if (decRef) + { + DecrementReferenceCount(); + } + } + + private bool ExitImpl() { KernelContext.CriticalSection.Enter(); SetNewSchedFlags(ThreadSchedState.TerminationPending); - _hasExited = true; + bool decRef = Interlocked.Exchange(ref _hasExited, 1) == 0; Signal(); KernelContext.CriticalSection.Leave(); + + return decRef; } public KernelResult Sleep(long timeout) @@ -457,161 +463,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return 0; } - public void Yield() - { - KernelContext.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); - - return; - } - - if (DynamicPriority < KScheduler.PrioritiesCount) - { - // Move current thread to the end of the queue. - _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); - } - - _scheduler.ThreadReselectionRequested = true; - - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); - } - - public void YieldWithLoadBalancing() - { - KernelContext.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - 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 predicate = x => x.DynamicPriority == prio; - - nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); - } - - IEnumerable 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; - } - } - } - - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); - - if (dst != null) - { - _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); - - _scheduler.ThreadReselectionRequested = true; - } - - if (this != nextThreadOnCurrentQueue) - { - _scheduler.ThreadReselectionRequested = true; - } - - 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) - { - _scheduler.ThreadReselectionRequested = true; - } - - KernelContext.CriticalSection.Leave(); - - KernelContext.Scheduler.ContextSwitch(); - } - public void SetPriority(int priority) { KernelContext.CriticalSection.Enter(); @@ -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) { - if (Interlocked.CompareExchange(ref _hostThreadRunning, 1, 0) == 0) + Interlocked.Add(ref _totalTimeRunning, ticks); + } + + public void StartHostThread() + { + 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() diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs new file mode 100644 index 0000000000..a7e9c4b3a2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs @@ -0,0 +1,19 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThreadContext + { + private int _locked; + + public bool Lock() + { + return Interlocked.Exchange(ref _locked, 1) == 0; + } + + public void Unlock() + { + Interlocked.Exchange(ref _locked, 0); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs index f70d930faf..3161e689b2 100644 --- a/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services }; private readonly KernelContext _context; - private readonly KProcess _selfProcess; + private KProcess _selfProcess; private readonly List _sessionHandles = new List(); private readonly List _portHandles = new List(); @@ -55,11 +55,7 @@ namespace Ryujinx.HLE.HOS.Services ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); - context.Syscall.CreateProcess(creationInfo, DefaultCapabilities, out int handle, null, ServerLoop); - - _selfProcess = context.Scheduler.GetCurrentProcess().HandleTable.GetKProcess(handle); - - context.Syscall.StartProcess(handle, 44, 3, 0x1000); + KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, ServerLoop); } private void AddPort(int serverPortHandle, IpcService obj) @@ -82,6 +78,8 @@ namespace Ryujinx.HLE.HOS.Services private void ServerLoop() { + _selfProcess = KernelStatic.GetCurrentProcess(); + if (SmObject != null) { _context.Syscall.ManageNamedPort("sm:", 50, out int serverPortHandle); @@ -95,7 +93,7 @@ namespace Ryujinx.HLE.HOS.Services InitDone.Dispose(); } - KThread thread = _context.Scheduler.GetCurrentThread(); + KThread thread = KernelStatic.GetCurrentThread(); ulong messagePtr = thread.TlsAddress; _context.Syscall.SetHeapSize(0x200000, out ulong heapAddr); @@ -107,18 +105,14 @@ namespace Ryujinx.HLE.HOS.Services while (true) { - int[] handles = _portHandles.ToArray(); + int[] portHandles = _portHandles.ToArray(); + int[] sessionHandles = _sessionHandles.ToArray(); + int[] handles = new int[portHandles.Length + sessionHandles.Length]; - for (int i = 0; i < handles.Length; i++) - { - if (_context.Syscall.AcceptSession(handles[i], out int serverSessionHandle) == KernelResult.Success) - { - AddSessionObj(serverSessionHandle, _ports[handles[i]]); - } - } - - handles = _sessionHandles.ToArray(); + portHandles.CopyTo(handles, 0); + sessionHandles.CopyTo(handles, portHandles.Length); + // We still need a timeout here to allow the service to pick up and listen new sessions... var rc = _context.Syscall.ReplyAndReceive(handles, replyTargetHandle, 1000000L, out int signaledIndex); thread.HandlePostSyscall(); @@ -130,8 +124,9 @@ namespace Ryujinx.HLE.HOS.Services replyTargetHandle = 0; - if (rc == KernelResult.Success && signaledIndex != -1) + if (rc == KernelResult.Success && signaledIndex >= portHandles.Length) { + // We got a IPC request, process it, pass to the appropriate service if needed. int signaledHandle = handles[signaledIndex]; if (Process(signaledHandle, heapAddr)) @@ -141,6 +136,15 @@ namespace Ryujinx.HLE.HOS.Services } else { + if (rc == KernelResult.Success) + { + // We got a new connection, accept the session to allow servicing future requests. + if (_context.Syscall.AcceptSession(handles[signaledIndex], out int serverSessionHandle) == KernelResult.Success) + { + AddSessionObj(serverSessionHandle, _ports[handles[signaledIndex]]); + } + } + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); @@ -150,8 +154,8 @@ namespace Ryujinx.HLE.HOS.Services private bool Process(int serverSessionHandle, ulong recvListAddr) { - KProcess process = _context.Scheduler.GetCurrentProcess(); - KThread thread = _context.Scheduler.GetCurrentThread(); + KProcess process = KernelStatic.GetCurrentProcess(); + KThread thread = KernelStatic.GetCurrentThread(); ulong messagePtr = thread.TlsAddress; ulong messageSize = 0x100; diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs index 389a980bb6..1043dac921 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -184,11 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public void WaitDequeueEvent() { - Monitor.Exit(Lock); - - KernelStatic.YieldUntilCompletion(WaitForLock); - - Monitor.Enter(Lock); + WaitForLock(); } public void SignalIsAllocatingEvent() @@ -198,21 +194,14 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger public void WaitIsAllocatingEvent() { - Monitor.Exit(Lock); - - KernelStatic.YieldUntilCompletion(WaitForLock); - - Monitor.Enter(Lock); + WaitForLock(); } private void WaitForLock() { - lock (Lock) + if (Active) { - if (Active) - { - Monitor.Wait(Lock); - } + Monitor.Wait(Lock); } } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 2c58898c4c..d54c64e109 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -115,11 +115,6 @@ namespace Ryujinx.HLE System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; - if (ConfigurationState.Instance.System.EnableMulticoreScheduling) - { - System.EnableMultiCoreScheduling(); - } - System.EnablePtc = ConfigurationState.Instance.System.EnablePtc; System.FsIntegrityCheckLevel = GetIntegrityCheckLevel(); diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index 293a8dca92..8990bd9839 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -1,4 +1,4 @@ -{ +{ "version": 17, "res_scale": 1, "res_scale_custom": 1, @@ -22,7 +22,6 @@ "check_updates_on_start": true, "enable_vsync": true, "enable_shader_cache": true, - "enable_multicore_scheduling": true, "enable_ptc": false, "enable_fs_integrity_checks": true, "fs_global_access_log_mode": 0, diff --git a/Ryujinx/THIRDPARTY.md b/Ryujinx/THIRDPARTY.md index 94b7ec3765..c7015deb69 100644 --- a/Ryujinx/THIRDPARTY.md +++ b/Ryujinx/THIRDPARTY.md @@ -200,4 +200,29 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +# Atmosphère (MIT) +``` +MIT License + +Copyright (c) 2018-2020 Atmosphère-NX + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ``` \ No newline at end of file diff --git a/Ryujinx/Ui/SettingsWindow.cs b/Ryujinx/Ui/SettingsWindow.cs index 100246bc46..07878ee416 100644 --- a/Ryujinx/Ui/SettingsWindow.cs +++ b/Ryujinx/Ui/SettingsWindow.cs @@ -43,7 +43,6 @@ namespace Ryujinx.Ui [GUI] CheckButton _checkUpdatesToggle; [GUI] CheckButton _vSyncToggle; [GUI] CheckButton _shaderCacheToggle; - [GUI] CheckButton _multiSchedToggle; [GUI] CheckButton _ptcToggle; [GUI] CheckButton _fsicToggle; [GUI] CheckButton _ignoreToggle; @@ -188,11 +187,6 @@ namespace Ryujinx.Ui _shaderCacheToggle.Click(); } - if (ConfigurationState.Instance.System.EnableMulticoreScheduling) - { - _multiSchedToggle.Click(); - } - if (ConfigurationState.Instance.System.EnablePtc) { _ptcToggle.Click(); @@ -401,7 +395,6 @@ namespace Ryujinx.Ui ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active; ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active; - ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active; ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; @@ -490,7 +483,7 @@ namespace Ryujinx.Ui foreach (string directory in fileChooser.Filenames) { bool directoryAdded = false; - + if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter)) { do diff --git a/Ryujinx/Ui/SettingsWindow.glade b/Ryujinx/Ui/SettingsWindow.glade index ebc3fcb143..1090bf2cc1 100644 --- a/Ryujinx/Ui/SettingsWindow.glade +++ b/Ryujinx/Ui/SettingsWindow.glade @@ -1439,24 +1439,6 @@ 4 - - - Enable Multicore Scheduling - True - True - False - Enables or disables multi-core scheduling of threads - start - 5 - 5 - True - - - False - True - 5 - - Enable Profiled Persistent Translation Cache diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json index 1602b48a4a..fb055c0d9f 100644 --- a/Ryujinx/_schema.json +++ b/Ryujinx/_schema.json @@ -18,7 +18,6 @@ "system_region", "docked_mode", "enable_vsync", - "enable_multicore_scheduling", "enable_ptc", "enable_fs_integrity_checks", "fs_global_access_log_mode", @@ -1196,17 +1195,6 @@ false ] }, - "enable_multicore_scheduling": { - "$id": "#/properties/enable_multicore_scheduling", - "type": "boolean", - "title": "Enable Multicore Scheduling", - "description": "Enables or disables multi-core scheduling of threads", - "default": true, - "examples": [ - true, - false - ] - }, "enable_ptc": { "$id": "#/properties/enable_ptc", "type": "boolean",