From f41bcd1e2298692f6668876353316e428c4c55b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Tue, 8 Dec 2020 16:13:54 +0530 Subject: [PATCH] Implement Preemptive Scheduling --- app/src/main/cpp/skyline/kernel/scheduler.cpp | 59 ++++++++++++++++--- app/src/main/cpp/skyline/kernel/scheduler.h | 21 +++++-- app/src/main/cpp/skyline/kernel/svc.cpp | 2 +- .../main/cpp/skyline/kernel/types/KThread.cpp | 10 ++++ .../main/cpp/skyline/kernel/types/KThread.h | 7 +++ app/src/main/cpp/skyline/nce.cpp | 10 +++- app/src/main/cpp/skyline/vfs/npdm.cpp | 4 +- 7 files changed, 96 insertions(+), 17 deletions(-) diff --git a/app/src/main/cpp/skyline/kernel/scheduler.cpp b/app/src/main/cpp/skyline/kernel/scheduler.cpp index e1514dd8..d12ab92e 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.cpp +++ b/app/src/main/cpp/skyline/kernel/scheduler.cpp @@ -1,18 +1,30 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include #include "types/KThread.h" #include "scheduler.h" namespace skyline::kernel { - Scheduler::CoreContext::CoreContext(u8 id) : id(id) {} + Scheduler::CoreContext::CoreContext(u8 id, u8 preemptionPriority) : id(id), preemptionPriority(preemptionPriority) {} Scheduler::Scheduler(const DeviceState &state) : state(state) {} + void Scheduler::SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls) { + if (*tls) { + const auto &state{*reinterpret_cast(*tls)->state}; + state.scheduler->Rotate(false); + state.scheduler->WaitSchedule(); + YieldPending = false; + } else { + YieldPending = true; + } + } + Scheduler::CoreContext &Scheduler::LoadBalance() { auto &thread{state.thread}; - auto currentCore{&cores.at(thread->coreId)}; + auto *currentCore{&cores.at(thread->coreId)}; if (!currentCore->queue.empty() && thread->affinityMask.count() != 1) { // Select core where the current thread will be scheduled the earliest based off average timeslice durations for resident threads @@ -71,15 +83,26 @@ namespace skyline::kernel { auto &thread{state.thread}; auto &core{loadBalance ? LoadBalance() : cores.at(thread->coreId)}; + signal::SetSignalHandler({YieldSignal}, SignalHandler); + + if (!thread->preemptionTimer) { + struct sigevent event{ + .sigev_signo = YieldSignal, + .sigev_notify = SIGEV_THREAD_ID, + .sigev_notify_thread_id = gettid(), + }; + timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &*thread->preemptionTimer); + } + std::unique_lock lock(core.mutex); auto nextThread{std::find_if(core.queue.begin(), core.queue.end(), [&](const std::shared_ptr &it) { return it->priority > thread->priority; })}; if (nextThread == core.queue.begin() && nextThread != core.queue.end()) { - throw exception("Migration Interrupt Required"); + core.queue.front()->SendSignal(YieldSignal); + core.queue.insert(std::next(core.queue.begin()), thread); } else { core.queue.insert(nextThread, thread); + core.mutateCondition.notify_all(); // We only want to trigger the conditional variable if the current thread isn't going to be scheduled next } - - core.mutateCondition.notify_all(); } void Scheduler::WaitSchedule() { @@ -88,7 +111,7 @@ namespace skyline::kernel { std::shared_lock lock(core->mutex); if (thread->affinityMask.count() > 1) { - std::chrono::milliseconds loadBalanceThreshold{1}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing + std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing while (!core->mutateCondition.wait_for(lock, loadBalanceThreshold, [&]() { return core->queue.front() == thread; })) { lock.unlock(); LoadBalance(); @@ -106,10 +129,16 @@ namespace skyline::kernel { core->mutateCondition.wait(lock, [&]() { return core->queue.front() == thread; }); } + if (thread->priority == core->preemptionPriority) { + struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast(PreemptiveTimeslice).count()}}; + timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + thread->isPreempted = true; + } + thread->timesliceStart = util::GetTimeTicks(); } - void Scheduler::Rotate() { + void Scheduler::Rotate(bool cooperative) { auto &thread{state.thread}; auto &core{cores.at(thread->coreId)}; @@ -122,6 +151,14 @@ namespace skyline::kernel { core.mutateCondition.notify_all(); } + + if (cooperative && thread->isPreempted) { + // If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer + struct itimerspec spec{}; + timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + } + + thread->isPreempted = false; } void Scheduler::RemoveThread() { @@ -130,5 +167,13 @@ namespace skyline::kernel { std::unique_lock lock(core.mutex); core.queue.erase(std::remove(core.queue.begin(), core.queue.end(), thread), core.queue.end()); + + if (thread->isPreempted) { + struct itimerspec spec{}; + timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + thread->isPreempted = false; + } + + YieldPending = false; } } diff --git a/app/src/main/cpp/skyline/kernel/scheduler.h b/app/src/main/cpp/skyline/kernel/scheduler.h index 5996a61d..094eb86c 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.h +++ b/app/src/main/cpp/skyline/kernel/scheduler.h @@ -8,7 +8,7 @@ namespace skyline { namespace constant { - constexpr u8 CoreCount{4}; // The amount of cores an HOS process can be scheduled onto (User applications can only be on the first 3 cores, the last one is reserved for the system) + constexpr u8 CoreCount{4}; //!< The amount of cores an HOS process can be scheduled onto (User applications can only be on the first 3 cores, the last one is reserved for the system) } namespace kernel { @@ -44,22 +44,32 @@ namespace skyline { struct CoreContext { u8 id; + u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative std::shared_mutex mutex; //!< Synchronizes all operations on the queue std::condition_variable_any mutateCondition; //!< A conditional variable which is signalled on every mutation of the queue std::deque> queue; //!< A queue of threads which are running or to be run on this core - explicit CoreContext(u8 id); + CoreContext(u8 id, u8 preemptionPriority); }; - std::array cores{CoreContext(0), CoreContext(1), CoreContext(2), CoreContext(3)}; + std::array cores{CoreContext(0, 59), CoreContext(1, 59), CoreContext(2, 59), CoreContext(3, 63)}; 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 yield in running threads + inline static thread_local bool YieldPending{}; //!< A flag denoting if a yield is pending on this thread, it's checked at SVC exit + Scheduler(const DeviceState &state); + /** + * @brief A signal handler designed to cause a non-cooperative yield for preemption and higher priority threads being inserted + */ + static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls); + /** * @brief Checks all cores and migrates the calling thread to the core where the calling thread should be scheduled the earliest * @return A reference to the CoreContext of the core which the calling thread is running on after load balancing - * @note This doesn't insert the thread into the migrated process's queue after load-balancing + * @note This doesn't insert the thread into the migrated process's queue after load balancing */ CoreContext& LoadBalance(); @@ -77,8 +87,9 @@ namespace skyline { /** * @brief Rotates the calling thread's resident core queue, if it is at the front of it + * @param cooperative If this was triggered by a cooperative yield as opposed to a preemptive one */ - void Rotate(); + void Rotate(bool cooperative = true); /** * @brief Removes the calling thread from it's resident core queue diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index b5619416..dd65f1b1 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -299,7 +299,7 @@ namespace skyline::kernel::svc { state.scheduler->WaitSchedule(); } else if (in == yieldWithoutCoreMigration || in == yieldWithCoreMigration || in == yieldToAnyThread) { // Core Migration doesn't affect us as threads schedule and load balance themselves - state.logger->Debug("svcSleepThread: Yielding thread ({})", in); + state.logger->Debug("svcSleepThread: Cooperative Yield"); state.scheduler->Rotate(); state.scheduler->WaitSchedule(); } diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.cpp b/app/src/main/cpp/skyline/kernel/types/KThread.cpp index 5bda2adf..a8897644 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KThread.cpp @@ -7,6 +7,7 @@ #include #include #include "KProcess.h" +#include "KThread.h" namespace skyline::kernel::type { KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) { @@ -23,6 +24,9 @@ namespace skyline::kernel::type { } if (thread.joinable()) thread.join(); + + if (preemptionTimer) + timer_delete(*preemptionTimer); } void KThread::StartThread() { @@ -188,6 +192,12 @@ namespace skyline::kernel::type { } } + void KThread::SendSignal(int signal) { + std::lock_guard lock(mutex); + if (running) + pthread_kill(pthread, signal); + } + void KThread::UpdatePriority(i8 priority) { this->priority = priority; } diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.h b/app/src/main/cpp/skyline/kernel/types/KThread.h index 2ce4f81e..7e75627b 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.h +++ b/app/src/main/cpp/skyline/kernel/types/KThread.h @@ -44,6 +44,8 @@ namespace skyline { CoreMask affinityMask{}; //!< A mask of CPU cores this thread is allowed to run on u64 timesliceStart{}; //!< Start of the scheduler timeslice u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread + std::optional preemptionTimer{}; //!< A kernel timer used for preemption interrupts + bool isPreempted{}; //!< If the preemption timer has been armed and will fire KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore); @@ -61,6 +63,11 @@ namespace skyline { */ void Kill(bool join); + /** + * @brief Sends a host OS signal to the thread which is running this KThread + */ + void SendSignal(int signal); + void UpdatePriority(i8 priority); }; } diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 9fd268c2..98cb5376 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -24,6 +24,12 @@ namespace skyline::nce { } else { throw exception("Unimplemented SVC 0x{:X}", svc); } + + if (kernel::Scheduler::YieldPending) { + state.scheduler->Rotate(false); + state.scheduler->WaitSchedule(); + kernel::Scheduler::YieldPending = false; + } } catch (const signal::SignalException &e) { if (e.signal != SIGINT) { state.logger->Error("{} (SVC: 0x{:X})", e.what(), svc); @@ -46,7 +52,7 @@ namespace skyline::nce { } void NCE::SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls) { - if (*tls) { + if (*tls) { // If TLS was restored then this occurred in guest code auto &mctx{ctx->uc_mcontext}; const auto &state{*reinterpret_cast(*tls)->state}; if (signal != SIGINT) { @@ -95,7 +101,7 @@ namespace skyline::nce { mctx.regs[1] = true; *tls = nullptr; - } else { + } else { // If TLS wasn't restored then this occurred in host code signal::ExceptionalSignalHandler(signal, info, ctx); //!< Delegate throwing a host exception to the exceptional signal handler } } diff --git a/app/src/main/cpp/skyline/vfs/npdm.cpp b/app/src/main/cpp/skyline/vfs/npdm.cpp index 46c22e2c..77c7ed27 100644 --- a/app/src/main/cpp/skyline/vfs/npdm.cpp +++ b/app/src/main/cpp/skyline/vfs/npdm.cpp @@ -31,8 +31,8 @@ namespace skyline::vfs { .magic = MetaMagic, }; threadInfo = { - .coreMask = 0b1110, - .priority = {0, 63}, + .coreMask = 0b0111, + .priority = {0, 59}, }; }