Initial work to support changing thread core on the scheduler, also some cond var priority fixes

This commit is contained in:
gdkchan 2018-05-13 00:22:42 -03:00
parent 3603497a13
commit 4546d1b9be
9 changed files with 442 additions and 199 deletions

View file

@ -1,130 +1,16 @@
using Ryujinx.Core.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle.Handles
{
class KProcessScheduler : IDisposable
{
private const int LowestPriority = 0x40;
private class SchedulerThread : IDisposable
{
public KThread Thread { get; private set; }
public bool IsActive { get; set; }
public AutoResetEvent WaitSync { get; private set; }
public ManualResetEvent WaitActivity { get; private set; }
public AutoResetEvent WaitSched { get; private set; }
public SchedulerThread(KThread Thread)
{
this.Thread = Thread;
IsActive = true;
WaitSync = new AutoResetEvent(false);
WaitActivity = new ManualResetEvent(true);
WaitSched = new AutoResetEvent(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
WaitSync.Dispose();
WaitActivity.Dispose();
WaitSched.Dispose();
}
}
}
private class ThreadQueue
{
private List<SchedulerThread> Threads;
public ThreadQueue()
{
Threads = new List<SchedulerThread>();
}
public void Push(SchedulerThread Thread)
{
lock (Threads)
{
Threads.Add(Thread);
}
}
public SchedulerThread Pop(int MinPriority = LowestPriority)
{
lock (Threads)
{
SchedulerThread SchedThread;
int HighestPriority = MinPriority;
int HighestPrioIndex = -1;
for (int Index = 0; Index < Threads.Count; Index++)
{
SchedThread = Threads[Index];
if (HighestPriority > SchedThread.Thread.ActualPriority)
{
HighestPriority = SchedThread.Thread.ActualPriority;
HighestPrioIndex = Index;
}
}
if (HighestPrioIndex == -1)
{
return null;
}
SchedThread = Threads[HighestPrioIndex];
Threads.RemoveAt(HighestPrioIndex);
return SchedThread;
}
}
public bool HasThread(SchedulerThread SchedThread)
{
lock (Threads)
{
return Threads.Contains(SchedThread);
}
}
public bool Remove(SchedulerThread SchedThread)
{
lock (Threads)
{
return Threads.Remove(SchedThread);
}
}
}
private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
private ThreadQueue[] WaitingToRun;
private ThreadQueue WaitingToRun;
private HashSet<int> ActiveProcessors;
private int ActiveCores;
private object SchedLock;
@ -136,14 +22,7 @@ namespace Ryujinx.Core.OsHle.Handles
AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
WaitingToRun = new ThreadQueue[4];
for (int Index = 0; Index < 4; Index++)
{
WaitingToRun[Index] = new ThreadQueue();
}
ActiveProcessors = new HashSet<int>();
WaitingToRun = new ThreadQueue();
SchedLock = new object();
}
@ -159,7 +38,7 @@ namespace Ryujinx.Core.OsHle.Handles
return;
}
if (ActiveProcessors.Add(Thread.ProcessorId))
if (AddActiveCore(Thread))
{
Thread.Thread.Execute();
@ -167,7 +46,7 @@ namespace Ryujinx.Core.OsHle.Handles
}
else
{
WaitingToRun[Thread.ProcessorId].Push(SchedThread);
WaitingToRun.Push(SchedThread);
PrintDbgThreadInfo(Thread, "waiting to run.");
}
@ -182,18 +61,18 @@ namespace Ryujinx.Core.OsHle.Handles
{
if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
{
WaitingToRun[Thread.ProcessorId].Remove(SchedThread);
WaitingToRun.Remove(SchedThread);
SchedThread.Dispose();
}
SchedulerThread NewThread = WaitingToRun[Thread.ProcessorId].Pop();
SchedulerThread NewThread = WaitingToRun.Pop(Thread.ActualCore);
if (NewThread == null)
{
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ProcessorId}!");
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
ActiveProcessors.Remove(Thread.ProcessorId);
RemoveActiveCore(Thread.ActualCore);
return;
}
@ -228,7 +107,7 @@ namespace Ryujinx.Core.OsHle.Handles
throw new InvalidOperationException();
}
Suspend(Thread.ProcessorId);
Suspend(Thread);
SchedThread.WaitSync.WaitOne();
@ -242,7 +121,7 @@ namespace Ryujinx.Core.OsHle.Handles
throw new InvalidOperationException();
}
Suspend(Thread.ProcessorId);
Suspend(Thread);
bool Result = SchedThread.WaitSync.WaitOne(Timeout);
@ -261,11 +140,13 @@ namespace Ryujinx.Core.OsHle.Handles
SchedThread.WaitSync.Set();
}
public void Suspend(int ProcessorId)
public void Suspend(KThread Thread)
{
lock (SchedLock)
{
SchedulerThread SchedThread = WaitingToRun[ProcessorId].Pop();
PrintDbgThreadInfo(Thread, "suspended.");
SchedulerThread SchedThread = WaitingToRun.Pop(Thread.ActualCore);
if (SchedThread != null)
{
@ -273,9 +154,9 @@ namespace Ryujinx.Core.OsHle.Handles
}
else
{
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ProcessorId}!");
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
ActiveProcessors.Remove(ProcessorId);
RemoveActiveCore(Thread.ActualCore);
}
}
}
@ -288,7 +169,9 @@ namespace Ryujinx.Core.OsHle.Handles
{
lock (SchedLock)
{
SchedulerThread SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.ActualPriority);
SchedulerThread SchedThread = WaitingToRun.Pop(
Thread.ActualCore,
Thread.ActualPriority);
if (SchedThread == null)
{
@ -307,7 +190,7 @@ namespace Ryujinx.Core.OsHle.Handles
{
//Just stop running the thread if it's not active,
//and run whatever is waiting to run with the higuest priority.
Suspend(Thread.ProcessorId);
Suspend(Thread);
}
Resume(Thread);
@ -333,14 +216,14 @@ namespace Ryujinx.Core.OsHle.Handles
lock (SchedLock)
{
if (ActiveProcessors.Add(Thread.ProcessorId))
if (AddActiveCore(Thread))
{
PrintDbgThreadInfo(Thread, "resuming execution...");
return;
}
WaitingToRun[Thread.ProcessorId].Push(SchedThread);
WaitingToRun.Push(SchedThread);
PrintDbgThreadInfo(Thread, "entering wait state...");
}
@ -354,6 +237,8 @@ namespace Ryujinx.Core.OsHle.Handles
{
if (!SchedThread.Thread.Thread.Execute())
{
PrintDbgThreadInfo(SchedThread.Thread, "waked.");
SchedThread.WaitSched.Set();
}
else
@ -362,6 +247,14 @@ namespace Ryujinx.Core.OsHle.Handles
}
}
public void Resort(KThread Thread)
{
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
WaitingToRun.Resort(SchedThread);
}
}
private bool IsActive(KThread Thread)
{
if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
@ -372,11 +265,62 @@ namespace Ryujinx.Core.OsHle.Handles
return SchedThread.IsActive;
}
private bool AddActiveCore(KThread Thread)
{
lock (SchedLock)
{
//First, try running it on Ideal Core.
int CoreMask = 1 << Thread.IdealCore;
if ((ActiveCores & CoreMask) == 0)
{
ActiveCores |= CoreMask;
Thread.ActualCore = Thread.IdealCore;
return true;
}
//If that fails, then try running on any core allowed by Core Mask.
CoreMask = Thread.CoreMask & ~ActiveCores;
if (CoreMask != 0)
{
CoreMask &= -CoreMask;
ActiveCores |= CoreMask;
for (int Bit = 0; Bit < 32; Bit++)
{
if (((CoreMask >> Bit) & 1) != 0)
{
Thread.ActualCore = Bit;
return true;
}
}
throw new InvalidOperationException();
}
return false;
}
}
private void RemoveActiveCore(int Core)
{
lock (SchedLock)
{
ActiveCores &= ~(1 << Core);
}
}
private void PrintDbgThreadInfo(KThread Thread, string Message)
{
Log.PrintDebug(LogClass.KernelScheduler, "(" +
"ThreadId = " + Thread.ThreadId + ", " +
"ProcessorId = " + Thread.ProcessorId + ", " +
"ActualCore = " + Thread.ActualCore + ", " +
"IdealCore = " + Thread.IdealCore + ", " +
"ActualPriority = " + Thread.ActualPriority + ", " +
"WantedPriority = " + Thread.WantedPriority + ") " + Message);
}

