summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/thread_queue_list.h16
-rw-r--r--src/core/hle/kernel/scheduler.cpp66
-rw-r--r--src/core/hle/kernel/scheduler.h69
-rw-r--r--src/core/hle/kernel/svc.cpp38
-rw-r--r--src/core/hle/kernel/thread.h1
5 files changed, 181 insertions, 9 deletions
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 133122c5f..e7594db68 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -49,6 +49,22 @@ struct ThreadQueueList {
49 return T(); 49 return T();
50 } 50 }
51 51
52 template <typename UnaryPredicate>
53 T get_first_filter(UnaryPredicate filter) const {
54 const Queue* cur = first;
55 while (cur != nullptr) {
56 if (!cur->data.empty()) {
57 for (const auto& item : cur->data) {
58 if (filter(item))
59 return item;
60 }
61 }
62 cur = cur->next_nonempty;
63 }
64
65 return T();
66 }
67
52 T pop_first() { 68 T pop_first() {
53 Queue* cur = first; 69 Queue* cur = first;
54 while (cur != nullptr) { 70 while (cur != nullptr) {
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 5a5f4cef1..df4d6cf0a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -9,6 +9,7 @@
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "core/arm/arm_interface.h" 10#include "core/arm/arm_interface.h"
11#include "core/core.h" 11#include "core/core.h"
12#include "core/core_cpu.h"
12#include "core/core_timing.h" 13#include "core/core_timing.h"
13#include "core/hle/kernel/kernel.h" 14#include "core/hle/kernel/kernel.h"
14#include "core/hle/kernel/process.h" 15#include "core/hle/kernel/process.h"
@@ -179,4 +180,69 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
179 ready_queue.prepare(priority); 180 ready_queue.prepare(priority);
180} 181}
181 182
183Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
184 std::lock_guard<std::mutex> lock(scheduler_mutex);
185
186 const u32 mask = 1U << core;
187 return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
188 return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
189 });
190}
191
192void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
193 ASSERT(thread != nullptr);
194 // Avoid yielding if the thread isn't even running.
195 ASSERT(thread->GetStatus() == ThreadStatus::Running);
196
197 // Sanity check that the priority is valid
198 ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
199
200 // Yield this thread -- sleep for zero time and force reschedule to different thread
201 WaitCurrentThread_Sleep();
202 GetCurrentThread()->WakeAfterDelay(0);
203}
204
205void Scheduler::YieldWithLoadBalancing(Thread* thread) {
206 ASSERT(thread != nullptr);
207 const auto priority = thread->GetPriority();
208 const auto core = static_cast<u32>(thread->GetProcessorID());
209
210 // Avoid yielding if the thread isn't even running.
211 ASSERT(thread->GetStatus() == ThreadStatus::Running);
212
213 // Sanity check that the priority is valid
214 ASSERT(priority < THREADPRIO_COUNT);
215
216 // Sleep for zero time to be able to force reschedule to different thread
217 WaitCurrentThread_Sleep();
218 GetCurrentThread()->WakeAfterDelay(0);
219
220 Thread* suggested_thread = nullptr;
221
222 // Search through all of the cpu cores (except this one) for a suggested thread.
223 // Take the first non-nullptr one
224 for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
225 const auto res =
226 Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
227 core, priority);
228
229 // If scheduler provides a suggested thread
230 if (res != nullptr) {
231 // And its better than the current suggested thread (or is the first valid one)
232 if (suggested_thread == nullptr ||
233 suggested_thread->GetPriority() > res->GetPriority()) {
234 suggested_thread = res;
235 }
236 }
237 }
238
239 // If a suggested thread was found, queue that for this core
240 if (suggested_thread != nullptr)
241 suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
242}
243
244void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
245 UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
246}
247
182} // namespace Kernel 248} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index c63032b7d..97ced4dfc 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -51,6 +51,75 @@ public:
51 /// Sets the priority of a thread in the scheduler 51 /// Sets the priority of a thread in the scheduler
52 void SetThreadPriority(Thread* thread, u32 priority); 52 void SetThreadPriority(Thread* thread, u32 priority);
53 53
54 /// Gets the next suggested thread for load balancing
55 Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const;
56
57 /**
58 * YieldWithoutLoadBalancing -- analogous to normal yield on a system
59 * Moves the thread to the end of the ready queue for its priority, and then reschedules the
60 * system to the new head of the queue.
61 *
62 * Example (Single Core -- but can be extrapolated to multi):
63 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
64 * Currently Running: ThreadR
65 *
66 * ThreadR calls YieldWithoutLoadBalancing
67 *
68 * ThreadR is moved to the end of ready_queue[prio=0]:
69 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
70 * Currently Running: Nothing
71 *
72 * System is rescheduled (ThreadA is popped off of queue):
73 * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
74 * Currently Running: ThreadA
75 *
76 * If the queue is empty at time of call, no yielding occurs. This does not cross between cores
77 * or priorities at all.
78 */
79 void YieldWithoutLoadBalancing(Thread* thread);
80
81 /**
82 * YieldWithLoadBalancing -- yield but with better selection of the new running thread
83 * Moves the current thread to the end of the ready queue for its priority, then selects a
84 * 'suggested thread' (a thread on a different core that could run on this core) from the
85 * scheduler, changes its core, and reschedules the current core to that thread.
86 *
87 * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were
88 * single core):
89 * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
90 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
91 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
92 *
93 * ThreadQ calls YieldWithLoadBalancing
94 *
95 * ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
96 * ready_queue[core=0][prio=0]: ThreadA, ThreadB
97 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
98 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
99 *
100 * A list of suggested threads for each core is compiled
101 * Suggested Threads: {ThreadC on Core 1}
102 * If this were quad core (as the switch is), there could be between 0 and 3 threads in this
103 * list. If there are more than one, the thread is selected by highest prio.
104 *
105 * ThreadC is core changed to Core 0:
106 * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
107 * ready_queue[core=1][prio=0]: ThreadD
108 * Currently Running: None on Core 0 || ThreadP on Core 1
109 *
110 * System is rescheduled (ThreadC is popped off of queue):
111 * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
112 * ready_queue[core=1][prio=0]: ThreadD
113 * Currently Running: ThreadC on Core 0 || ThreadP on Core 1
114 *
115 * If no suggested threads can be found this will behave just as normal yield. If there are
116 * multiple candidates for the suggested thread on a core, the highest prio is taken.
117 */
118 void YieldWithLoadBalancing(Thread* thread);
119
120 /// Currently unknown -- asserts as unimplemented on call
121 void YieldAndWaitForLoadBalancing(Thread* thread);
122
54 /// Returns a list of all threads managed by the scheduler 123 /// Returns a list of all threads managed by the scheduler
55 const std::vector<SharedPtr<Thread>>& GetThreadList() const { 124 const std::vector<SharedPtr<Thread>>& GetThreadList() const {
56 return thread_list; 125 return thread_list;
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 5d36792ca..348a22904 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1208,18 +1208,38 @@ static void ExitThread() {
1208static void SleepThread(s64 nanoseconds) { 1208static void SleepThread(s64 nanoseconds) {
1209 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); 1209 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
1210 1210
1211 // Don't attempt to yield execution if there are no available threads to run, 1211 enum class SleepType : s64 {
1212 // this way we avoid a useless reschedule to the idle thread. 1212 YieldWithoutLoadBalancing = 0,
1213 if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) 1213 YieldWithLoadBalancing = -1,
1214 return; 1214 YieldAndWaitForLoadBalancing = -2,
1215 };
1215 1216
1216 // Sleep current thread and check for next thread to schedule 1217 if (nanoseconds <= 0) {
1217 WaitCurrentThread_Sleep(); 1218 auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
1219 switch (static_cast<SleepType>(nanoseconds)) {
1220 case SleepType::YieldWithoutLoadBalancing:
1221 scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
1222 break;
1223 case SleepType::YieldWithLoadBalancing:
1224 scheduler.YieldWithLoadBalancing(GetCurrentThread());
1225 break;
1226 case SleepType::YieldAndWaitForLoadBalancing:
1227 scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
1228 break;
1229 default:
1230 UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
1231 }
1232 } else {
1233 // Sleep current thread and check for next thread to schedule
1234 WaitCurrentThread_Sleep();
1218 1235
1219 // Create an event to wake the thread up after the specified nanosecond delay has passed 1236 // Create an event to wake the thread up after the specified nanosecond delay has passed
1220 GetCurrentThread()->WakeAfterDelay(nanoseconds); 1237 GetCurrentThread()->WakeAfterDelay(nanoseconds);
1238 }
1221 1239
1222 Core::System::GetInstance().PrepareReschedule(); 1240 // Reschedule all CPU cores
1241 for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
1242 Core::System::GetInstance().CpuCore(i).PrepareReschedule();
1223} 1243}
1224 1244
1225/// Wait process wide key atomic 1245/// Wait process wide key atomic
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index d384d50db..77aec099a 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -26,6 +26,7 @@ enum ThreadPriority : u32 {
26 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps 26 THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
27 THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps 27 THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
28 THREADPRIO_LOWEST = 63, ///< Lowest thread priority 28 THREADPRIO_LOWEST = 63, ///< Lowest thread priority
29 THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
29}; 30};
30 31
31enum ThreadProcessorId : s32 { 32enum ThreadProcessorId : s32 {