mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-29 14:55:28 +03:00
Implement Preemptive Scheduling
This commit is contained in:
parent
cf000f5750
commit
f41bcd1e22
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ namespace skyline::vfs {
|
|||||||
.magic = MetaMagic,
|
.magic = MetaMagic,
|
||||||
};
|
};
|
||||||
threadInfo = {
|
threadInfo = {
|
||||||
.coreMask = 0b1110,
|
.coreMask = 0b0111,
|
||||||
.priority = {0, 63},
|
.priority = {0, 59},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user