mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-04 23:35:12 +01:00
Implement Preemptive Scheduling
This commit is contained in:
parent
cf000f5750
commit
f41bcd1e22
@ -1,18 +1,30 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <unistd.h>
|
||||
#include <common/signal.h>
|
||||
#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<nce::ThreadContext *>(*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<type::KThread> &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<std::chrono::nanoseconds>(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;
|
||||
}
|
||||
}
|
||||
|
@ -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<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:
|
||||
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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <nce.h>
|
||||
#include <os.h>
|
||||
#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;
|
||||
}
|
||||
|
@ -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<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);
|
||||
|
||||
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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<ThreadContext *>(*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
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ namespace skyline::vfs {
|
||||
.magic = MetaMagic,
|
||||
};
|
||||
threadInfo = {
|
||||
.coreMask = 0b1110,
|
||||
.priority = {0, 63},
|
||||
.coreMask = 0b0111,
|
||||
.priority = {0, 59},
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user