diff options
| -rw-r--r-- | src/common/thread_queue_list.h | 16 | ||||
| -rw-r--r-- | src/core/hle/kernel/scheduler.cpp | 66 | ||||
| -rw-r--r-- | src/core/hle/kernel/scheduler.h | 69 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc.cpp | 38 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 1 | ||||
| -rw-r--r-- | src/core/hle/service/service.cpp | 14 | ||||
| -rw-r--r-- | src/core/hle/service/service.h | 8 |
7 files changed, 193 insertions, 19 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 | ||
| 183 | Thread* 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 | |||
| 192 | void 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 | |||
| 205 | void 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 | |||
| 244 | void 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() { | |||
| 1208 | static void SleepThread(s64 nanoseconds) { | 1208 | static 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 | ||
| 31 | enum ThreadProcessorId : s32 { | 32 | enum ThreadProcessorId : s32 { |
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index d41df3732..d25b80ab0 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -97,29 +97,33 @@ ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_ses | |||
| 97 | ServiceFrameworkBase::~ServiceFrameworkBase() = default; | 97 | ServiceFrameworkBase::~ServiceFrameworkBase() = default; |
| 98 | 98 | ||
| 99 | void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { | 99 | void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { |
| 100 | ASSERT(port == nullptr); | 100 | ASSERT(!port_installed); |
| 101 | port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); | 101 | |
| 102 | auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); | ||
| 102 | port->SetHleHandler(shared_from_this()); | 103 | port->SetHleHandler(shared_from_this()); |
| 104 | port_installed = true; | ||
| 103 | } | 105 | } |
| 104 | 106 | ||
| 105 | void ServiceFrameworkBase::InstallAsNamedPort() { | 107 | void ServiceFrameworkBase::InstallAsNamedPort() { |
| 106 | ASSERT(port == nullptr); | 108 | ASSERT(!port_installed); |
| 107 | 109 | ||
| 108 | auto& kernel = Core::System::GetInstance().Kernel(); | 110 | auto& kernel = Core::System::GetInstance().Kernel(); |
| 109 | auto [server_port, client_port] = | 111 | auto [server_port, client_port] = |
| 110 | Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); | 112 | Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); |
| 111 | server_port->SetHleHandler(shared_from_this()); | 113 | server_port->SetHleHandler(shared_from_this()); |
| 112 | kernel.AddNamedPort(service_name, std::move(client_port)); | 114 | kernel.AddNamedPort(service_name, std::move(client_port)); |
| 115 | port_installed = true; | ||
| 113 | } | 116 | } |
| 114 | 117 | ||
| 115 | Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { | 118 | Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { |
| 116 | ASSERT(port == nullptr); | 119 | ASSERT(!port_installed); |
| 117 | 120 | ||
| 118 | auto& kernel = Core::System::GetInstance().Kernel(); | 121 | auto& kernel = Core::System::GetInstance().Kernel(); |
| 119 | auto [server_port, client_port] = | 122 | auto [server_port, client_port] = |
| 120 | Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); | 123 | Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); |
| 121 | port = MakeResult(std::move(server_port)).Unwrap(); | 124 | auto port = MakeResult(std::move(server_port)).Unwrap(); |
| 122 | port->SetHleHandler(shared_from_this()); | 125 | port->SetHleHandler(shared_from_this()); |
| 126 | port_installed = true; | ||
| 123 | return client_port; | 127 | return client_port; |
| 124 | } | 128 | } |
| 125 | 129 | ||
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 98483ecf1..029533628 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h | |||
| @@ -96,11 +96,9 @@ private: | |||
| 96 | /// Maximum number of concurrent sessions that this service can handle. | 96 | /// Maximum number of concurrent sessions that this service can handle. |
| 97 | u32 max_sessions; | 97 | u32 max_sessions; |
| 98 | 98 | ||
| 99 | /** | 99 | /// Flag to store if a port was already create/installed to detect multiple install attempts, |
| 100 | * Port where incoming connections will be received. Only created when InstallAsService() or | 100 | /// which is not supported. |
| 101 | * InstallAsNamedPort() are called. | 101 | bool port_installed = false; |
| 102 | */ | ||
| 103 | Kernel::SharedPtr<Kernel::ServerPort> port; | ||
| 104 | 102 | ||
| 105 | /// Function used to safely up-cast pointers to the derived class before invoking a handler. | 103 | /// Function used to safely up-cast pointers to the derived class before invoking a handler. |
| 106 | InvokerFn* handler_invoker; | 104 | InvokerFn* handler_invoker; |