mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-20 20:57:54 +03:00
Fix Priority Queue + Cooperative Yielding + Conditional Variable Timeouts
This commit is contained in:
parent
33bbfb9fb7
commit
14dbb5305a
@ -9,7 +9,9 @@ extern skyline::u16 Fps;
|
|||||||
extern skyline::u32 FrameTime;
|
extern skyline::u32 FrameTime;
|
||||||
|
|
||||||
namespace skyline::gpu {
|
namespace skyline::gpu {
|
||||||
PresentationEngine::PresentationEngine(const DeviceState &state) : state(state), vsyncEvent(std::make_shared<kernel::type::KEvent>(state)), bufferEvent(std::make_shared<kernel::type::KEvent>(state)) {}
|
PresentationEngine::PresentationEngine(const DeviceState &state) : state(state), vsyncEvent(std::make_shared<kernel::type::KEvent>(state)), bufferEvent(std::make_shared<kernel::type::KEvent>(state)) {
|
||||||
|
vsyncEvent->Signal();
|
||||||
|
}
|
||||||
|
|
||||||
PresentationEngine::~PresentationEngine() {
|
PresentationEngine::~PresentationEngine() {
|
||||||
if (window)
|
if (window)
|
||||||
|
@ -10,7 +10,7 @@ namespace skyline::input {
|
|||||||
{*this, hid->npad[2], NpadId::Player3}, {*this, hid->npad[3], NpadId::Player4},
|
{*this, hid->npad[2], NpadId::Player3}, {*this, hid->npad[3], NpadId::Player4},
|
||||||
{*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6},
|
{*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6},
|
||||||
{*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8},
|
{*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8},
|
||||||
{*this, hid->npad[8], NpadId::Unknown}, {*this, hid->npad[9], NpadId::Handheld},
|
{*this, hid->npad[8], NpadId::Handheld}, {*this, hid->npad[9], NpadId::Unknown},
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
void NpadManager::Update() {
|
void NpadManager::Update() {
|
||||||
|
@ -187,7 +187,7 @@ namespace skyline::input {
|
|||||||
|
|
||||||
info.header.timestamp = util::GetTimeTicks();
|
info.header.timestamp = util::GetTimeTicks();
|
||||||
info.header.entryCount = std::min(static_cast<u8>(info.header.entryCount + 1), constant::HidEntryCount);
|
info.header.entryCount = std::min(static_cast<u8>(info.header.entryCount + 1), constant::HidEntryCount);
|
||||||
info.header.maxEntry = constant::HidEntryCount - 1;
|
info.header.maxEntry = info.header.entryCount;
|
||||||
info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0;
|
info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0;
|
||||||
|
|
||||||
auto &entry{info.state.at(info.header.currentEntry)};
|
auto &entry{info.state.at(info.header.currentEntry)};
|
||||||
|
@ -22,7 +22,7 @@ namespace skyline::kernel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler::CoreContext &Scheduler::LoadBalance(const std::shared_ptr<type::KThread> &thread) {
|
Scheduler::CoreContext &Scheduler::LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert) {
|
||||||
std::lock_guard migrationLock(thread->coreMigrationMutex);
|
std::lock_guard migrationLock(thread->coreMigrationMutex);
|
||||||
auto *currentCore{&cores.at(thread->coreId)};
|
auto *currentCore{&cores.at(thread->coreId)};
|
||||||
|
|
||||||
@ -59,20 +59,21 @@ namespace skyline::kernel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (optimalCore != currentCore) {
|
if (optimalCore != currentCore) {
|
||||||
std::unique_lock coreLock(currentCore->mutex);
|
RemoveThread();
|
||||||
currentCore->queue.erase(std::remove(currentCore->queue.begin(), currentCore->queue.end(), thread), currentCore->queue.end());
|
|
||||||
currentCore->frontCondition.notify_all();
|
|
||||||
|
|
||||||
thread->coreId = optimalCore->id;
|
thread->coreId = optimalCore->id;
|
||||||
|
InsertThread(thread);
|
||||||
state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id);
|
state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id);
|
||||||
} else {
|
} else {
|
||||||
|
if (alwaysInsert)
|
||||||
|
InsertThread(thread);
|
||||||
state.logger->Debug("Load Balancing T{}: C{} (Late)", thread->id, currentCore->id);
|
state.logger->Debug("Load Balancing T{}: C{} (Late)", thread->id, currentCore->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *optimalCore;
|
return *optimalCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (alwaysInsert)
|
||||||
|
InsertThread(thread);
|
||||||
state.logger->Debug("Load Balancing T{}: C{} (Early)", thread->id, currentCore->id);
|
state.logger->Debug("Load Balancing T{}: C{} (Early)", thread->id, currentCore->id);
|
||||||
|
|
||||||
return *currentCore;
|
return *currentCore;
|
||||||
@ -91,7 +92,9 @@ namespace skyline::kernel {
|
|||||||
struct sigevent event{
|
struct sigevent event{
|
||||||
.sigev_signo = YieldSignal,
|
.sigev_signo = YieldSignal,
|
||||||
.sigev_notify = SIGEV_THREAD_ID,
|
.sigev_notify = SIGEV_THREAD_ID,
|
||||||
.sigev_notify_thread_id = gettid(),
|
._sigev_un = {
|
||||||
|
._tid = gettid(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
timer_t timer;
|
timer_t timer;
|
||||||
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &timer))
|
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &timer))
|
||||||
@ -120,8 +123,6 @@ namespace skyline::kernel {
|
|||||||
} else {
|
} else {
|
||||||
core.queue.insert(nextThread, thread);
|
core.queue.insert(nextThread, thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
thread->needsReorder = true; // We need to reorder the thread from back to align it with other threads of it's priority and ensure strict ordering amongst priorities
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::WaitSchedule(bool loadBalance) {
|
void Scheduler::WaitSchedule(bool loadBalance) {
|
||||||
@ -137,7 +138,6 @@ namespace skyline::kernel {
|
|||||||
if (thread->coreId == core->id) {
|
if (thread->coreId == core->id) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
} else {
|
} else {
|
||||||
InsertThread(state.thread);
|
|
||||||
core = &cores.at(thread->coreId);
|
core = &cores.at(thread->coreId);
|
||||||
lock = std::shared_lock(core->mutex);
|
lock = std::shared_lock(core->mutex);
|
||||||
}
|
}
|
||||||
@ -185,26 +185,14 @@ namespace skyline::kernel {
|
|||||||
if (core.queue.front() == thread) {
|
if (core.queue.front() == thread) {
|
||||||
thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4)); // 0.25 * old timeslice duration + 0.75 * current timeslice duration
|
thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4)); // 0.25 * old timeslice duration + 0.75 * current timeslice duration
|
||||||
|
|
||||||
core.queue.pop_front();
|
// Splice the linked element from the beginning of the queue to where it's priority is present
|
||||||
if (!thread->needsReorder) {
|
core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin());
|
||||||
core.queue.push_back(thread);
|
|
||||||
} else if (core.queue.size() > 1 && core.queue.back()->priority > thread->priority) {
|
|
||||||
// If 'needsReorder' is set, the core queue isn't empty nor has only one member and the thread at the back of the queue has a lower priority than the current one
|
|
||||||
// We can attempt to reorder this thread, this is done by doing a priority-aware insert with the search starting at the "folding point"
|
|
||||||
// The folding point is where a thread has a lower priority than the one succeeding it in the queue, this is where a new "sequence" starts from highest to lowest priorities
|
|
||||||
u8 lastPriority{core.queue.front()->priority};
|
|
||||||
auto foldingPoint{std::find_if(std::next(core.queue.begin()), core.queue.end(), [&](const std::shared_ptr<type::KThread> &it) {
|
|
||||||
return lastPriority > it->priority ? true : lastPriority = it->priority, false;
|
|
||||||
})};
|
|
||||||
core.queue.insert(std::upper_bound(foldingPoint, core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread);
|
|
||||||
thread->needsReorder = false;
|
|
||||||
} else {
|
|
||||||
core.queue.push_back(thread);
|
|
||||||
thread->needsReorder = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.unlock();
|
if (core.queue.front() != thread) {
|
||||||
core.frontCondition.notify_all();
|
// If we aren't at the front of the queue, only then should we notify other threads that the front of the queue has changed
|
||||||
|
lock.unlock();
|
||||||
|
core.frontCondition.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
if (cooperative && thread->isPreempted) {
|
if (cooperative && thread->isPreempted) {
|
||||||
// If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer
|
// If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer
|
||||||
@ -232,7 +220,6 @@ namespace skyline::kernel {
|
|||||||
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr);
|
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr);
|
||||||
thread->isPreempted = true;
|
thread->isPreempted = true;
|
||||||
}
|
}
|
||||||
thread->needsReorder = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,18 +243,59 @@ namespace skyline::kernel {
|
|||||||
} else {
|
} else {
|
||||||
core->queue.insert(targetIt, thread);
|
core->queue.insert(targetIt, thread);
|
||||||
}
|
}
|
||||||
thread->needsReorder = true;
|
}
|
||||||
|
|
||||||
|
void Scheduler::ParkThread() {
|
||||||
|
auto &thread{state.thread};
|
||||||
|
RemoveThread();
|
||||||
|
{
|
||||||
|
std::unique_lock lock(parkedMutex);
|
||||||
|
parkedQueue.insert(std::upper_bound(parkedQueue.begin(), parkedQueue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread);
|
||||||
|
thread->coreId = constant::ParkedCoreId;
|
||||||
|
parkedFrontCondition.wait(lock, [&]() { return parkedCore.queue.front() == thread && thread->coreId != constant::ParkedCoreId; });
|
||||||
|
}
|
||||||
|
InsertThread(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::WakeParkedThread() {
|
||||||
|
std::unique_lock parkedLock(parkedMutex);
|
||||||
|
if (!parkedQueue.empty()) {
|
||||||
|
auto &thread{state.thread};
|
||||||
|
auto &core{cores.at(thread->coreId)};
|
||||||
|
std::unique_lock coreLock(core.mutex);
|
||||||
|
auto nextThread{core.queue.size() > 1 ? *std::next(core.queue.begin()) : nullptr};
|
||||||
|
nextThread = nextThread->priority == thread->priority ? nextThread : nullptr; // If the next thread doesn't have the same priority then it won't be scheduled next
|
||||||
|
auto parkedThread{parkedQueue.front()};
|
||||||
|
|
||||||
|
// We need to be conservative about waking up a parked thread, it should only be done if it's priority is higher than the current thread
|
||||||
|
// Alternatively, it should be done if it's priority is equivalent to the current thread's priority but the next thread had been scheduled prior or if there is no next thread (Current thread would be rescheduled)
|
||||||
|
if (parkedThread->priority < thread->priority || (parkedThread->priority == thread->priority && (!nextThread || parkedThread->timesliceStart < nextThread->timesliceStart))) {
|
||||||
|
parkedThread->coreId = thread->coreId;
|
||||||
|
parkedLock.unlock();
|
||||||
|
parkedFrontCondition.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::RemoveThread() {
|
void Scheduler::RemoveThread() {
|
||||||
auto &thread{state.thread};
|
auto &thread{state.thread};
|
||||||
auto &core{cores.at(thread->coreId)};
|
auto &core{cores.at(thread->coreId)};
|
||||||
|
|
||||||
{
|
{
|
||||||
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());
|
auto it{std::find(core.queue.begin(), core.queue.end(), thread)};
|
||||||
|
if (it != core.queue.end()) {
|
||||||
|
it = core.queue.erase(it);
|
||||||
|
if (it == core.queue.begin()) {
|
||||||
|
// We need to update the averageTimeslice accordingly, if we've been unscheduled by this
|
||||||
|
if (thread->timesliceStart)
|
||||||
|
thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4));
|
||||||
|
|
||||||
|
// We need to notify that the front was changed, if we were at the front previously
|
||||||
|
lock.unlock();
|
||||||
|
core.frontCondition.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
core.frontCondition.notify_all(); // We need to notify any threads in line to be scheduled
|
|
||||||
|
|
||||||
if (thread->isPreempted) {
|
if (thread->isPreempted) {
|
||||||
struct itimerspec spec{};
|
struct itimerspec spec{};
|
||||||
|
@ -9,6 +9,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)
|
||||||
|
constexpr u8 ParkedCoreId{CoreCount}; //!< // An invalid core ID, representing that a thread has been parked
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
@ -47,13 +48,19 @@ namespace skyline {
|
|||||||
u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative
|
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 frontCondition; //!< A conditional variable which is signalled when the front of the queue has changed
|
std::condition_variable_any frontCondition; //!< A conditional variable which is signalled when the front of the queue has changed
|
||||||
std::deque<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core
|
std::list<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core
|
||||||
|
|
||||||
CoreContext(u8 id, u8 preemptionPriority);
|
CoreContext(u8 id, u8 preemptionPriority);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<CoreContext, constant::CoreCount> cores{CoreContext(0, 59), CoreContext(1, 59), CoreContext(2, 59), CoreContext(3, 63)};
|
std::array<CoreContext, constant::CoreCount> cores{CoreContext(0, 59), CoreContext(1, 59), CoreContext(2, 59), CoreContext(3, 63)};
|
||||||
|
|
||||||
|
std::mutex parkedMutex; //!< Synchronizes all operations on the queue of parked threads
|
||||||
|
std::condition_variable parkedFrontCondition; //!< A conditional variable which is signalled when the front of the parked queue has changed
|
||||||
|
std::list<std::shared_ptr<type::KThread>> parkedQueue; //!< A queue of threads which are parked and waiting on core migration
|
||||||
|
|
||||||
|
CoreContext parkedCore{constant::CoreCount, 64}; //!< A psuedo-core which all parked threads are moved onto
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding
|
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 int YieldSignal{SIGRTMIN}; //!< The signal used to cause a yield in running threads
|
||||||
@ -68,10 +75,11 @@ namespace skyline {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks all cores and migrates the specified thread to the core where the calling thread should be scheduled the earliest
|
* @brief Checks all cores and migrates the specified thread to the core where the calling thread should be scheduled the earliest
|
||||||
|
* @param alwaysInsert If to insert the thread even if it hasn't migrated cores, this is used during thread creation
|
||||||
* @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 inserts the thread into the migrated process's queue after load balancing, there is no need to call it redundantly
|
||||||
*/
|
*/
|
||||||
CoreContext& LoadBalance(const std::shared_ptr<type::KThread> &thread);
|
CoreContext& LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Inserts the specified thread into the scheduler queue at the appropriate location based on it's priority
|
* @brief Inserts the specified thread into the scheduler queue at the appropriate location based on it's priority
|
||||||
@ -103,10 +111,42 @@ namespace skyline {
|
|||||||
*/
|
*/
|
||||||
void UpdatePriority(const std::shared_ptr<type::KThread>& thread);
|
void UpdatePriority(const std::shared_ptr<type::KThread>& thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parks the calling thread after removing it from it's resident core's queue and inserts it on the core it's been awoken on
|
||||||
|
* @note This will not handle waiting for the thread to be scheduled, this should be followed with a call to WaitSchedule/TimedWaitSchedule
|
||||||
|
*/
|
||||||
|
void ParkThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wakes a single parked thread which may be appropriate for running next on this core
|
||||||
|
* @note We will only wake a thread if it is determined to be a better pick than the thread which would be run on this core next
|
||||||
|
*/
|
||||||
|
void WakeParkedThread();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Removes the calling thread from it's resident core queue
|
* @brief Removes the calling thread from it's resident core queue
|
||||||
*/
|
*/
|
||||||
void RemoveThread();
|
void RemoveThread();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A lock which removes the calling thread from it's resident core's scheduler queue and adds it back when being destroyed
|
||||||
|
* @note It also blocks till the thread has been rescheduled in it's destructor, this behavior might not be preferable in some cases
|
||||||
|
* @note This is not an analogue to KScopedSchedulerLock on HOS, it is for handling thread state changes which we handle with Scheduler::YieldPending
|
||||||
|
*/
|
||||||
|
struct SchedulerScopedLock {
|
||||||
|
private:
|
||||||
|
const DeviceState& state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline SchedulerScopedLock(const DeviceState& state) : state(state) {
|
||||||
|
state.scheduler->RemoveThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ~SchedulerScopedLock() {
|
||||||
|
state.scheduler->InsertThread(state.thread);
|
||||||
|
state.scheduler->WaitSchedule();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ namespace skyline::kernel::svc {
|
|||||||
newChunk.attributes.isUncached = value.isUncached;
|
newChunk.attributes.isUncached = value.isUncached;
|
||||||
state.process->memory.InsertChunk(newChunk);
|
state.process->memory.InsertChunk(newChunk);
|
||||||
|
|
||||||
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} - 0x{:X} (0x{:X} bytes)", static_cast<bool>(value.isUncached), pointer, pointer + size, size);
|
state.logger->Debug("svcSetMemoryAttribute: Set CPU caching to {} at 0x{:X} - 0x{:X} (0x{:X} bytes)", !static_cast<bool>(value.isUncached), pointer, pointer + size, size);
|
||||||
state.ctx->gpr.w0 = Result{};
|
state.ctx->gpr.w0 = Result{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +266,7 @@ namespace skyline::kernel::svc {
|
|||||||
KHandle handle{state.ctx->gpr.w0};
|
KHandle handle{state.ctx->gpr.w0};
|
||||||
try {
|
try {
|
||||||
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
||||||
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->id);
|
state.logger->Debug("svcStartThread: Starting thread #{}: 0x{:X}", thread->id, handle);
|
||||||
thread->Start();
|
thread->Start();
|
||||||
state.ctx->gpr.w0 = Result{};
|
state.ctx->gpr.w0 = Result{};
|
||||||
} catch (const std::out_of_range &) {
|
} catch (const std::out_of_range &) {
|
||||||
@ -294,14 +294,28 @@ namespace skyline::kernel::svc {
|
|||||||
.tv_nsec = static_cast<long>(in % 1000000000),
|
.tv_nsec = static_cast<long>(in % 1000000000),
|
||||||
};
|
};
|
||||||
|
|
||||||
state.scheduler->Rotate();
|
SchedulerScopedLock schedulerLock(state);
|
||||||
nanosleep(&spec, nullptr);
|
nanosleep(&spec, nullptr);
|
||||||
state.scheduler->WaitSchedule();
|
} else {
|
||||||
} else if (in == yieldWithoutCoreMigration || in == yieldWithCoreMigration || in == yieldToAnyThread) {
|
switch (in) {
|
||||||
// Core Migration doesn't affect us as threads schedule and load balance themselves
|
case yieldWithCoreMigration:
|
||||||
state.logger->Debug("svcSleepThread: Cooperative Yield");
|
state.logger->Debug("svcSleepThread: Waking any appropriate parked threads");
|
||||||
state.scheduler->Rotate();
|
state.scheduler->WakeParkedThread();
|
||||||
state.scheduler->WaitSchedule();
|
case yieldWithoutCoreMigration:
|
||||||
|
state.logger->Debug("svcSleepThread: Cooperative Yield");
|
||||||
|
state.scheduler->Rotate();
|
||||||
|
state.scheduler->WaitSchedule();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case yieldToAnyThread:
|
||||||
|
state.logger->Debug("svcSleepThread: Parking current thread");
|
||||||
|
state.scheduler->ParkThread();
|
||||||
|
state.scheduler->WaitSchedule(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +344,7 @@ namespace skyline::kernel::svc {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
||||||
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
|
state.logger->Debug("svcSetThreadPriority: Setting thread #{}'s priority to {}", thread->id, priority);
|
||||||
if (thread->priority != priority) {
|
if (thread->priority != priority) {
|
||||||
thread->priority = priority;
|
thread->priority = priority;
|
||||||
state.scheduler->UpdatePriority(thread);
|
state.scheduler->UpdatePriority(thread);
|
||||||
@ -348,7 +362,7 @@ namespace skyline::kernel::svc {
|
|||||||
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
auto thread{state.process->GetHandle<type::KThread>(handle)};
|
||||||
auto idealCore{thread->idealCore};
|
auto idealCore{thread->idealCore};
|
||||||
auto affinityMask{thread->affinityMask};
|
auto affinityMask{thread->affinityMask};
|
||||||
state.logger->Debug("svcGetThreadCoreMask: Setting Ideal Core ({}) + Affinity Mask ({})", idealCore, affinityMask);
|
state.logger->Debug("svcGetThreadCoreMask: Getting thread #{}'s Ideal Core ({}) + Affinity Mask ({})", thread->id, idealCore, affinityMask);
|
||||||
|
|
||||||
state.ctx->gpr.x2 = affinityMask.to_ullong();
|
state.ctx->gpr.x2 = affinityMask.to_ullong();
|
||||||
state.ctx->gpr.w1 = idealCore;
|
state.ctx->gpr.w1 = idealCore;
|
||||||
@ -388,18 +402,23 @@ namespace skyline::kernel::svc {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.logger->Debug("svcSetThreadCoreMask: Setting Ideal Core ({}) + Affinity Mask ({})", idealCore, affinityMask);
|
state.logger->Debug("svcSetThreadCoreMask: Setting thread #{}'s Ideal Core ({}) + Affinity Mask ({})", thread->id, idealCore, affinityMask);
|
||||||
|
|
||||||
thread->idealCore = idealCore;
|
thread->idealCore = idealCore;
|
||||||
thread->affinityMask = affinityMask;
|
thread->affinityMask = affinityMask;
|
||||||
|
|
||||||
if (!affinityMask.test(thread->coreId)) {
|
if (!affinityMask.test(thread->coreId)) {
|
||||||
state.logger->Debug("svcSetThreadCoreMask: Migrating to Ideal Core C{} -> C{}", thread->coreId, idealCore);
|
state.logger->Debug("svcSetThreadCoreMask: Migrating thread #{} to Ideal Core C{} -> C{}", thread->id, thread->coreId, idealCore);
|
||||||
|
|
||||||
state.scheduler->RemoveThread();
|
if (thread == state.thread) {
|
||||||
thread->coreId = idealCore;
|
state.scheduler->RemoveThread();
|
||||||
state.scheduler->InsertThread(state.thread);
|
state.scheduler->InsertThread(state.thread);
|
||||||
state.scheduler->WaitSchedule();
|
state.scheduler->WaitSchedule();
|
||||||
|
} else if (!thread->running) {
|
||||||
|
thread->coreId = idealCore;
|
||||||
|
} else {
|
||||||
|
throw exception("svcSetThreadCoreMask: Migrating a running thread due to a new core mask is not supported");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.ctx->gpr.w0 = Result{};
|
state.ctx->gpr.w0 = Result{};
|
||||||
@ -411,7 +430,7 @@ namespace skyline::kernel::svc {
|
|||||||
|
|
||||||
void GetCurrentProcessorNumber(const DeviceState &state) {
|
void GetCurrentProcessorNumber(const DeviceState &state) {
|
||||||
auto coreId{state.thread->coreId};
|
auto coreId{state.thread->coreId};
|
||||||
state.logger->Debug("svcGetCurrentProcessorNumber: Writing current core: {}", coreId);
|
state.logger->Debug("svcGetCurrentProcessorNumber: C{}", coreId);
|
||||||
state.ctx->gpr.w0 = coreId;
|
state.ctx->gpr.w0 = coreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,10 +539,6 @@ namespace skyline::kernel::svc {
|
|||||||
|
|
||||||
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
|
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
|
||||||
state.ctx->gpr.w0 = Result{};
|
state.ctx->gpr.w0 = Result{};
|
||||||
|
|
||||||
// There is an implicit yield while resetting a signal
|
|
||||||
state.scheduler->Rotate();
|
|
||||||
state.scheduler->WaitSchedule();
|
|
||||||
} catch (const std::out_of_range &) {
|
} catch (const std::out_of_range &) {
|
||||||
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
|
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
|
||||||
state.ctx->gpr.w0 = result::InvalidHandle;
|
state.ctx->gpr.w0 = result::InvalidHandle;
|
||||||
@ -567,36 +582,32 @@ namespace skyline::kernel::svc {
|
|||||||
u64 timeout{state.ctx->gpr.x3};
|
u64 timeout{state.ctx->gpr.x3};
|
||||||
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
|
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
|
||||||
|
|
||||||
// The thread shouldn't be occupying the core while it's waiting on objects
|
SchedulerScopedLock schedulerLock(state);
|
||||||
state.scheduler->Rotate();
|
|
||||||
auto start{util::GetTimeNs()};
|
auto start{util::GetTimeNs()};
|
||||||
[&] () {
|
while (true) {
|
||||||
while (true) {
|
if (state.thread->cancelSync) {
|
||||||
if (state.thread->cancelSync) {
|
state.thread->cancelSync = false;
|
||||||
state.thread->cancelSync = false;
|
state.ctx->gpr.w0 = result::Cancelled;
|
||||||
state.ctx->gpr.w0 = result::Cancelled;
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 index{};
|
|
||||||
for (const auto &object : objectTable) {
|
|
||||||
if (object->signalled) {
|
|
||||||
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]);
|
|
||||||
state.ctx->gpr.w0 = Result{};
|
|
||||||
state.ctx->gpr.w1 = index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((util::GetTimeNs() - start) >= timeout) {
|
|
||||||
state.logger->Debug("svcWaitSynchronization: Wait has timed out");
|
|
||||||
state.ctx->gpr.w0 = result::TimedOut;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}();
|
|
||||||
state.scheduler->WaitSchedule();
|
u32 index{};
|
||||||
|
for (const auto &object : objectTable) {
|
||||||
|
if (object->signalled) {
|
||||||
|
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]);
|
||||||
|
state.ctx->gpr.w0 = Result{};
|
||||||
|
state.ctx->gpr.w1 = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((util::GetTimeNs() - start) >= timeout) {
|
||||||
|
state.logger->Debug("svcWaitSynchronization: Wait has timed out");
|
||||||
|
state.ctx->gpr.w0 = result::TimedOut;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelSynchronization(const DeviceState &state) {
|
void CancelSynchronization(const DeviceState &state) {
|
||||||
@ -658,13 +669,13 @@ namespace skyline::kernel::svc {
|
|||||||
KHandle requesterHandle{state.ctx->gpr.w2};
|
KHandle requesterHandle{state.ctx->gpr.w2};
|
||||||
|
|
||||||
i64 timeout{static_cast<i64>(state.ctx->gpr.x3)};
|
i64 timeout{static_cast<i64>(state.ctx->gpr.x3)};
|
||||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mutex, conditional, timeout);
|
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {}ns", mutex, conditional, timeout);
|
||||||
|
|
||||||
auto result{state.process->ConditionalVariableWait(conditional, mutex, requesterHandle, timeout)};
|
auto result{state.process->ConditionalVariableWait(conditional, mutex, requesterHandle, timeout)};
|
||||||
if (result == Result{})
|
if (result == Result{})
|
||||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and reacquired mutex");
|
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable (0x{:X}) and reacquired mutex", conditional);
|
||||||
else if (result == result::TimedOut)
|
else if (result == result::TimedOut)
|
||||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
|
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out ({}ns) for 0x{:X}", timeout, conditional);
|
||||||
state.ctx->gpr.w0 = result;
|
state.ctx->gpr.w0 = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,11 +727,11 @@ namespace skyline::kernel::svc {
|
|||||||
|
|
||||||
void GetThreadId(const DeviceState &state) {
|
void GetThreadId(const DeviceState &state) {
|
||||||
KHandle handle{state.ctx->gpr.w1};
|
KHandle handle{state.ctx->gpr.w1};
|
||||||
size_t pid{state.process->GetHandle<type::KThread>(handle)->id};
|
size_t tid{state.process->GetHandle<type::KThread>(handle)->id};
|
||||||
|
|
||||||
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
|
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, TID: {}", handle, tid);
|
||||||
|
|
||||||
state.ctx->gpr.x1 = pid;
|
state.ctx->gpr.x1 = tid;
|
||||||
state.ctx->gpr.w0 = Result{};
|
state.ctx->gpr.w0 = Result{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,10 +15,10 @@ namespace skyline::kernel::type {
|
|||||||
return Get(index++);
|
return Get(index++);
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 *KProcess::TlsPage::Get(u8 index) {
|
u8 *KProcess::TlsPage::Get(u8 slot) {
|
||||||
if (index >= constant::TlsSlots)
|
if (slot >= constant::TlsSlots)
|
||||||
throw exception("TLS slot is out of range");
|
throw exception("TLS slot is out of range");
|
||||||
return memory->ptr + (constant::TlsSlotSize * index);
|
return memory->ptr + (constant::TlsSlotSize * slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KProcess::TlsPage::Full() {
|
bool KProcess::TlsPage::Full() {
|
||||||
@ -234,41 +234,34 @@ namespace skyline::kernel::type {
|
|||||||
} else {
|
} else {
|
||||||
__atomic_store_n(mutex, 0, __ATOMIC_SEQ_CST);
|
__atomic_store_n(mutex, 0, __ATOMIC_SEQ_CST);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.scheduler->Rotate();
|
|
||||||
state.scheduler->WaitSchedule();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result KProcess::ConditionalVariableWait(u32 *key, u32 *mutex, KHandle tag, i64 timeout) {
|
Result KProcess::ConditionalVariableWait(u32 *key, u32 *mutex, KHandle tag, i64 timeout) {
|
||||||
{
|
{
|
||||||
std::lock_guard lock(syncWaiterMutex);
|
std::lock_guard lock(syncWaiterMutex);
|
||||||
auto queue{syncWaiters.equal_range(key)};
|
auto queue{syncWaiters.equal_range(key)};
|
||||||
auto it{syncWaiters.insert(std::upper_bound(queue.first, queue.second, state.thread->priority.load(), [](const i8 priority, const SyncWaiters::value_type &it) { return it.second->priority > priority; }), {key, state.thread})};
|
syncWaiters.insert(std::upper_bound(queue.first, queue.second, state.thread->priority.load(), [](const i8 priority, const SyncWaiters::value_type &it) { return it.second->priority > priority; }), {key, state.thread});
|
||||||
|
|
||||||
// TODO: REMOVE THIS AFTER TESTING
|
|
||||||
auto prevIt{std::prev(it)}, nextIt{std::next(it)};
|
|
||||||
if ((prevIt != syncWaiters.begin() && prevIt->first == key && prevIt->second->priority > state.thread->priority.load()))
|
|
||||||
throw exception("Previous node incorrect");
|
|
||||||
if ((nextIt != syncWaiters.end() && nextIt->first == key && nextIt->second->priority < state.thread->priority.load()))
|
|
||||||
throw exception("Next node incorrect");
|
|
||||||
|
|
||||||
__atomic_store_n(key, true, __ATOMIC_SEQ_CST); // We need to notify any userspace threads that there are waiters on this conditional variable by writing back a boolean flag denoting it
|
__atomic_store_n(key, true, __ATOMIC_SEQ_CST); // We need to notify any userspace threads that there are waiters on this conditional variable by writing back a boolean flag denoting it
|
||||||
|
|
||||||
MutexUnlock(mutex);
|
|
||||||
state.scheduler->RemoveThread();
|
state.scheduler->RemoveThread();
|
||||||
|
MutexUnlock(mutex);
|
||||||
|
__sync_synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasTimedOut{};
|
if (timeout > 0 && !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout))) {
|
||||||
if (timeout > 0)
|
std::unique_lock lock(syncWaiterMutex);
|
||||||
hasTimedOut = !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout));
|
|
||||||
else
|
|
||||||
state.scheduler->WaitSchedule(false);
|
|
||||||
|
|
||||||
if (hasTimedOut) {
|
|
||||||
std::lock_guard lock(syncWaiterMutex);
|
|
||||||
auto queue{syncWaiters.equal_range(key)};
|
auto queue{syncWaiters.equal_range(key)};
|
||||||
syncWaiters.erase(std::find(queue.first, queue.second, SyncWaiters::value_type{key, state.thread}));
|
auto iterator{std::find(queue.first, queue.second, SyncWaiters::value_type{key, state.thread})};
|
||||||
|
if (iterator != queue.second)
|
||||||
|
if (syncWaiters.erase(iterator) == queue.second)
|
||||||
|
__atomic_store_n(key, false, __ATOMIC_SEQ_CST);
|
||||||
|
lock.unlock();
|
||||||
|
state.scheduler->InsertThread(state.thread);
|
||||||
|
state.scheduler->WaitSchedule();
|
||||||
return result::TimedOut;
|
return result::TimedOut;
|
||||||
|
} else {
|
||||||
|
state.scheduler->WaitSchedule(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -284,13 +277,13 @@ namespace skyline::kernel::type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::ConditionalVariableSignal(u32 *key, u64 amount) {
|
void KProcess::ConditionalVariableSignal(u32 *key, u64 amount) {
|
||||||
std::unique_lock lock(syncWaiterMutex);
|
std::lock_guard lock(syncWaiterMutex);
|
||||||
auto queue{syncWaiters.equal_range(key)};
|
auto queue{syncWaiters.equal_range(key)};
|
||||||
auto it{queue.first};
|
auto it{queue.first};
|
||||||
if (queue.first != queue.second)
|
if (queue.first != queue.second)
|
||||||
for (; it != queue.second && amount; it = syncWaiters.erase(it), amount--)
|
for (; it != queue.second && amount; it = syncWaiters.erase(it), amount--)
|
||||||
state.scheduler->InsertThread(it->second);
|
state.scheduler->InsertThread(it->second);
|
||||||
if (it == queue.second)
|
if (it == queue.second)
|
||||||
__atomic_store_n(key, 0, __ATOMIC_SEQ_CST); // We need to update the boolean flag denoting that there are no more threads waiting on this conditional variable
|
__atomic_store_n(key, false, __ATOMIC_SEQ_CST); // We need to update the boolean flag denoting that there are no more threads waiting on this conditional variable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace skyline {
|
|||||||
|
|
||||||
u8 *ReserveSlot();
|
u8 *ReserveSlot();
|
||||||
|
|
||||||
u8 *Get(u8 index);
|
u8 *Get(u8 slot);
|
||||||
|
|
||||||
bool Full();
|
bool Full();
|
||||||
};
|
};
|
||||||
|
@ -164,9 +164,7 @@ namespace skyline::kernel::type {
|
|||||||
std::unique_lock lock(mutex);
|
std::unique_lock lock(mutex);
|
||||||
if (!running) {
|
if (!running) {
|
||||||
running = true;
|
running = true;
|
||||||
auto sharedThis{shared_from_this()};
|
state.scheduler->LoadBalance(shared_from_this(), true); // This will automatically insert the thread into the core queue after load balancing
|
||||||
state.scheduler->LoadBalance(sharedThis);
|
|
||||||
state.scheduler->InsertThread(sharedThis);
|
|
||||||
if (self) {
|
if (self) {
|
||||||
pthread = pthread_self();
|
pthread = pthread_self();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
@ -49,7 +49,6 @@ namespace skyline {
|
|||||||
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
|
std::optional<timer_t> preemptionTimer{}; //!< A kernel timer used for preemption interrupts
|
||||||
bool isPreempted{}; //!< If the preemption timer has been armed and will fire
|
bool isPreempted{}; //!< If the preemption timer has been armed and will fire
|
||||||
bool needsReorder{}; //!< If the thread needs to reorder itself during scheduler rotation
|
|
||||||
std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members
|
std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members
|
||||||
u32* waitKey; //!< The key of the mutex which this thread is waiting on
|
u32* waitKey; //!< The key of the mutex which this thread is waiting on
|
||||||
KHandle waitTag; //!< The handle of the thread which requested the mutex lock
|
KHandle waitTag; //!< The handle of the thread which requested the mutex lock
|
||||||
|
@ -10,12 +10,14 @@ namespace skyline::service::audio {
|
|||||||
|
|
||||||
Result IAudioDevice::ListAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result IAudioDevice::ListAudioDeviceName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
span buffer{request.outputBuf.at(0)};
|
span buffer{request.outputBuf.at(0)};
|
||||||
for (std::string_view deviceName : {"AudioTvOutput\0", "AudioStereoJackOutput\0", "AudioBuiltInSpeakerOutput\0"}) {
|
std::array<std::string_view, 3> devices{"AudioTvOutput\0", "AudioStereoJackOutput\0", "AudioBuiltInSpeakerOutput\0"};
|
||||||
|
for (std::string_view deviceName : devices) {
|
||||||
if (deviceName.size() > buffer.size())
|
if (deviceName.size() > buffer.size())
|
||||||
throw exception("The buffer supplied to ListAudioDeviceName is too small");
|
throw exception("The buffer supplied to ListAudioDeviceName is too small");
|
||||||
buffer.copy_from(deviceName);
|
buffer.copy_from(deviceName);
|
||||||
buffer = buffer.subspan(deviceName.size());
|
buffer = buffer.subspan(deviceName.size());
|
||||||
}
|
}
|
||||||
|
response.Push<u32>(devices.size());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace skyline::service::nvdrv::device {
|
|||||||
std::pair<std::function<NvStatus(IoctlType, span<u8>, span<u8>)>, std::string_view> function;
|
std::pair<std::function<NvStatus(IoctlType, span<u8>, span<u8>)>, std::string_view> function;
|
||||||
try {
|
try {
|
||||||
function = GetIoctlFunction(cmd);
|
function = GetIoctlFunction(cmd);
|
||||||
state.logger->Debug("{} @ {}: {}", typeString, GetName(), function.second);
|
state.logger->Debug("{}: {} @ {}", typeString, GetName(), function.second);
|
||||||
} catch (std::out_of_range &) {
|
} catch (std::out_of_range &) {
|
||||||
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", GetName(), cmd);
|
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", GetName(), cmd);
|
||||||
return NvStatus::NotImplemented;
|
return NvStatus::NotImplemented;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user