Fix Priority Queue + Cooperative Yielding + Conditional Variable Timeouts

This commit is contained in:
◱ PixelyIon 2020-12-22 19:08:37 +05:30 committed by ◱ Mark
parent 33bbfb9fb7
commit 14dbb5305a
12 changed files with 202 additions and 129 deletions

View File

@ -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)

View File

@ -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() {

View File

@ -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)};

View File

@ -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;
}
if (core.queue.front() != thread) {
// 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(); lock.unlock();
core.frontCondition.notify_all(); 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{};

View File

@ -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();
}
};
} }
} }

View File

@ -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: Waking any appropriate parked threads");
state.scheduler->WakeParkedThread();
case yieldWithoutCoreMigration:
state.logger->Debug("svcSleepThread: Cooperative Yield"); state.logger->Debug("svcSleepThread: Cooperative Yield");
state.scheduler->Rotate(); state.scheduler->Rotate();
state.scheduler->WaitSchedule(); 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);
if (thread == state.thread) {
state.scheduler->RemoveThread(); state.scheduler->RemoveThread();
thread->coreId = idealCore;
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,10 +582,8 @@ 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;
@ -595,8 +608,6 @@ namespace skyline::kernel::svc {
return; return;
} }
} }
}();
state.scheduler->WaitSchedule();
} }
void CancelSynchronization(const DeviceState &state) { void CancelSynchronization(const DeviceState &state) {
@ -662,9 +673,9 @@ namespace skyline::kernel::svc {
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{};
} }

View File

@ -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
} }
} }

View File

@ -48,7 +48,7 @@ namespace skyline {
u8 *ReserveSlot(); u8 *ReserveSlot();
u8 *Get(u8 index); u8 *Get(u8 slot);
bool Full(); bool Full();
}; };

View File

@ -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();

View File

@ -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

View File

@ -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 {};
} }

View File

@ -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;