Implement Preemptive Scheduling

This commit is contained in:
◱ PixelyIon 2020-12-08 16:13:54 +05:30 committed by ◱ Mark
parent cf000f5750
commit f41bcd1e22
7 changed files with 96 additions and 17 deletions

View File

@ -1,18 +1,30 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <unistd.h>
#include <common/signal.h> #include <common/signal.h>
#include "types/KThread.h" #include "types/KThread.h"
#include "scheduler.h" #include "scheduler.h"
namespace skyline::kernel { 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) {} 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<nce::ThreadContext *>(*tls)->state};
state.scheduler->Rotate(false);
state.scheduler->WaitSchedule();
YieldPending = false;
} else {
YieldPending = true;
}
}
Scheduler::CoreContext &Scheduler::LoadBalance() { Scheduler::CoreContext &Scheduler::LoadBalance() {
auto &thread{state.thread}; auto &thread{state.thread};
auto currentCore{&cores.at(thread->coreId)}; auto *currentCore{&cores.at(thread->coreId)};
if (!currentCore->queue.empty() && thread->affinityMask.count() != 1) { 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 // 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 &thread{state.thread};
auto &core{loadBalance ? LoadBalance() : cores.at(thread->coreId)}; 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); std::unique_lock lock(core.mutex);
auto nextThread{std::find_if(core.queue.begin(), core.queue.end(), [&](const std::shared_ptr<type::KThread> &it) { return it->priority > thread->priority; })}; auto nextThread{std::find_if(core.queue.begin(), core.queue.end(), [&](const std::shared_ptr<type::KThread> &it) { return it->priority > thread->priority; })};
if (nextThread == core.queue.begin() && nextThread != core.queue.end()) { 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 { } else {
core.queue.insert(nextThread, thread); 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() { void Scheduler::WaitSchedule() {
@ -88,7 +111,7 @@ namespace skyline::kernel {
std::shared_lock lock(core->mutex); std::shared_lock lock(core->mutex);
if (thread->affinityMask.count() > 1) { 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; })) { while (!core->mutateCondition.wait_for(lock, loadBalanceThreshold, [&]() { return core->queue.front() == thread; })) {
lock.unlock(); lock.unlock();
LoadBalance(); LoadBalance();
@ -106,10 +129,16 @@ namespace skyline::kernel {
core->mutateCondition.wait(lock, [&]() { return core->queue.front() == thread; }); 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<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true;
}
thread->timesliceStart = util::GetTimeTicks(); thread->timesliceStart = util::GetTimeTicks();
} }
void Scheduler::Rotate() { void Scheduler::Rotate(bool cooperative) {
auto &thread{state.thread}; auto &thread{state.thread};
auto &core{cores.at(thread->coreId)}; auto &core{cores.at(thread->coreId)};
@ -122,6 +151,14 @@ namespace skyline::kernel {
core.mutateCondition.notify_all(); 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() { void Scheduler::RemoveThread() {
@ -130,5 +167,13 @@ namespace skyline::kernel {
std::unique_lock lock(core.mutex); std::unique_lock lock(core.mutex);
core.queue.erase(std::remove(core.queue.begin(), core.queue.end(), thread), core.queue.end()); 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;
} }
} }

View File

@ -8,7 +8,7 @@
namespace skyline { namespace skyline {
namespace constant { 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 { namespace kernel {
@ -44,22 +44,32 @@ namespace skyline {
struct CoreContext { struct CoreContext {
u8 id; 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::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::condition_variable_any mutateCondition; //!< A conditional variable which is signalled on every mutation of the queue
std::deque<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core std::deque<std::shared_ptr<type::KThread>> 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<CoreContext, constant::CoreCount> cores{CoreContext(0), CoreContext(1), CoreContext(2), CoreContext(3)}; std::array<CoreContext, constant::CoreCount> cores{CoreContext(0, 59), CoreContext(1, 59), CoreContext(2, 59), CoreContext(3, 63)};
public: 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); 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 * @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 * @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(); 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 * @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 * @brief Removes the calling thread from it's resident core queue

View File

@ -299,7 +299,7 @@ namespace skyline::kernel::svc {
state.scheduler->WaitSchedule(); state.scheduler->WaitSchedule();
} else if (in == yieldWithoutCoreMigration || in == yieldWithCoreMigration || in == yieldToAnyThread) { } else if (in == yieldWithoutCoreMigration || in == yieldWithCoreMigration || in == yieldToAnyThread) {
// Core Migration doesn't affect us as threads schedule and load balance themselves // 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->Rotate();
state.scheduler->WaitSchedule(); state.scheduler->WaitSchedule();
} }

View File

@ -7,6 +7,7 @@
#include <nce.h> #include <nce.h>
#include <os.h> #include <os.h>
#include "KProcess.h" #include "KProcess.h"
#include "KThread.h"
namespace skyline::kernel::type { 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) { 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()) if (thread.joinable())
thread.join(); thread.join();
if (preemptionTimer)
timer_delete(*preemptionTimer);
} }
void KThread::StartThread() { 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) { void KThread::UpdatePriority(i8 priority) {
this->priority = priority; this->priority = priority;
} }

View File

@ -44,6 +44,8 @@ namespace skyline {
CoreMask affinityMask{}; //!< A mask of CPU cores this thread is allowed to run on CoreMask affinityMask{}; //!< A mask of CPU cores this thread is allowed to run on
u64 timesliceStart{}; //!< Start of the scheduler timeslice u64 timesliceStart{}; //!< Start of the scheduler timeslice
u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread
std::optional<timer_t> 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); 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); 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); void UpdatePriority(i8 priority);
}; };
} }

View File

@ -24,6 +24,12 @@ namespace skyline::nce {
} else { } else {
throw exception("Unimplemented SVC 0x{:X}", svc); 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) { } catch (const signal::SignalException &e) {
if (e.signal != SIGINT) { if (e.signal != SIGINT) {
state.logger->Error("{} (SVC: 0x{:X})", e.what(), svc); 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) { 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}; auto &mctx{ctx->uc_mcontext};
const auto &state{*reinterpret_cast<ThreadContext *>(*tls)->state}; const auto &state{*reinterpret_cast<ThreadContext *>(*tls)->state};
if (signal != SIGINT) { if (signal != SIGINT) {
@ -95,7 +101,7 @@ namespace skyline::nce {
mctx.regs[1] = true; mctx.regs[1] = true;
*tls = nullptr; *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 signal::ExceptionalSignalHandler(signal, info, ctx); //!< Delegate throwing a host exception to the exceptional signal handler
} }
} }

View File

@ -31,8 +31,8 @@ namespace skyline::vfs {
.magic = MetaMagic, .magic = MetaMagic,
}; };
threadInfo = { threadInfo = {
.coreMask = 0b1110, .coreMask = 0b0111,
.priority = {0, 63}, .priority = {0, 59},
}; };
} }