From 2525bafe068ca018306ac7cf65c675f27c05de2d Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Wed, 30 Nov 2022 02:58:02 +0530 Subject: [PATCH] Consolidate thread yielding in `Scheduler` There's multiple locations where a thread is yielded in the scheduler and all of them repeat the code of checking for `pendingYield` and signalling with an optional optimization of checking if the thread being yielded is the calling thread. All this functionality has now been consolidated into `Scheduler::YieldThread` which checks for `pendingYield` and does the calling thread yield optimization. This should lead to better readability and better performance in cases where `UpdatePriority` would signal the calling thread. --- app/src/main/cpp/skyline/kernel/scheduler.cpp | 46 ++++++++----------- app/src/main/cpp/skyline/kernel/scheduler.h | 5 ++ 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/app/src/main/cpp/skyline/kernel/scheduler.cpp b/app/src/main/cpp/skyline/kernel/scheduler.cpp index ccabc25a..0f78c153 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.cpp +++ b/app/src/main/cpp/skyline/kernel/scheduler.cpp @@ -85,6 +85,22 @@ namespace skyline::kernel { return *currentCore; } + void Scheduler::YieldThread(const std::shared_ptr &thread) { + if (state.thread != thread) { + // If another thread is being yielded, we need to send it an OS signal to yield + if (!thread->pendingYield) { + // We only want to yield the thread if it hasn't already been sent a signal to yield in the past + // Not doing this can lead to races and deadlocks but is also slower as it prevents redundant signals + thread->SendSignal(YieldSignal); + thread->pendingYield = true; + } + } else { + // If the calling thread is being yielded, we can just set the YieldPending flag + // This avoids an OS signal which would just flip the YieldPending flag but with significantly more overhead + YieldPending = true; + } + } + void Scheduler::InsertThread(const std::shared_ptr &thread) { std::scoped_lock migrationLock{thread->coreMigrationMutex}; auto &core{cores.at(thread->coreId)}; @@ -117,19 +133,7 @@ namespace skyline::kernel { core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), front->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin()); core.queue.push_front(thread); - if (state.thread != front) { - // If the calling thread isn't at the front, we need to send it an OS signal to yield - if (!front->pendingYield) { - // We only want to yield the thread if it hasn't already been sent a signal to yield in the past - // Not doing this can lead to races and deadlocks but is also slower as it prevents redundant signals - front->SendSignal(YieldSignal); - front->pendingYield = true; - } - } else { - // If the calling thread at the front is being yielded, we can just set the YieldPending flag - // This avoids an OS signal which would just flip the YieldPending flag but with significantly more overhead - YieldPending = true; - } + YieldThread(front); } else { core.queue.push_front(thread); } @@ -287,10 +291,7 @@ namespace skyline::kernel { } else if (currentIt == core->queue.begin()) { // Alternatively, if it's currently running then we'd just want to yield if there's a higher priority thread to run instead if (nextIt != core->queue.end() && (*nextIt)->priority < thread->priority) { - if (!thread->pendingYield) { - thread->SendSignal(YieldSignal); - thread->pendingYield = true; - } + YieldThread(thread); } else if (!thread->isPreempted && thread->priority == core->preemptionPriority) { // If the thread needs to be preempted due to its new priority then arm its preemption timer thread->ArmPreemptionTimer(PreemptiveTimeslice); @@ -305,11 +306,7 @@ namespace skyline::kernel { auto targetIt{std::upper_bound(core->queue.begin(), core->queue.end(), thread->priority.load(), type::KThread::IsHigherPriority)}; if (targetIt == core->queue.begin() && targetIt != core->queue.end()) { core->queue.insert(std::next(core->queue.begin()), thread); - auto front{core->queue.front()}; - if (!front->pendingYield) { - front->SendSignal(YieldSignal); - front->pendingYield = true; - } + YieldThread(core->queue.front()); } else { core->queue.insert(targetIt, thread); } @@ -381,10 +378,7 @@ namespace skyline::kernel { if (it == core->queue.begin()) { // We need to send a yield signal to the thread if it's currently running - if (!thread->pendingYield) { - thread->SendSignal(YieldSignal); - thread->pendingYield = true; - } + YieldThread(thread); thread->forceYield = true; } } else { diff --git a/app/src/main/cpp/skyline/kernel/scheduler.h b/app/src/main/cpp/skyline/kernel/scheduler.h index fe01a875..ab3d5ad3 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.h +++ b/app/src/main/cpp/skyline/kernel/scheduler.h @@ -64,6 +64,11 @@ namespace skyline { */ void MigrateToCore(const std::shared_ptr &thread, CoreContext *¤tCore, CoreContext *targetCore, std::unique_lock &lock); + /** + * @brief Trigger a thread to yield via a signal or on SVC exit if it is the current thread + */ + void YieldThread(const std::shared_ptr &thread); + public: static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding inline static int YieldSignal{SIGRTMIN}; //!< The signal used to cause a non-cooperative yield in running threads