mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
kernel: hand guest mutex ownership directly to FIFO waiters
This commit is contained in:
1 parent
440245f616
commit
e87fb8c859
2 files changed
+227
-49
No files matched your search
+74
-48
@@ -343,14 +343,21 @@ static bool KernelClockGettimeSpecial(KernelClockid clock_id, KernelTimespec* tp
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct PthreadMutexPrivate {
|
struct PthreadMutexPrivate {
|
||||||
|
struct Waiter {
|
||||||
|
Pthread thread = nullptr;
|
||||||
|
std::condition_variable cv;
|
||||||
|
Waiter* previous = nullptr;
|
||||||
|
Waiter* next = nullptr;
|
||||||
|
};
|
||||||
uint8_t reserved[256];
|
uint8_t reserved[256];
|
||||||
std::string name;
|
std::string name;
|
||||||
std::mutex m;
|
std::mutex m;
|
||||||
std::condition_variable cv;
|
|
||||||
Pthread owner = nullptr;
|
Pthread owner = nullptr;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
int type = 1;
|
int type = 1;
|
||||||
int pprotocol = PTHREAD_PRIO_NONE;
|
int pprotocol = PTHREAD_PRIO_NONE;
|
||||||
|
Waiter* first_waiter = nullptr;
|
||||||
|
Waiter* last_waiter = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PthreadMutexattrPrivate {
|
struct PthreadMutexattrPrivate {
|
||||||
@@ -1242,6 +1249,20 @@ static int PthreadRwlockInitNamed(PthreadRwlock* rwlock, const PthreadRwlockattr
|
|||||||
const char* name);
|
const char* name);
|
||||||
static int PthreadCondInitNamed(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
|
static int PthreadCondInitNamed(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
|
||||||
|
|
||||||
|
static void NativeMutexRemoveWaiter(PthreadMutexPrivate* mutex,
|
||||||
|
PthreadMutexPrivate::Waiter* waiter) {
|
||||||
|
if (waiter->previous != nullptr) {
|
||||||
|
waiter->previous->next = waiter->next;
|
||||||
|
} else {
|
||||||
|
mutex->first_waiter = waiter->next;
|
||||||
|
}
|
||||||
|
if (waiter->next != nullptr) {
|
||||||
|
waiter->next->previous = waiter->previous;
|
||||||
|
} else {
|
||||||
|
mutex->last_waiter = waiter->previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int NativeMutexLock(PthreadMutexPrivate* mutex, KernelUseconds* timeout_us) {
|
static int NativeMutexLock(PthreadMutexPrivate* mutex, KernelUseconds* timeout_us) {
|
||||||
EXIT_IF(mutex == nullptr);
|
EXIT_IF(mutex == nullptr);
|
||||||
|
|
||||||
@@ -1258,54 +1279,53 @@ static int NativeMutexLock(PthreadMutexPrivate* mutex, KernelUseconds* timeout_u
|
|||||||
mutex->count++;
|
mutex->count++;
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
if (timeout_us != nullptr) {
|
|
||||||
if (*timeout_us > 0) {
|
|
||||||
mutex->cv.wait_for(lock, std::chrono::microseconds(*timeout_us));
|
|
||||||
}
|
|
||||||
return ETIMEDOUT;
|
|
||||||
}
|
|
||||||
return EDEADLK;
|
return EDEADLK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout_us == nullptr) {
|
if (mutex->owner == nullptr) {
|
||||||
while (mutex->owner != nullptr) {
|
mutex->owner = self;
|
||||||
mutex->cv.wait_for(lock, std::chrono::microseconds(SIGNAL_APC_POLL_MICROS));
|
mutex->count = 1;
|
||||||
if (mutex->owner != nullptr) {
|
return OK;
|
||||||
lock.unlock();
|
}
|
||||||
KernelDispatchPendingSignalForCurrentThread();
|
if (timeout_us != nullptr && *timeout_us == 0) {
|
||||||
lock.lock();
|
return ETIMEDOUT;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (*timeout_us == 0) {
|
|
||||||
if (mutex->owner != nullptr) {
|
|
||||||
return ETIMEDOUT;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const auto deadline =
|
|
||||||
std::chrono::steady_clock::now() + std::chrono::microseconds(*timeout_us);
|
|
||||||
while (mutex->owner != nullptr) {
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
if (now >= deadline) {
|
|
||||||
return ETIMEDOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto remaining = deadline - now;
|
|
||||||
const auto poll = (remaining < std::chrono::microseconds(SIGNAL_APC_POLL_MICROS)
|
|
||||||
? remaining
|
|
||||||
: std::chrono::steady_clock::duration(
|
|
||||||
std::chrono::microseconds(SIGNAL_APC_POLL_MICROS)));
|
|
||||||
mutex->cv.wait_for(lock, poll);
|
|
||||||
if (mutex->owner != nullptr) {
|
|
||||||
lock.unlock();
|
|
||||||
KernelDispatchPendingSignalForCurrentThread();
|
|
||||||
lock.lock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex->owner = self;
|
// Guest mutex waits are not cancellation points, unlike the host condition wait.
|
||||||
mutex->count = 1;
|
int previous_cancel_state = 0;
|
||||||
return OK;
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &previous_cancel_state);
|
||||||
|
PthreadMutexPrivate::Waiter waiter;
|
||||||
|
waiter.thread = self;
|
||||||
|
waiter.previous = mutex->last_waiter;
|
||||||
|
if (mutex->last_waiter != nullptr) {
|
||||||
|
mutex->last_waiter->next = &waiter;
|
||||||
|
} else {
|
||||||
|
mutex->first_waiter = &waiter;
|
||||||
|
}
|
||||||
|
mutex->last_waiter = &waiter;
|
||||||
|
const auto deadline = timeout_us != nullptr
|
||||||
|
? std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::microseconds(*timeout_us)
|
||||||
|
: std::chrono::steady_clock::time_point::max();
|
||||||
|
int result = OK;
|
||||||
|
while (mutex->owner != self) {
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
if (now >= deadline) {
|
||||||
|
NativeMutexRemoveWaiter(mutex, &waiter);
|
||||||
|
result = ETIMEDOUT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waiter.cv.wait_until(lock, std::min(deadline, now +
|
||||||
|
std::chrono::microseconds(SIGNAL_APC_POLL_MICROS)));
|
||||||
|
if (mutex->owner != self) {
|
||||||
|
lock.unlock();
|
||||||
|
KernelDispatchPendingSignalForCurrentThread();
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock.unlock();
|
||||||
|
pthread_setcancelstate(previous_cancel_state, nullptr);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int NativeMutexTrylock(PthreadMutexPrivate* mutex) {
|
static int NativeMutexTrylock(PthreadMutexPrivate* mutex) {
|
||||||
@@ -1358,10 +1378,16 @@ static int NativeMutexUnlock(PthreadMutexPrivate* mutex, uint32_t* recurse = nul
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex->owner = nullptr;
|
if (auto* waiter = mutex->first_waiter) {
|
||||||
mutex->count = 0;
|
NativeMutexRemoveWaiter(mutex, waiter);
|
||||||
lock.unlock();
|
// Transfer ownership in FIFO order before waking the selected waiter.
|
||||||
mutex->cv.notify_one();
|
mutex->owner = waiter->thread;
|
||||||
|
mutex->count = 1;
|
||||||
|
waiter->cv.notify_one();
|
||||||
|
} else {
|
||||||
|
mutex->owner = nullptr;
|
||||||
|
mutex->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
#include "common/file.h"
|
#include "common/file.h"
|
||||||
#include "ArchiveTestFixture.h"
|
#include "ArchiveTestFixture.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/singleton.h"
|
||||||
#include "common/subsystems.h"
|
#include "common/subsystems.h"
|
||||||
#include "common/threads.h"
|
#include "common/threads.h"
|
||||||
#include "common/stringUtils.h"
|
#include "common/stringUtils.h"
|
||||||
#include "graphics/presentation/window/windowInternal.h"
|
#include "graphics/presentation/window/windowInternal.h"
|
||||||
#include "kernel/eventQueue.h"
|
#include "kernel/eventQueue.h"
|
||||||
#include "kernel/fileSystem.h"
|
#include "kernel/fileSystem.h"
|
||||||
|
#include "kernel/pthread.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/network.h"
|
#include "libs/network.h"
|
||||||
#include "loader/additionalContent.h"
|
#include "loader/additionalContent.h"
|
||||||
|
#include "loader/runtimeLinker.h"
|
||||||
#include "loader/symbolDatabase.h"
|
#include "loader/symbolDatabase.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -85,6 +88,148 @@ void Check(bool value, const char *text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Predicate>
|
||||||
|
void WaitForMutexTest(Predicate ready) {
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||||
|
while (!ready()) {
|
||||||
|
Check(std::chrono::steady_clock::now() < deadline, "mutex waiter made progress");
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MutexWaiter {
|
||||||
|
Libs::LibKernel::PthreadMutex *mutex;
|
||||||
|
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||||
|
bool cancel = false;
|
||||||
|
Libs::LibKernel::Pthread thread = nullptr;
|
||||||
|
std::vector<uint8_t> stack = std::vector<uint8_t>(1024 * 1024);
|
||||||
|
std::atomic<unsigned> parked{0}, resume{0};
|
||||||
|
std::atomic<bool> acquired{false}, release{false}, finished{false};
|
||||||
|
int result = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_local MutexWaiter *current_mutex_waiter = nullptr;
|
||||||
|
|
||||||
|
void KYTY_SYSV_ABI ParkMutexWaiter(int, void *) {
|
||||||
|
auto &waiter = *current_mutex_waiter;
|
||||||
|
const auto ticket = waiter.parked.fetch_add(1) + 1;
|
||||||
|
WaitForMutexTest([&] { return waiter.resume.load() >= ticket; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void *KYTY_SYSV_ABI RunMutexWaiter(void *arg) {
|
||||||
|
using namespace Libs::LibKernel;
|
||||||
|
auto &waiter = *static_cast<MutexWaiter *>(arg);
|
||||||
|
current_mutex_waiter = &waiter;
|
||||||
|
waiter.result = waiter.timeout == 0 ? PthreadMutexLock(waiter.mutex)
|
||||||
|
: PthreadMutexTimedlock(waiter.mutex, waiter.timeout);
|
||||||
|
if (waiter.result == OK) {
|
||||||
|
waiter.acquired = true;
|
||||||
|
WaitForMutexTest([&] { return waiter.release.load(); });
|
||||||
|
Check(PthreadMutexUnlock(waiter.mutex) == OK, "waiter unlocks owned mutex");
|
||||||
|
}
|
||||||
|
waiter.finished = true;
|
||||||
|
if (waiter.cancel) {
|
||||||
|
PthreadTestcancel();
|
||||||
|
Check(false, "deferred mutex waiter cancellation remains pending");
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueueMutexWaiter(MutexWaiter &waiter) {
|
||||||
|
using namespace Libs::LibKernel;
|
||||||
|
PthreadAttr attr = nullptr;
|
||||||
|
Check(PthreadAttrInit(&attr) == OK &&
|
||||||
|
PthreadAttrSetstack(&attr, waiter.stack.data(), waiter.stack.size()) == OK &&
|
||||||
|
PthreadCreate(&waiter.thread, &attr, RunMutexWaiter, &waiter, "MutexTest") == OK &&
|
||||||
|
PthreadAttrDestroy(&attr) == OK,
|
||||||
|
"create guest mutex waiter");
|
||||||
|
// Pending guest signals are dispatched inside the contended lock, after enqueueing.
|
||||||
|
// Holding that callback proves queue order without letting the waiter acquire.
|
||||||
|
PthreadQueuePendingSignal(waiter.thread, 30);
|
||||||
|
WaitForMutexTest([&] { return waiter.parked.load() == 1; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMutexHandoff() {
|
||||||
|
using namespace Libs::LibKernel;
|
||||||
|
Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||||
|
Initialize();
|
||||||
|
PthreadInitSelfForMainThread();
|
||||||
|
Loader::SymbolDatabase symbols;
|
||||||
|
Libs::InitLibKernel_1(&symbols);
|
||||||
|
const auto *install_symbol = symbols.FindByNid("WkwEd3N7w0Y", Loader::SymbolType::Func);
|
||||||
|
const auto *remove_symbol = symbols.FindByNid("Qhv5ARAoOEc", Loader::SymbolType::Func);
|
||||||
|
Check(install_symbol != nullptr && remove_symbol != nullptr, "mutex signal exports resolve");
|
||||||
|
const auto install = reinterpret_cast<int(KYTY_SYSV_ABI *)(int, void *)>(install_symbol->vaddr);
|
||||||
|
const auto remove = reinterpret_cast<int(KYTY_SYSV_ABI *)(int)>(remove_symbol->vaddr);
|
||||||
|
Check(install(30, reinterpret_cast<void *>(ParkMutexWaiter)) == OK, "install mutex test signal");
|
||||||
|
|
||||||
|
PthreadMutexattr attr = nullptr;
|
||||||
|
Check(PthreadMutexattrInit(&attr) == OK && PthreadMutexattrSettype(&attr, 2) == OK,
|
||||||
|
"create recursive mutex attributes");
|
||||||
|
for (unsigned timed = 0; timed < 3; ++timed) {
|
||||||
|
PthreadMutex mutex = nullptr;
|
||||||
|
Check(PthreadMutexInit(&mutex, &attr, "MutexHandoff") == OK &&
|
||||||
|
PthreadMutexLock(&mutex) == OK && PthreadMutexLock(&mutex) == OK,
|
||||||
|
"lock recursive mutex twice");
|
||||||
|
std::array<MutexWaiter, 3> waiters{{{&mutex}, {&mutex}, {&mutex}}};
|
||||||
|
waiters[timed].timeout = 5000;
|
||||||
|
for (auto &waiter : waiters) {
|
||||||
|
QueueMutexWaiter(waiter);
|
||||||
|
}
|
||||||
|
waiters[timed].resume = 1;
|
||||||
|
Check(PthreadJoin(waiters[timed].thread, nullptr) == OK &&
|
||||||
|
waiters[timed].result == KERNEL_ERROR_ETIMEDOUT,
|
||||||
|
"timed waiter unlinks from queue head, middle and tail");
|
||||||
|
Check(PthreadMutexUnlock(&mutex) == OK && PthreadMutexTrylock(&mutex) == OK &&
|
||||||
|
PthreadMutexUnlock(&mutex) == OK,
|
||||||
|
"partial recursive unlock retains ownership");
|
||||||
|
Check(PthreadMutexUnlock(&mutex) == OK &&
|
||||||
|
PthreadMutexTrylock(&mutex) == KERNEL_ERROR_EBUSY,
|
||||||
|
"unlock reserves ownership before queued waiter runs");
|
||||||
|
for (auto &waiter : waiters) {
|
||||||
|
if (&waiter == &waiters[timed]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
waiter.resume = 1;
|
||||||
|
WaitForMutexTest([&] { return waiter.acquired.load(); });
|
||||||
|
Check(PthreadMutexTrylock(&mutex) == KERNEL_ERROR_EBUSY, "FIFO waiter owns mutex");
|
||||||
|
waiter.release = true;
|
||||||
|
WaitForMutexTest([&] { return waiter.finished.load(); });
|
||||||
|
Check(PthreadJoin(waiter.thread, nullptr) == OK && waiter.result == OK,
|
||||||
|
"FIFO waiter completes");
|
||||||
|
}
|
||||||
|
Check(PthreadMutexLock(&mutex) == OK && PthreadMutexUnlock(&mutex) == OK &&
|
||||||
|
PthreadMutexDestroy(&mutex) == OK,
|
||||||
|
"mutex remains usable after timed waiter removal");
|
||||||
|
}
|
||||||
|
Check(PthreadMutexattrDestroy(&attr) == OK, "destroy recursive mutex attributes");
|
||||||
|
|
||||||
|
PthreadMutex mutex = nullptr;
|
||||||
|
Check(PthreadMutexInit(&mutex, nullptr, "MutexCancel") == OK &&
|
||||||
|
PthreadMutexLock(&mutex) == OK &&
|
||||||
|
PthreadMutexTimedlock(&mutex, 5000) == KERNEL_ERROR_EDEADLK,
|
||||||
|
"nonrecursive self timed lock reports deadlock");
|
||||||
|
MutexWaiter waiter{&mutex};
|
||||||
|
waiter.cancel = true;
|
||||||
|
QueueMutexWaiter(waiter);
|
||||||
|
Check(PthreadCancel(waiter.thread) == OK, "cancel queued mutex waiter");
|
||||||
|
PthreadQueuePendingSignal(waiter.thread, 30);
|
||||||
|
waiter.resume = 1;
|
||||||
|
WaitForMutexTest([&] { return waiter.parked.load() == 2; });
|
||||||
|
Check(PthreadMutexUnlock(&mutex) == OK &&
|
||||||
|
PthreadMutexTrylock(&mutex) == KERNEL_ERROR_EBUSY,
|
||||||
|
"canceled waiter retains its queue position");
|
||||||
|
waiter.release = true;
|
||||||
|
waiter.resume = 2;
|
||||||
|
void *result = nullptr;
|
||||||
|
Check(PthreadJoin(waiter.thread, &result) == OK &&
|
||||||
|
result == reinterpret_cast<void *>(-1) && waiter.acquired && waiter.finished,
|
||||||
|
"mutex lock defers cancellation until explicit cancellation point");
|
||||||
|
Check(PthreadMutexLock(&mutex) == OK && PthreadMutexUnlock(&mutex) == OK &&
|
||||||
|
PthreadMutexDestroy(&mutex) == OK && remove(30) == OK,
|
||||||
|
"mutex remains usable after waiter cancellation");
|
||||||
|
}
|
||||||
|
|
||||||
class TempDirectory {
|
class TempDirectory {
|
||||||
public:
|
public:
|
||||||
TempDirectory() {
|
TempDirectory() {
|
||||||
@@ -2260,7 +2405,7 @@ void CheckSocketWakeup() {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int, char**) {
|
int main(int argc, char** argv) {
|
||||||
Common::InitializeThreads();
|
Common::InitializeThreads();
|
||||||
Common::Subsystems subsystems;
|
Common::Subsystems subsystems;
|
||||||
subsystems.Initialize<Config::Lifecycle>();
|
subsystems.Initialize<Config::Lifecycle>();
|
||||||
@@ -2269,6 +2414,13 @@ int main(int, char**) {
|
|||||||
Config::Load(options);
|
Config::Load(options);
|
||||||
subsystems.Initialize<Log::Lifecycle>();
|
subsystems.Initialize<Log::Lifecycle>();
|
||||||
|
|
||||||
|
TestMutexHandoff();
|
||||||
|
if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") {
|
||||||
|
subsystems.Destroy();
|
||||||
|
std::printf("KernelFileSystemTests: mutex cases passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Check(SDL_InitSubSystem(SDL_INIT_VIDEO), "initialize Vulkan test video");
|
Check(SDL_InitSubSystem(SDL_INIT_VIDEO), "initialize Vulkan test video");
|
||||||
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
|
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
|
||||||
graphics->graphic_ctx.screen_width = 64;
|
graphics->graphic_ctx.screen_width = 64;
|
||||||
|
|||||||
Reference in new issue
Block a user