Addition of Conditional Variables

This commit adds working conditional variables, in addition to the mutex and threading implementation. It directly depends on the memory optimization from the previous commit to be able to perform atomic operations on the mutex.
This commit is contained in:
◱ PixelyIon 2020-02-05 12:12:53 +05:30 committed by ◱ PixelyIon
parent 03b65bd90a
commit 694c8ef4e2
4 changed files with 140 additions and 84 deletions

View File

@ -43,7 +43,7 @@ namespace skyline {
constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization
constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle
constexpr handle_t ThreadSelf = 0xFFFF8000; //!< This is the handle used by threads to refer to themselves
constexpr u8 DefaultPriority = 31; //!< The default priority of a process
constexpr u8 DefaultPriority = 44; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr u32 MtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex

View File

@ -10,7 +10,7 @@ namespace skyline::kernel::svc {
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return;
}
auto& heap = state.process->heap;
auto &heap = state.process->heap;
heap->Resize(size);
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.x1 = heap->address;
@ -45,7 +45,7 @@ namespace skyline::kernel::svc {
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", address);
return;
}
if(!chunk->state.AttributeChangeAllowed) {
if (!chunk->state.AttributeChangeAllowed) {
state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", address);
return;
@ -60,29 +60,29 @@ namespace skyline::kernel::svc {
const u64 destination = state.ctx->registers.x0;
const u64 source = state.ctx->registers.x1;
const u64 size = state.ctx->registers.x2;
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) {
if (!utils::PageAligned(destination) || !utils::PageAligned(source)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!utils::PageAligned(size)) {
if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(destination)) {
if (!stack.IsInside(destination)) {
state.ctx->registers.w0 = constant::status::InvMemRange;
state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
auto descriptor = state.os->memory.Get(source);
if(!descriptor) {
if (!descriptor) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!descriptor->chunk.state.MapAllowed) {
if (!descriptor->chunk.state.MapAllowed) {
state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, descriptor->chunk.state.value);
return;
@ -90,7 +90,7 @@ namespace skyline::kernel::svc {
state.process->NewHandle<type::KPrivateMemory>(destination, size, descriptor->block.permission, memory::MemoryStates::Stack);
state.process->CopyMemory(source, destination, size);
auto object = state.process->GetMemoryObject(source);
if(!object)
if (!object)
throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source);
object->item->UpdatePermission(source, size, {false, false, false});
state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
@ -101,41 +101,41 @@ namespace skyline::kernel::svc {
const u64 source = state.ctx->registers.x0;
const u64 destination = state.ctx->registers.x1;
const u64 size = state.ctx->registers.x2;
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) {
if (!utils::PageAligned(destination) || !utils::PageAligned(source)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!utils::PageAligned(size)) {
if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return;
}
auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(source)) {
if (!stack.IsInside(source)) {
state.ctx->registers.w0 = constant::status::InvMemRange;
state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
auto sourceDesc = state.os->memory.Get(source);
auto destDesc = state.os->memory.Get(destination);
if(!sourceDesc || !destDesc) {
if (!sourceDesc || !destDesc) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return;
}
if(!destDesc->chunk.state.MapAllowed) {
if (!destDesc->chunk.state.MapAllowed) {
state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destDesc->chunk.state.value);
return;
}
auto destObject = state.process->GetMemoryObject(destination);
if(!destObject)
if (!destObject)
throw exception("svcUnmapMemory: Cannot find destination memory object in handle table for address 0x{:X}", destination);
destObject->item->UpdatePermission(destination, size, sourceDesc->block.permission);
state.process->CopyMemory(destination, source, size);
auto sourceObject = state.process->GetMemoryObject(destination);
if(!sourceObject)
if (!sourceObject)
throw exception("svcUnmapMemory: Cannot find source memory object in handle table for address 0x{:X}", source);
state.process->DeleteHandle(sourceObject->handle);
state.logger->Debug("svcUnmapMemory: Unmapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
@ -379,7 +379,7 @@ namespace skyline::kernel::svc {
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start = utils::GetCurrTimeNs();
while (true) {
if(state.thread->cancelSync) {
if (state.thread->cancelSync) {
state.thread->cancelSync = false;
state.ctx->registers.w0 = constant::status::Interrupted;
break;
@ -414,62 +414,63 @@ namespace skyline::kernel::svc {
void ArbitrateLock(DeviceState &state) {
auto address = state.ctx->registers.x1;
if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
return;
}
auto ownerHandle = state.ctx->registers.w0;
auto requesterHandle = state.ctx->registers.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} for thread {}", address, state.thread->pid);
state.process->MutexLock(address, ownerHandle);
state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X} for thread {}", address, state.thread->pid);
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X}", address);
if (state.process->MutexLock(address, ownerHandle))
state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X}", address);
else
state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::Success;
}
void ArbitrateUnlock(DeviceState &state) {
auto address = state.ctx->registers.x0;
if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
return;
}
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address);
if(state.process->MutexUnlock(address)) {
state.ctx->registers.w0 = constant::status::Success;
if (state.process->MutexUnlock(address)) {
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::Success;
} else {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
}
}
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddress = state.ctx->registers.x0;
if (!utils::WordAligned(mtxAddress)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress);
state.ctx->registers.w0 = constant::status::InvAddress;
return;
}
auto condAddress = state.ctx->registers.x1;
auto handle = state.ctx->registers.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);
if(!state.process->MutexUnlock(mtxAddress)) {
state.ctx->registers.w0 = constant::status::InvAddress;
if (!state.process->MutexUnlock(mtxAddress)) {
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mtxAddress);
state.ctx->registers.w0 = constant::status::InvAddress;
return;
}
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
if (state.process->ConditionalVariableWait(condAddress, timeout)) {
state.ctx->registers.w0 = constant::status::Success;
state.process->MutexLock(mtxAddress, handle, true);
if (state.process->ConditionalVariableWait(condAddress, mtxAddress, timeout)) {
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
state.ctx->registers.w0 = constant::status::Success;
} else {
state.ctx->registers.w0 = constant::status::Timeout;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
state.ctx->registers.w0 = constant::status::Timeout;
}
}

View File

@ -109,7 +109,7 @@ namespace skyline::kernel::type {
}
void KProcess::WriteMemory(void *source, const u64 offset, const size_t size, const bool forceGuest) const {
if(!forceGuest) {
if (!forceGuest) {
auto destination = GetHostAddress(offset);
if (destination) {
memcpy(reinterpret_cast<void *>(destination), source, size);
@ -131,7 +131,7 @@ namespace skyline::kernel::type {
void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const {
auto sourceHost = GetHostAddress(source);
auto destinationHost = GetHostAddress(destination);
if(sourceHost && destinationHost) {
if (sourceHost && destinationHost) {
memcpy(reinterpret_cast<void *>(destinationHost), reinterpret_cast<const void *>(sourceHost), size);
} else {
if (size <= PAGE_SIZE) {
@ -165,65 +165,66 @@ namespace skyline::kernel::type {
return std::nullopt;
}
void KProcess::MutexLock(u64 address, handle_t owner, bool alwaysLock) {
bool KProcess::MutexLock(u64 address, handle_t owner) {
std::unique_lock lock(mutexLock);
u32 mtxVal = ReadMemory<u32>(address);
if (alwaysLock) {
if (!mtxVal) {
state.logger->Warn("Mutex Value was 0");
mtxVal = (constant::MtxOwnerMask & state.thread->handle);
WriteMemory<u32>(mtxVal, address);
return;
// TODO: Replace with atomic CAS
}
} else {
if (mtxVal != (owner | ~constant::MtxOwnerMask))
return;
}
auto mtx = GetPointer<u32>(address);
auto &mtxWaiters = mutexes[address];
u32 mtxExpected = 0;
if (__atomic_compare_exchange_n(mtx, &mtxExpected, (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return true;
if (owner && (__atomic_load_n(mtx, __ATOMIC_SEQ_CST) != (owner | ~constant::MtxOwnerMask)))
return false;
std::shared_ptr<WaitStatus> status;
for (auto it = mtxWaiters.begin();; ++it) {
if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority)
continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid);
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle);
mtxWaiters.insert(it, status);
break;
}
lock.unlock();
while (!status->flag);
lock.lock();
status->flag = false;
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it)
if ((*it)->pid == state.thread->pid) {
if ((*it)->handle == state.thread->handle) {
mtxWaiters.erase(it);
break;
}
mtxVal = (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask);
WriteMemory<u32>(mtxVal, address);
lock.unlock();
}
bool KProcess::MutexUnlock(u64 address) {
std::lock_guard lock(mutexLock);
u32 mtxVal = ReadMemory<u32>(address);
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
return false;
auto &mtxWaiters = mutexes[address];
if (mtxWaiters.empty()) {
mtxVal = 0;
WriteMemory<u32>(mtxVal, address);
} else
(*mtxWaiters.begin())->flag = true;
return true;
}
bool KProcess::ConditionalVariableWait(u64 address, u64 timeout) {
bool KProcess::MutexUnlock(u64 address) {
std::unique_lock lock(mutexLock);
auto mtx = GetPointer<u32>(address);
auto &mtxWaiters = mutexes[address];
u32 mtxDesired{};
if (!mtxWaiters.empty())
mtxDesired = (*mtxWaiters.begin())->handle | ((mtxWaiters.size() > 1) ? ~constant::MtxOwnerMask : 0);
u32 mtxExpected = (constant::MtxOwnerMask & state.thread->handle) | ~constant::MtxOwnerMask;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
mtxExpected = constant::MtxOwnerMask & state.thread->handle;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return false;
}
if (mtxDesired) {
auto status = (*mtxWaiters.begin());
status->flag = true;
lock.unlock();
while(status->flag);
lock.lock();
}
return true;
}
bool KProcess::ConditionalVariableWait(u64 conditionalAddress, u64 mutexAddress, u64 timeout) {
std::unique_lock lock(conditionalLock);
auto &condWaiters = conditionals[address];
auto &condWaiters = conditionals[conditionalAddress];
std::shared_ptr<WaitStatus> status;
for (auto it = condWaiters.begin();; ++it) {
if (it != condWaiters.end() && (*it)->priority >= state.thread->priority)
continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid);
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle, mutexAddress);
condWaiters.insert(it, status);
break;
}
@ -235,8 +236,12 @@ namespace skyline::kernel::type {
timedOut = true;
}
lock.lock();
if (!status->flag)
timedOut = false;
else
status->flag = false;
for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it)
if ((*it)->pid == state.thread->pid) {
if ((*it)->handle == state.thread->handle) {
condWaiters.erase(it);
break;
}
@ -245,10 +250,56 @@ namespace skyline::kernel::type {
}
void KProcess::ConditionalVariableSignal(u64 address, u64 amount) {
std::lock_guard lock(conditionalLock);
std::unique_lock condLock(conditionalLock);
auto &condWaiters = conditionals[address];
amount = std::min(condWaiters.size(), amount);
for (size_t i = 0; i < amount; ++i)
condWaiters[i]->flag = true;
u64 count{};
auto iter = condWaiters.begin();
while (iter != condWaiters.end() && count < amount) {
auto &thread = *iter;
auto mtx = GetPointer<u32>(thread->mutexAddress);
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[thread->mutexAddress];
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

@ -7,7 +7,7 @@
#include "KSession.h"
#include "KEvent.h"
#include <kernel/memory.h>
#include <condition_variable>
#include <list>
namespace skyline::kernel::type {
/**
@ -91,9 +91,12 @@ namespace skyline::kernel::type {
struct WaitStatus {
std::atomic_bool flag{false}; //!< The underlying atomic flag of the thread
u8 priority; //!< The priority of the thread
pid_t pid; //!< The PID of the thread
handle_t handle; //!< The handle of the thread
u64 mutexAddress{}; //!< The address of the mutex
WaitStatus(u8 priority, pid_t pid) : priority(priority), pid(pid) {}
WaitStatus(u8 priority, handle_t handle) : priority(priority), handle(handle) {}
WaitStatus(u8 priority, handle_t handle, u64 mutexAddress) : priority(priority), handle(handle), mutexAddress(mutexAddress) {}
};
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
@ -102,11 +105,11 @@ namespace skyline::kernel::type {
std::unordered_map<handle_t, std::shared_ptr<KObject>> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::unordered_map<pid_t, std::shared_ptr<KThread>> threads; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::unordered_map<u64, std::list<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
std::shared_ptr<KPrivateMemory> heap; //!< The kernel memory object backing the allocated heap
Mutex mutexLock; //!< This Mutex is to prevent concurrent mutex operations to happen at once
Mutex conditionalLock; //!< This Mutex is to prevent concurrent conditional variable operations to happen at once
Mutex mutexLock; //!< This mutex is to prevent concurrent mutex operations to happen at once
Mutex conditionalLock; //!< This mutex is to prevent concurrent conditional variable operations to happen at once
/**
* @brief Creates a KThread object for the main thread and opens the process's memory file
@ -296,9 +299,9 @@ namespace skyline::kernel::type {
* @brief This locks the Mutex at the specified address
* @param address The address of the mutex
* @param owner The handle of the current mutex owner
* @param alwaysLock If to return rather than lock if owner tag is not matched
* @return If the mutex was successfully locked
*/
void MutexLock(u64 address, handle_t owner, bool alwaysLock = false);
bool MutexLock(u64 address, handle_t owner);
/**
* @brief This unlocks the Mutex at the specified address
@ -308,11 +311,12 @@ namespace skyline::kernel::type {
bool MutexUnlock(u64 address);
/**
* @param address The address of the conditional variable
* @param conditionalAddress The address of the conditional variable
* @param mutexAddress The address of the mutex
* @param timeout The amount of time to wait for the conditional variable
* @return If the conditional variable was successfully waited for or timed out
*/
bool ConditionalVariableWait(u64 address, u64 timeout);
bool ConditionalVariableWait(u64 conditionalAddress, u64 mutexAddress, u64 timeout);
/**
* @brief This signals a number of conditional variable waiters