Implement Conditional Variables

This commit is contained in:
◱ PixelyIon 2020-12-20 19:39:36 +05:30 committed by ◱ Mark
parent 7079f11add
commit 33bbfb9fb7
11 changed files with 192 additions and 214 deletions

View File

@ -22,8 +22,7 @@ namespace skyline::kernel {
} }
} }
Scheduler::CoreContext &Scheduler::LoadBalance() { Scheduler::CoreContext &Scheduler::LoadBalance(const std::shared_ptr<type::KThread> &thread) {
auto &thread{state.thread};
std::lock_guard migrationLock(thread->coreMigrationMutex); std::lock_guard migrationLock(thread->coreMigrationMutex);
auto *currentCore{&cores.at(thread->coreId)}; auto *currentCore{&cores.at(thread->coreId)};
@ -66,21 +65,21 @@ namespace skyline::kernel {
thread->coreId = optimalCore->id; thread->coreId = optimalCore->id;
state.logger->Debug("Load Balancing: C{} -> C{}", currentCore->id, optimalCore->id); state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id);
} else { } else {
state.logger->Debug("Load Balancing: C{} (Late)", currentCore->id); state.logger->Debug("Load Balancing T{}: C{} (Late)", thread->id, currentCore->id);
} }
return *optimalCore; return *optimalCore;
} }
state.logger->Debug("Load Balancing: C{} (Early)", currentCore->id); state.logger->Debug("Load Balancing T{}: C{} (Early)", thread->id, currentCore->id);
return *currentCore; return *currentCore;
} }
void Scheduler::InsertThread(const std::shared_ptr<type::KThread> &thread, bool loadBalance) { void Scheduler::InsertThread(const std::shared_ptr<type::KThread> &thread) {
auto &core{loadBalance ? LoadBalance() : cores.at(thread->coreId)}; auto &core{cores.at(thread->coreId)};
thread_local bool signalHandlerSetup{}; thread_local bool signalHandlerSetup{};
if (!signalHandlerSetup) { if (!signalHandlerSetup) {
@ -125,20 +124,20 @@ namespace skyline::kernel {
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 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() { void Scheduler::WaitSchedule(bool loadBalance) {
auto &thread{state.thread}; auto &thread{state.thread};
auto *core{&cores.at(thread->coreId)}; auto *core{&cores.at(thread->coreId)};
std::shared_lock lock(core->mutex); std::shared_lock lock(core->mutex);
if (thread->affinityMask.count() > 1) { if (loadBalance && thread->affinityMask.count() > 1) {
std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< 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->frontCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { while (!core->frontCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) {
lock.unlock(); lock.unlock();
LoadBalance(); LoadBalance(state.thread);
if (thread->coreId == core->id) { if (thread->coreId == core->id) {
lock.lock(); lock.lock();
} else { } else {
InsertThread(state.thread, false); 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);
} }
@ -158,6 +157,26 @@ namespace skyline::kernel {
thread->timesliceStart = util::GetTimeTicks(); thread->timesliceStart = util::GetTimeTicks();
} }
bool Scheduler::TimedWaitSchedule(std::chrono::nanoseconds timeout) {
auto &thread{state.thread};
auto *core{&cores.at(thread->coreId)};
std::shared_lock lock(core->mutex);
if (core->frontCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && 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();
return true;
} else {
return false;
}
}
void Scheduler::Rotate(bool cooperative) { 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)};

View File

@ -67,24 +67,30 @@ namespace skyline {
static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls); 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 specified 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(const std::shared_ptr<type::KThread> &thread);
/** /**
* @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
* @param loadBalance If to load balance or use the thread's current core (KThread::coreId)
* @note It isn't supported to load balance if the supplied thread isn't the calling thread, it'll lead to UB
*/ */
void InsertThread(const std::shared_ptr<type::KThread>& thread, bool loadBalance = true); void InsertThread(const std::shared_ptr<type::KThread>& thread);
/** /**
* @brief Wait for the current thread to be scheduled on it's resident core * @brief Wait for the current thread to be scheduled on it's resident core
* @param loadBalance If the thread is appropriate for load balancing then if to load balance it occassionally or not
* @note There is an assumption of the thread being on it's resident core queue, if it's not this'll never return * @note There is an assumption of the thread being on it's resident core queue, if it's not this'll never return
*/ */
void WaitSchedule(); void WaitSchedule(bool loadBalance = true);
/**
* @brief Wait for the current thread to be scheduled on it's resident core or for the timeout to expire
* @return If the thread has been scheduled (true) or if the timer expired before it could be (false)
* @note This will never load balance as it uses the timeout itself as a result this shouldn't be used as a replacement for regular waits
*/
bool TimedWaitSchedule(std::chrono::nanoseconds timeout);
/** /**
* @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

View File

@ -398,7 +398,7 @@ namespace skyline::kernel::svc {
state.scheduler->RemoveThread(); state.scheduler->RemoveThread();
thread->coreId = idealCore; thread->coreId = idealCore;
state.scheduler->InsertThread(state.thread, false); state.scheduler->InsertThread(state.thread);
state.scheduler->WaitSchedule(); state.scheduler->WaitSchedule();
} }
@ -520,6 +520,10 @@ 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;
@ -550,6 +554,7 @@ namespace skyline::kernel::svc {
case type::KType::KThread: case type::KType::KThread:
case type::KType::KEvent: case type::KType::KEvent:
case type::KType::KSession: case type::KType::KSession:
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object));
break; break;
default: { default: {
@ -557,38 +562,41 @@ namespace skyline::kernel::svc {
return; return;
} }
} }
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object));
} }
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
state.scheduler->Rotate();
auto start{util::GetTimeNs()}; auto start{util::GetTimeNs()};
while (true) { [&] () {
if (state.thread->cancelSync) { while (true) {
state.thread->cancelSync = false; if (state.thread->cancelSync) {
state.ctx->gpr.w0 = result::Cancelled; state.thread->cancelSync = false;
break; state.ctx->gpr.w0 = result::Cancelled;
}
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; return;
} }
index++;
}
if ((util::GetTimeNs() - start) >= timeout) { u32 index{};
state.logger->Debug("svcWaitSynchronization: Wait has timed out"); for (const auto &object : objectTable) {
state.ctx->gpr.w0 = result::TimedOut; if (object->signalled) {
return; 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();
} }
void CancelSynchronization(const DeviceState &state) { void CancelSynchronization(const DeviceState &state) {
@ -608,18 +616,15 @@ namespace skyline::kernel::svc {
return; return;
} }
KHandle ownerHandle{state.ctx->gpr.w0};
KHandle requesterHandle{state.ctx->gpr.w2};
if (requesterHandle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X}", pointer); state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X}", pointer);
auto result{state.process->MutexLock(pointer, ownerHandle)}; KHandle ownerHandle{state.ctx->gpr.w0};
KHandle requesterHandle{state.ctx->gpr.w2};
auto result{state.process->MutexLock(pointer, ownerHandle, requesterHandle)};
if (result == Result{}) if (result == Result{})
state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X}", pointer); state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X}", pointer);
else if (result == result::InvalidHandle) else if (result == result::InvalidHandle)
state.logger->Warn("svcArbitrateLock: 'handle' invalid: 0x{:X}", ownerHandle); state.logger->Warn("svcArbitrateLock: 'ownerHandle' invalid: 0x{:X}", ownerHandle);
else if (result == result::InvalidCurrentMemory) else if (result == result::InvalidCurrentMemory)
state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex or didn't have waiter flag at 0x{:X}", pointer); state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex or didn't have waiter flag at 0x{:X}", pointer);
@ -635,10 +640,9 @@ namespace skyline::kernel::svc {
} }
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", mutex); state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", mutex);
state.process->MutexUnlock(mutex); state.process->MutexUnlock(mutex);
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex); state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", mutex);
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
@ -650,31 +654,26 @@ namespace skyline::kernel::svc {
return; return;
} }
auto conditional{reinterpret_cast<void *>(state.ctx->gpr.x1)}; auto conditional{reinterpret_cast<u32 *>(state.ctx->gpr.x1)};
KHandle handle{state.ctx->gpr.w2}; KHandle requesterHandle{state.ctx->gpr.w2};
if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
state.process->MutexUnlock(mutex); i64 timeout{static_cast<i64>(state.ctx->gpr.x3)};
u64 timeout{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);
if (state.process->ConditionalVariableWait(conditional, mutex, timeout)) { auto result{state.process->ConditionalVariableWait(conditional, mutex, requesterHandle, timeout)};
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex"); if (result == Result{})
state.ctx->gpr.w0 = Result{}; state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and reacquired mutex");
} else { else if (result == result::TimedOut)
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
state.ctx->gpr.w0 = result::TimedOut; state.ctx->gpr.w0 = result;
}
} }
void SignalProcessWideKey(const DeviceState &state) { void SignalProcessWideKey(const DeviceState &state) {
auto conditional{reinterpret_cast<void *>(state.ctx->gpr.x0)}; auto key{reinterpret_cast<u32 *>(state.ctx->gpr.x0)};
KHandle count{state.ctx->gpr.w1}; KHandle count{state.ctx->gpr.w1};
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", conditional, count); state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", key, count);
state.process->ConditionalVariableSignal(conditional, count); state.process->ConditionalVariableSignal(key, count);
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }

View File

@ -109,13 +109,12 @@ namespace skyline::kernel::type {
return std::nullopt; return std::nullopt;
} }
constexpr u32 HandleWaitMask{0x40000000}; //!< A mask of a bit which denotes if the handle has waiters or not constexpr u32 HandleWaitersBit{0b01000000000000000000000000000000}; //!< A bit which denotes if a mutex psuedo-handle has waiters or not
Result KProcess::MutexLock(u32 *mutex, KHandle ownerHandle, KHandle tag) {
Result KProcess::MutexLock(u32 *mutex, KHandle ownerHandle) {
std::shared_ptr<KThread> owner; std::shared_ptr<KThread> owner;
try { try {
owner = GetHandle<KThread>(ownerHandle & ~HandleWaitMask); owner = GetHandle<KThread>(ownerHandle);
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
return result::InvalidHandle; return result::InvalidHandle;
} }
@ -125,10 +124,10 @@ namespace skyline::kernel::type {
std::lock_guard lock(owner->waiterMutex); std::lock_guard lock(owner->waiterMutex);
u32 value{}; u32 value{};
if (__atomic_compare_exchange_n(mutex, &value, (HandleWaitMask & state.thread->handle), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) if (__atomic_compare_exchange_n(mutex, &value, tag, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
// We try to do a CAS to get ownership of the mutex in the case that it's unoccupied // We try to do a CAS to get ownership of the mutex in the case that it's unoccupied
return {}; return {};
if (value != (ownerHandle | HandleWaitMask)) if (value != (ownerHandle | HandleWaitersBit))
// We ensure that the mutex's value is the handle with the waiter bit set // We ensure that the mutex's value is the handle with the waiter bit set
return result::InvalidCurrentMemory; return result::InvalidCurrentMemory;
@ -138,6 +137,7 @@ namespace skyline::kernel::type {
std::atomic_store(&state.thread->waitThread, owner); std::atomic_store(&state.thread->waitThread, owner);
state.thread->waitKey = mutex; state.thread->waitKey = mutex;
state.thread->waitTag = tag;
} }
if (isHighestPriority) { if (isHighestPriority) {
@ -178,7 +178,7 @@ namespace skyline::kernel::type {
} while (owner); } while (owner);
} }
state.scheduler->WaitSchedule(); state.scheduler->WaitSchedule(false);
return {}; return {};
} }
@ -195,12 +195,13 @@ namespace skyline::kernel::type {
// Move all threads waiting on this key to the next owner's waiter list // Move all threads waiting on this key to the next owner's waiter list
std::shared_ptr<KThread> nextWaiter{}; std::shared_ptr<KThread> nextWaiter{};
for (auto it{waiters.erase(nextOwnerIt)}; it != waiters.end(); it++) { for (auto it{waiters.erase(nextOwnerIt)}, nextIt{std::next(it)}; it != waiters.end(); it = nextIt++) {
if ((*it)->waitKey == mutex) { auto thread{*it};
if (thread->waitKey == mutex) {
nextOwner->waiters.splice(std::upper_bound(nextOwner->waiters.begin(), nextOwner->waiters.end(), (*it)->priority.load(), KThread::IsHigherPriority), waiters, it); nextOwner->waiters.splice(std::upper_bound(nextOwner->waiters.begin(), nextOwner->waiters.end(), (*it)->priority.load(), KThread::IsHigherPriority), waiters, it);
std::atomic_store(&(*it)->waitThread, nextOwner); std::atomic_store(&thread->waitThread, nextOwner);
if (!nextWaiter) if (!nextWaiter)
nextWaiter = *it; nextWaiter = thread;
} }
} }
@ -223,128 +224,73 @@ namespace skyline::kernel::type {
priority = std::min(ownerPriority, nextWaiter->priority.load()); priority = std::min(ownerPriority, nextWaiter->priority.load());
} while (ownerPriority != priority && nextOwner->priority.compare_exchange_strong(ownerPriority, priority)); } while (ownerPriority != priority && nextOwner->priority.compare_exchange_strong(ownerPriority, priority));
__atomic_store_n(mutex, nextOwner->handle | HandleWaitMask, __ATOMIC_SEQ_CST); __atomic_store_n(mutex, nextOwner->waitTag | HandleWaitersBit, __ATOMIC_SEQ_CST);
} else { } else {
__atomic_store_n(mutex, nextOwner->handle, __ATOMIC_SEQ_CST); __atomic_store_n(mutex, nextOwner->waitTag, __ATOMIC_SEQ_CST);
} }
// Finally, schedule the next owner accordingly // Finally, schedule the next owner accordingly
state.scheduler->InsertThread(nextOwner, false); state.scheduler->InsertThread(nextOwner);
} else { } else {
__atomic_store_n(mutex, 0, __ATOMIC_SEQ_CST); __atomic_store_n(mutex, 0, __ATOMIC_SEQ_CST);
return;
} }
state.scheduler->Rotate();
state.scheduler->WaitSchedule();
} }
bool KProcess::ConditionalVariableWait(void *conditional, u32 *mutex, u64 timeout) { Result KProcess::ConditionalVariableWait(u32 *key, u32 *mutex, KHandle tag, i64 timeout) {
/* FIXME {
std::unique_lock lock(conditionalLock); std::lock_guard lock(syncWaiterMutex);
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})};
auto &condWaiters{conditionals[reinterpret_cast<u64>(conditional)]}; // TODO: REMOVE THIS AFTER TESTING
std::shared_ptr<WaitStatus> status; auto prevIt{std::prev(it)}, nextIt{std::next(it)};
for (auto it{condWaiters.begin()};; it++) { if ((prevIt != syncWaiters.begin() && prevIt->first == key && prevIt->second->priority > state.thread->priority.load()))
if (it != condWaiters.end() && (*it)->priority >= state.thread->priority) throw exception("Previous node incorrect");
continue; if ((nextIt != syncWaiters.end() && nextIt->first == key && nextIt->second->priority < state.thread->priority.load()))
throw exception("Next node incorrect");
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle, mutex); __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
condWaiters.insert(it, status);
break; MutexUnlock(mutex);
state.scheduler->RemoveThread();
} }
lock.unlock(); bool hasTimedOut{};
if (timeout > 0)
bool timedOut{}; hasTimedOut = !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout));
auto start{util::GetTimeNs()};
while (!status->flag)
if ((util::GetTimeNs() - start) >= timeout)
timedOut = true;
lock.lock();
if (!status->flag)
timedOut = false;
else else
status->flag = false; state.scheduler->WaitSchedule(false);
for (auto it{condWaiters.begin()}; it != condWaiters.end(); it++) { if (hasTimedOut) {
if ((*it)->handle == state.thread->handle) { std::lock_guard lock(syncWaiterMutex);
condWaiters.erase(it); auto queue{syncWaiters.equal_range(key)};
break; syncWaiters.erase(std::find(queue.first, queue.second, SyncWaiters::value_type{key, state.thread}));
} return result::TimedOut;
} }
lock.unlock(); while (true) {
KHandle value{};
return !timedOut; if (__atomic_compare_exchange_n(mutex, &value, tag, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
*/ return {};
return false; if (!(value & HandleWaitersBit))
if (!__atomic_compare_exchange_n(mutex, &value, value | HandleWaitersBit, false, __ATOMIC_SEQ_CST, __ATOMIC_RELAXED))
continue;
if (MutexLock(mutex, value & ~HandleWaitersBit, tag) == Result{})
return {};
}
} }
void KProcess::ConditionalVariableSignal(void *conditional, u64 amount) { void KProcess::ConditionalVariableSignal(u32 *key, u64 amount) {
/* FIXME std::unique_lock lock(syncWaiterMutex);
std::unique_lock condLock(conditionalLock); auto queue{syncWaiters.equal_range(key)};
auto it{queue.first};
auto &condWaiters{conditionals[reinterpret_cast<u64>(conditional)]}; if (queue.first != queue.second)
u64 count{}; for (; it != queue.second && amount; it = syncWaiters.erase(it), amount--)
state.scheduler->InsertThread(it->second);
auto iter{condWaiters.begin()}; if (it == queue.second)
while (iter != condWaiters.end() && count < amount) { __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
auto &thread{*iter};
auto mtx{thread->mutex};
u32 mtxValue{__atomic_load_n(mtx, __ATOMIC_SEQ_CST)};
while (true) {
u32 mtxDesired{};
if (!mtxValue)
mtxDesired = (constant::MtxOwnerMask & thread->handle);
else if ((mtxValue & constant::MtxOwnerMask) == state.thread->handle)
mtxDesired = mtxValue | (constant::MtxOwnerMask & thread->handle);
else if (mtxValue & ~constant::MtxOwnerMask)
mtxDesired = mtxValue | ~constant::MtxOwnerMask;
else
break;
if (__atomic_compare_exchange_n(mtx, &mtxValue, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
break;
}
if (mtxValue && ((mtxValue & constant::MtxOwnerMask) != state.thread->handle)) {
std::unique_lock mtxLock(mutexLock);
auto &mtxWaiters{mutexes[reinterpret_cast<u64>(thread->mutex)]};
std::shared_ptr<WaitStatus> status;
for (auto it{mtxWaiters.begin()};; it++) {
if (it != mtxWaiters.end() && (*it)->priority >= thread->priority)
continue;
status = std::make_shared<WaitStatus>(thread->priority, thread->handle);
mtxWaiters.insert(it, status);
break;
}
mtxLock.unlock();
while (!status->flag);
mtxLock.lock();
status->flag = false;
for (auto it{mtxWaiters.begin()}; it != mtxWaiters.end(); it++) {
if ((*it)->handle == thread->handle) {
mtxWaiters.erase(it);
break;
}
}
mtxLock.unlock();
}
thread->flag = true;
iter++;
count++;
condLock.unlock();
while (thread->flag);
condLock.lock();
}
*/
} }
} }

View File

@ -30,6 +30,10 @@ namespace skyline {
bool disableThreadCreation{}; //!< If to disable thread creation, we use this to prevent thread creation after all threads have been killed bool disableThreadCreation{}; //!< If to disable thread creation, we use this to prevent thread creation after all threads have been killed
std::vector<std::shared_ptr<KThread>> threads; std::vector<std::shared_ptr<KThread>> threads;
using SyncWaiters = std::multimap<void*, std::shared_ptr<KThread>>;
std::mutex syncWaiterMutex; //!< Synchronizes all mutations to the map to prevent races
SyncWaiters syncWaiters; //!< All threads waiting on process-wide synchronization primitives (Atomic keys + Address Arbiter)
/** /**
* @brief The status of a single TLS page (A page is 4096 bytes on ARMv8) * @brief The status of a single TLS page (A page is 4096 bytes on ARMv8)
* Each TLS page has 8 slots, each 0x200 (512) bytes in size * Each TLS page has 8 slots, each 0x200 (512) bytes in size
@ -101,10 +105,10 @@ namespace skyline {
}; };
/** /**
* @brief Creates a new handle to a KObject and adds it to the process handle_table * @brief Creates a new handle to a KObject and adds it to the process handle_table
* @tparam objectClass The class of the kernel object to create * @tparam objectClass The class of the kernel object to create
* @param args The arguments for the kernel object except handle, pid and state * @param args The arguments for the kernel object except handle, pid and state
*/ */
template<typename objectClass, typename ...objectArgs> template<typename objectClass, typename ...objectArgs>
HandleOut<objectClass> NewHandle(objectArgs... args) { HandleOut<objectClass> NewHandle(objectArgs... args) {
std::unique_lock lock(handleMutex); std::unique_lock lock(handleMutex);
@ -119,9 +123,9 @@ namespace skyline {
} }
/** /**
* @brief Inserts an item into the process handle table * @brief Inserts an item into the process handle table
* @return The handle of the corresponding item in the handle table * @return The handle of the corresponding item in the handle table
*/ */
template<typename objectClass> template<typename objectClass>
KHandle InsertItem(std::shared_ptr<objectClass> &item) { KHandle InsertItem(std::shared_ptr<objectClass> &item) {
std::unique_lock lock(handleMutex); std::unique_lock lock(handleMutex);
@ -177,10 +181,10 @@ namespace skyline {
} }
/** /**
* @brief Retrieves a kernel memory object that owns the specified address * @brief Retrieves a kernel memory object that owns the specified address
* @param address The address to look for * @param address The address to look for
* @return A shared pointer to the corresponding KMemory object * @return A shared pointer to the corresponding KMemory object
*/ */
std::optional<HandleOut<KMemory>> GetMemoryObject(u8 *ptr); std::optional<HandleOut<KMemory>> GetMemoryObject(u8 *ptr);
/** /**
@ -191,27 +195,26 @@ namespace skyline {
} }
/** /**
* @brief Locks the mutex at the specified address * @brief Locks the mutex at the specified address
* @param ownerHandle The psuedo-handle of the current mutex owner * @param ownerHandle The psuedo-handle of the current mutex owner
*/ * @param tag The handle of the thread which is requesting this lock
Result MutexLock(u32 *mutex, KHandle ownerHandle); */
Result MutexLock(u32 *mutex, KHandle ownerHandle, KHandle tag);
/** /**
* @brief Unlocks the mutex at the specified address * @brief Unlocks the mutex at the specified address
*/ */
void MutexUnlock(u32 *mutex); void MutexUnlock(u32 *mutex);
/** /**
* @param timeout The amount of time to wait for the conditional variable * @brief Waits on the conditional variable at the specified address
* @return If the conditional variable was successfully waited for or timed out */
*/ Result ConditionalVariableWait(u32 *key, u32 *mutex, KHandle tag, i64 timeout);
bool ConditionalVariableWait(void *conditional, u32 *mutex, u64 timeout);
/** /**
* @brief Signals a number of conditional variable waiters * @brief Signals the conditional variable at the specified address
* @param amount The amount of waiters to signal */
*/ void ConditionalVariableSignal(u32 *key, u64 amount);
void ConditionalVariableSignal(void *conditional, u64 amount);
/** /**
* @brief Resets the object to an unsignalled state * @brief Resets the object to an unsignalled state

View File

@ -58,7 +58,6 @@ namespace skyline::kernel::type {
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, nce::NCE::SignalHandler); signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, nce::NCE::SignalHandler);
try { try {
state.scheduler->InsertThread(state.thread);
state.scheduler->WaitSchedule(); state.scheduler->WaitSchedule();
asm volatile( asm volatile(
@ -165,7 +164,9 @@ namespace skyline::kernel::type {
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
if (!running) { if (!running) {
running = true; running = true;
state.logger->Debug("Starting thread #{}", id); auto sharedThis{shared_from_this()};
state.scheduler->LoadBalance(sharedThis);
state.scheduler->InsertThread(sharedThis);
if (self) { if (self) {
pthread = pthread_self(); pthread = pthread_self();
lock.unlock(); lock.unlock();

View File

@ -26,7 +26,7 @@ namespace skyline {
public: public:
std::mutex mutex; //!< Synchronizes all thread state changes std::mutex mutex; //!< Synchronizes all thread state changes
bool running{false}; bool running{false}; //!< If the host thread that corresponds to this thread is running, this doesn't reflect guest scheduling changes
std::atomic<bool> cancelSync{false}; //!< This is to flag to a thread to cancel a synchronization call it currently is in std::atomic<bool> cancelSync{false}; //!< This is to flag to a thread to cancel a synchronization call it currently is in
KHandle handle; KHandle handle;
@ -51,7 +51,8 @@ namespace skyline {
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 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 on 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
std::shared_ptr<KThread> waitThread; //!< The thread which this thread is waiting on std::shared_ptr<KThread> waitThread; //!< The thread which this thread is waiting on
std::list<std::shared_ptr<type::KThread>> waiters; //!< A queue of threads waiting on this thread sorted by priority std::list<std::shared_ptr<type::KThread>> waiters; //!< A queue of threads waiting on this thread sorted by priority

View File

@ -39,6 +39,7 @@ namespace skyline::kernel {
process->InitializeHeap(); process->InitializeHeap();
auto thread{process->CreateThread(entry)}; auto thread{process->CreateThread(entry)};
if (thread) { if (thread) {
state.logger->Debug("Starting main HOS thread");
thread->Start(true); thread->Start(true);
process->Kill(true, true, true); process->Kill(true, true, true);
} }

View File

@ -7,7 +7,7 @@
namespace skyline::service::am { namespace skyline::service::am {
void ICommonStateGetter::QueueMessage(ICommonStateGetter::Message message) { void ICommonStateGetter::QueueMessage(ICommonStateGetter::Message message) {
messageQueue.emplace(message); messageQueue.emplace_back(message);
messageEvent->Signal(); messageEvent->Signal();
} }
@ -29,7 +29,11 @@ namespace skyline::service::am {
return result::NoMessages; return result::NoMessages;
response.Push(messageQueue.front()); response.Push(messageQueue.front());
messageQueue.pop(); messageQueue.pop_front();
if (messageQueue.empty())
messageEvent->ResetSignal();
return {}; return {};
} }

View File

@ -3,7 +3,6 @@
#pragma once #pragma once
#include <queue>
#include <kernel/types/KEvent.h> #include <kernel/types/KEvent.h>
#include <services/serviceman.h> #include <services/serviceman.h>
@ -33,7 +32,7 @@ namespace skyline::service::am {
}; };
std::shared_ptr<type::KEvent> messageEvent; //!< The event signalled when there is a message available std::shared_ptr<type::KEvent> messageEvent; //!< The event signalled when there is a message available
std::queue<Message> messageQueue; //!< A queue of all the messages that the program is yet to consume std::deque<Message> messageQueue; //!< A queue of all the messages that the program is yet to consume
enum class FocusState : u8 { enum class FocusState : u8 {
InFocus = 1, //!< The application is in foreground InFocus = 1, //!< The application is in foreground

View File

@ -132,7 +132,6 @@ namespace skyline::service::hosbinder {
} }
if (!nvBuffer) if (!nvBuffer)
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId); throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId);
nvmapLock.unlock();
} }
gpu::texture::Format format; gpu::texture::Format format;