View file

@ -7,9 +7,13 @@ namespace Ryujinx.Core.OsHle.Handles
{
public AThread Thread { get; private set; }
public int CoreMask { get; set; }
public long MutexAddress { get; set; }
public long CondVarAddress { get; set; }
private Process Process;
public KThread NextMutexThread { get; set; }
public KThread NextCondVarThread { get; set; }
@ -18,16 +22,24 @@ namespace Ryujinx.Core.OsHle.Handles
public int ActualPriority { get; private set; }
public int WantedPriority { get; private set; }
public int ProcessorId { get; private set; }
public int IdealCore { get; private set; }
public int ActualCore { get; set; }
public int WaitHandle { get; set; }
public int ThreadId => Thread.ThreadId;
public KThread(AThread Thread, int ProcessorId, int Priority)
public KThread(
AThread Thread,
Process Process,
int IdealCore,
int Priority)
{
this.Thread = Thread;
this.ProcessorId = ProcessorId;
this.Thread = Thread;
this.Process = Process;
this.IdealCore = IdealCore;
CoreMask = 1 << IdealCore;
ActualPriority = WantedPriority = Priority;
}
@ -54,59 +66,138 @@ namespace Ryujinx.Core.OsHle.Handles
{
ActualPriority = CurrPriority;
UpdateWaitList();
UpdateWaitLists();
MutexOwner?.UpdatePriority();
}
}
private void UpdateWaitList()
private void UpdateWaitLists()
{
UpdateMutexList();
UpdateCondVarList();
Process.Scheduler.Resort(this);
}
private void UpdateMutexList()
{
KThread OwnerThread = MutexOwner;
if (OwnerThread != null)
if (OwnerThread == null)
{
//The MutexOwner field should only be non null when the thread is
//waiting for the lock, and the lock belongs to another thread.
if (OwnerThread == this)
return;
}
//The MutexOwner field should only be non-null when the thread is
//waiting for the lock, and the lock belongs to another thread.
if (OwnerThread == this)
{
throw new InvalidOperationException();
}
lock (OwnerThread)
{
//Remove itself from the list.
KThread CurrThread = OwnerThread;
while (CurrThread.NextMutexThread != null)
{
throw new InvalidOperationException();
if (CurrThread.NextMutexThread == this)
{
CurrThread.NextMutexThread = NextMutexThread;
break;
}
CurrThread = CurrThread.NextMutexThread;
}
lock (OwnerThread)
{
//Remove itself from the list.
KThread CurrThread = OwnerThread;
//Re-add taking new priority into account.
CurrThread = OwnerThread;
while (CurrThread.NextMutexThread != null)
while (CurrThread.NextMutexThread != null)
{
if (CurrThread.NextMutexThread.ActualPriority > ActualPriority)
{
if (CurrThread.NextMutexThread == this)
break;
}
CurrThread = CurrThread.NextMutexThread;
}
NextMutexThread = CurrThread.NextMutexThread;
CurrThread.NextMutexThread = this;
}
}
private void UpdateCondVarList()
{
lock (Process.ThreadArbiterListLock)
{
if (Process.ThreadArbiterListHead == null)
{
return;
}
//Remove itself from the list.
bool Found;
KThread CurrThread = Process.ThreadArbiterListHead;
if (Found = (Process.ThreadArbiterListHead == this))
{
Process.ThreadArbiterListHead = Process.ThreadArbiterListHead.NextCondVarThread;
}
else
{
while (CurrThread.NextCondVarThread != null)
{
if (CurrThread.NextCondVarThread == this)
{
CurrThread.NextMutexThread = NextMutexThread;
CurrThread.NextCondVarThread = NextCondVarThread;
Found = true;
break;
}
CurrThread = CurrThread.NextMutexThread;
CurrThread = CurrThread.NextCondVarThread;
}
//Re-add taking new priority into account.
CurrThread = OwnerThread;
while (CurrThread.NextMutexThread != null)
{
if (CurrThread.NextMutexThread.ActualPriority < ActualPriority)
{
break;
}
CurrThread = CurrThread.NextMutexThread;
}
NextMutexThread = CurrThread.NextMutexThread;
CurrThread.NextMutexThread = this;
}
if (!Found)
{
return;
}
//Re-add taking new priority into account.
if (Process.ThreadArbiterListHead == null ||
Process.ThreadArbiterListHead.ActualPriority > ActualPriority)
{
NextCondVarThread = Process.ThreadArbiterListHead;
Process.ThreadArbiterListHead = this;
return;
}
CurrThread = Process.ThreadArbiterListHead;
while (CurrThread.NextCondVarThread != null)
{
if (CurrThread.NextCondVarThread.ActualPriority > ActualPriority)
{
break;
}
CurrThread = CurrThread.NextCondVarThread;
}
NextCondVarThread = CurrThread.NextCondVarThread;
CurrThread.NextCondVarThread = this;
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Threading;
namespace Ryujinx.Core.OsHle.Handles
{
class SchedulerThread : IDisposable
{
public KThread Thread { get; private set; }
public SchedulerThread Next { get; set; }
public bool IsActive { get; set; }
public AutoResetEvent WaitSync { get; private set; }
public ManualResetEvent WaitActivity { get; private set; }
public AutoResetEvent WaitSched { get; private set; }
public SchedulerThread(KThread Thread)
{
this.Thread = Thread;
IsActive = true;
WaitSync = new AutoResetEvent(false);
WaitActivity = new ManualResetEvent(true);
WaitSched = new AutoResetEvent(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
WaitSync.Dispose();
WaitActivity.Dispose();
WaitSched.Dispose();
}
}
}
}

View file

@ -0,0 +1,158 @@
namespace Ryujinx.Core.OsHle.Handles
{
class ThreadQueue
{
private const int LowestPriority = 0x40;
private SchedulerThread Head;
private object ListLock;
public ThreadQueue()
{
ListLock = new object();
}
public void Push(SchedulerThread Wait)
{
lock (ListLock)
{
//Ensure that we're not creating circular references
//by adding a thread that is already on the list.
if (HasThread(Wait))
{
return;
}
if (Head == null || Head.Thread.ActualPriority > Wait.Thread.ActualPriority)
{
Wait.Next = Head;
Head = Wait;
return;
}
SchedulerThread Curr = Head;
while (Curr.Next != null)
{
if (Curr.Next.Thread.ActualPriority > Wait.Thread.ActualPriority)
{
break;
}
Curr = Curr.Next;
}
Wait.Next = Curr.Next;
Curr.Next = Wait;
}
}
public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
{
lock (ListLock)
{
int CoreMask = 1 << Core;
SchedulerThread Prev = null;
SchedulerThread Curr = Head;
while (Curr != null)
{
KThread Thread = Curr.Thread;
if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
{
if (Prev != null)
{
Prev.Next = Curr.Next;
}
else
{
Head = Head.Next;
}
break;
}
Prev = Curr;
Curr = Curr.Next;
}
return Curr;
}
}
public bool Remove(SchedulerThread Thread)
{
lock (ListLock)
{
if (Head == null)
{
return false;
}
else if (Head == Thread)
{
Head = Head.Next;
return true;
}
SchedulerThread Prev = Head;
SchedulerThread Curr = Head.Next;
while (Curr != null)
{
if (Curr == Thread)
{
Prev.Next = Curr.Next;
return true;
}
Prev = Curr;
Curr = Curr.Next;
}
return false;
}
}
public bool Resort(SchedulerThread Thread)
{
lock (ListLock)
{
if (Remove(Thread))
{
Push(Thread);
return true;
}
return false;
}
}
public bool HasThread(SchedulerThread Thread)
{
lock (ListLock)
{
SchedulerThread Curr = Head;
while (Curr != null)
{
if (Curr == Thread)
{
return true;
}
Curr = Curr.Next;
}
return false;
}
}
}
}

View file

@ -22,8 +22,6 @@ namespace Ryujinx.Core.OsHle.Kernel
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
private object CondVarLock;
private HashSet<(HSharedMem, long)> MappedSharedMems;
private ulong CurrentHeapSize;
@ -80,8 +78,6 @@ namespace Ryujinx.Core.OsHle.Kernel
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
CondVarLock = new object();
MappedSharedMems = new HashSet<(HSharedMem, long)>();
}

View file

@ -131,7 +131,7 @@ namespace Ryujinx.Core.OsHle.Kernel
Handles[HandlesCount] = WaitEvent;
Process.Scheduler.Suspend(CurrThread.ProcessorId);
Process.Scheduler.Suspend(CurrThread);
int HandleIndex;
@ -237,7 +237,7 @@ namespace Ryujinx.Core.OsHle.Kernel
if (Session != null)
{
Process.Scheduler.Suspend(CurrThread.ProcessorId);
Process.Scheduler.Suspend(CurrThread);
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);

View file

@ -75,7 +75,7 @@ namespace Ryujinx.Core.OsHle.Kernel
}
else
{
Process.Scheduler.Suspend(CurrThread.ProcessorId);
Process.Scheduler.Suspend(CurrThread);
Thread.Sleep(NsTimeConverter.GetTimeMs(Ns));
@ -132,7 +132,7 @@ namespace Ryujinx.Core.OsHle.Kernel
private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
{
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ProcessorId;
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
}
private void SvcGetThreadId(AThreadState ThreadState)

View file

@ -260,17 +260,23 @@ namespace Ryujinx.Core.OsHle.Kernel
WaitThread.MutexAddress = MutexAddress;
WaitThread.CondVarAddress = CondVarAddress;
lock (CondVarLock)
lock (Process.ThreadArbiterListLock)
{
KThread CurrThread = Process.ThreadArbiterList;
KThread CurrThread = Process.ThreadArbiterListHead;
if (CurrThread != null)
if (CurrThread == null || CurrThread.ActualPriority > WaitThread.ActualPriority)
{
WaitThread.NextCondVarThread = Process.ThreadArbiterListHead;
Process.ThreadArbiterListHead = WaitThread;
}
else
{
bool DoInsert = CurrThread != WaitThread;
while (CurrThread.NextCondVarThread != null)
{
if (CurrThread.NextCondVarThread.ActualPriority < WaitThread.ActualPriority)
if (CurrThread.NextCondVarThread.ActualPriority > WaitThread.ActualPriority)
{
break;
}
@ -293,10 +299,6 @@ namespace Ryujinx.Core.OsHle.Kernel
CurrThread.NextCondVarThread = WaitThread;
}
}
else
{
Process.ThreadArbiterList = WaitThread;
}
}
Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
@ -315,10 +317,10 @@ namespace Ryujinx.Core.OsHle.Kernel
private void CondVarSignal(long CondVarAddress, int Count)
{
lock (CondVarLock)
lock (Process.ThreadArbiterListLock)
{
KThread PrevThread = null;
KThread CurrThread = Process.ThreadArbiterList;
KThread CurrThread = Process.ThreadArbiterListHead;
while (CurrThread != null && (Count == -1 || Count > 0))
{
@ -330,7 +332,7 @@ namespace Ryujinx.Core.OsHle.Kernel
}
else
{
Process.ThreadArbiterList = CurrThread.NextCondVarThread;
Process.ThreadArbiterListHead = CurrThread.NextCondVarThread;
}
CurrThread.NextCondVarThread = null;
@ -401,7 +403,7 @@ namespace Ryujinx.Core.OsHle.Kernel
return;
}
if (CurrThread.NextMutexThread.ActualPriority < WaitThread.ActualPriority)
if (CurrThread.NextMutexThread.ActualPriority > WaitThread.ActualPriority)
{
break;
}

View file

@ -38,7 +38,9 @@ namespace Ryujinx.Core.OsHle
public KProcessScheduler Scheduler { get; private set; }
public KThread ThreadArbiterList { get; set; }
public KThread ThreadArbiterListHead { get; set; }
public object ThreadArbiterListLock { get; private set; }
public KProcessHandleTable HandleTable { get; private set; }
@ -70,6 +72,8 @@ namespace Ryujinx.Core.OsHle
Memory = new AMemory();
ThreadArbiterListLock = new object();
HandleTable = new KProcessHandleTable();
AppletState = new AppletStateMgr();
@ -196,7 +200,7 @@ namespace Ryujinx.Core.OsHle
AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint);
KThread Thread = new KThread(CpuThread, ProcessorId, Priority);
KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority);
int Handle = HandleTable.OpenHandle(Thread);