mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
kernel: hand guest mutex ownership directly to FIFO waiters
This commit is contained in:
+74
-48
@@ -343,14 +343,21 @@ static bool KernelClockGettimeSpecial(KernelClockid clock_id, KernelTimespec* tp
|
||||
#endif
|
||||
|
||||
struct PthreadMutexPrivate {
|
||||
struct Waiter {
|
||||
Pthread thread = nullptr;
|
||||
std::condition_variable cv;
|
||||
Waiter* previous = nullptr;
|
||||
Waiter* next = nullptr;
|
||||
};
|
||||
uint8_t reserved[256];
|
||||
std::string name;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
Pthread owner = nullptr;
|
||||
uint32_t count = 0;
|
||||
int type = 1;
|
||||
int pprotocol = PTHREAD_PRIO_NONE;
|
||||
Waiter* first_waiter = nullptr;
|
||||
Waiter* last_waiter = nullptr;
|
||||
};
|
||||
|
||||
struct PthreadMutexattrPrivate {
|
||||
@@ -1242,6 +1249,20 @@ static int PthreadRwlockInitNamed(PthreadRwlock* rwlock, const PthreadRwlockattr
|
||||
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) {
|
||||
EXIT_IF(mutex == nullptr);
|
||||
|
||||
@@ -1258,54 +1279,53 @@ static int NativeMutexLock(PthreadMutexPrivate* mutex, KernelUseconds* timeout_u
|
||||
mutex->count++;
|
||||
return OK;
|
||||
}
|
||||
if (timeout_us != nullptr) {
|
||||
if (*timeout_us > 0) {
|
||||
mutex->cv.wait_for(lock, std::chrono::microseconds(*timeout_us));
|
||||
}
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
return EDEADLK;
|
||||
}
|
||||
|
||||
if (timeout_us == nullptr) {
|
||||
while (mutex->owner != nullptr) {
|
||||
mutex->cv.wait_for(lock, std::chrono::microseconds(SIGNAL_APC_POLL_MICROS));
|
||||
if (mutex->owner != nullptr) {
|
||||
lock.unlock();
|
||||
KernelDispatchPendingSignalForCurrentThread();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
if (mutex->owner == nullptr) {
|
||||
mutex->owner = self;
|
||||
mutex->count = 1;
|
||||
return OK;
|
||||
}
|
||||
if (timeout_us != nullptr && *timeout_us == 0) {
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
|
||||
mutex->owner = self;
|
||||
mutex->count = 1;
|
||||
return OK;
|
||||
// Guest mutex waits are not cancellation points, unlike the host condition wait.
|
||||
int previous_cancel_state = 0;
|
||||
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) {
|
||||
@@ -1358,10 +1378,16 @@ static int NativeMutexUnlock(PthreadMutexPrivate* mutex, uint32_t* recurse = nul
|
||||
return OK;
|
||||
}
|
||||
|
||||
mutex->owner = nullptr;
|
||||
mutex->count = 0;
|
||||
lock.unlock();
|
||||
mutex->cv.notify_one();
|
||||
if (auto* waiter = mutex->first_waiter) {
|
||||
NativeMutexRemoveWaiter(mutex, waiter);
|
||||
// Transfer ownership in FIFO order before waking the selected waiter.
|
||||
mutex->owner = waiter->thread;
|
||||
mutex->count = 1;
|
||||
waiter->cv.notify_one();
|
||||
} else {
|
||||
mutex->owner = nullptr;
|
||||
mutex->count = 0;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
#include "common/file.h"
|
||||
#include "ArchiveTestFixture.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/additionalContent.h"
|
||||
#include "loader/runtimeLinker.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#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 {
|
||||
public:
|
||||
TempDirectory() {
|
||||
@@ -2260,7 +2405,7 @@ void CheckSocketWakeup() {
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int, char**) {
|
||||
int main(int argc, char** argv) {
|
||||
Common::InitializeThreads();
|
||||
Common::Subsystems subsystems;
|
||||
subsystems.Initialize<Config::Lifecycle>();
|
||||
@@ -2269,6 +2414,13 @@ int main(int, char**) {
|
||||
Config::Load(options);
|
||||
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");
|
||||
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
|
||||
graphics->graphic_ctx.screen_width = 64;
|
||||
|
||||
Reference in New Issue
Block a user