Kernel: Refactor synchronization to better match RE

This commit is contained in:
Fernando Sahmkow 2020-02-11 17:36:39 -04:00 committed by FernandoS27
parent c5aefe42aa
commit d23d504d77
23 changed files with 212 additions and 80 deletions

View file

@ -183,6 +183,8 @@ add_library(core STATIC
hle/kernel/svc_wrap.h
hle/kernel/synchronization_object.cpp
hle/kernel/synchronization_object.h
hle/kernel/synchronization.cpp
hle/kernel/synchronization.h
hle/kernel/thread.cpp
hle/kernel/thread.h
hle/kernel/transfer_memory.cpp

View file

@ -31,6 +31,11 @@ void ClientSession::Acquire(Thread* thread) {
UNIMPLEMENTED();
}
bool ClientSession::IsSignaled() const {
UNIMPLEMENTED();
return true;
}
ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kernel,
std::shared_ptr<Session> parent,
std::string name) {

View file

@ -48,6 +48,8 @@ public:
void Acquire(Thread* thread) override;
bool IsSignaled() const override;
private:
static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel,
std::shared_ptr<Session> parent,

View file

@ -23,6 +23,7 @@
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
@ -96,7 +97,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
}
struct KernelCore::Impl {
explicit Impl(Core::System& system) : system{system}, global_scheduler{system} {}
explicit Impl(Core::System& system)
: system{system}, global_scheduler{system}, synchronization{system} {}
void Initialize(KernelCore& kernel) {
Shutdown();
@ -191,6 +193,7 @@ struct KernelCore::Impl {
std::vector<std::shared_ptr<Process>> process_list;
Process* current_process = nullptr;
Kernel::GlobalScheduler global_scheduler;
Kernel::Synchronization synchronization;
std::shared_ptr<ResourceLimit> system_resource_limit;
@ -270,6 +273,14 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
return impl->cores[id];
}
Kernel::Synchronization& KernelCore::Synchronization() {
return impl->synchronization;
}
const Kernel::Synchronization& KernelCore::Synchronization() const {
return impl->synchronization;
}
Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
return *impl->exclusive_monitor;
}

View file

@ -29,6 +29,7 @@ class HandleTable;
class PhysicalCore;
class Process;
class ResourceLimit;
class Synchronization;
class Thread;
/// Represents a single instance of the kernel.
@ -92,6 +93,12 @@ public:
/// Gets the an instance of the respective physical CPU core.
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
/// Gets the an instance of the Synchronization Interface.
Kernel::Synchronization& Synchronization();
/// Gets the an instance of the Synchronization Interface.
const Kernel::Synchronization& Synchronization() const;
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);

View file

@ -357,7 +357,7 @@ void Process::ChangeStatus(ProcessStatus new_status) {
status = new_status;
is_signaled = true;
WakeupAllWaitingThreads();
Signal();
}
void Process::AllocateMainThreadStack(u64 stack_size) {

View file

@ -359,10 +359,6 @@ private:
/// specified by metadata provided to the process during loading.
bool is_64bit_process = true;
/// Whether or not this process is signaled. This occurs
/// upon the process changing to a different state.
bool is_signaled = false;
/// Total running time for the process in ticks.
u64 total_process_running_time_ticks = 0;

View file

@ -15,26 +15,26 @@ ReadableEvent::ReadableEvent(KernelCore& kernel) : SynchronizationObject{kernel}
ReadableEvent::~ReadableEvent() = default;
bool ReadableEvent::ShouldWait(const Thread* thread) const {
return !signaled;
return !is_signaled;
}
void ReadableEvent::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
ASSERT_MSG(IsSignaled(), "object unavailable!");
}
void ReadableEvent::Signal() {
if (!signaled) {
signaled = true;
WakeupAllWaitingThreads();
if (!is_signaled) {
is_signaled = true;
SynchronizationObject::Signal();
};
}
void ReadableEvent::Clear() {
signaled = false;
is_signaled = false;
}
ResultCode ReadableEvent::Reset() {
if (!signaled) {
if (!is_signaled) {
return ERR_INVALID_STATE;
}

View file

@ -46,13 +46,11 @@ public:
/// then ERR_INVALID_STATE will be returned.
ResultCode Reset();
void Signal() override;
private:
explicit ReadableEvent(KernelCore& kernel);
void Signal();
bool signaled{};
std::string name; ///< Name of event (optional)
};

View file

@ -39,6 +39,10 @@ void ServerPort::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
bool ServerPort::IsSignaled() const {
return !pending_sessions.empty();
}
ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions,
std::string name) {
std::shared_ptr<ServerPort> server_port = std::make_shared<ServerPort>(kernel);

View file

@ -82,6 +82,8 @@ public:
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
bool IsSignaled() const override;
private:
/// ServerSessions waiting to be accepted by the port
std::vector<std::shared_ptr<ServerSession>> pending_sessions;

View file

@ -50,6 +50,16 @@ bool ServerSession::ShouldWait(const Thread* thread) const {
return pending_requesting_threads.empty() || currently_handling != nullptr;
}
bool ServerSession::IsSignaled() const {
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
if (!parent->Client()) {
return true;
}
// Wait if we have no pending requests, or if we're currently handling a request.
return !(pending_requesting_threads.empty() || currently_handling != nullptr);
}
void ServerSession::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
// We are now handling a request, pop it from the stack.

View file

@ -73,6 +73,8 @@ public:
return parent.get();
}
bool IsSignaled() const override;
/**
* Sets the HLE handler for the session. This handler will be called to service IPC requests
* instead of the regular IPC machinery. (The regular IPC machinery is currently not

View file

@ -29,6 +29,11 @@ bool Session::ShouldWait(const Thread* thread) const {
return {};
}
bool Session::IsSignaled() const {
UNIMPLEMENTED();
return true;
}
void Session::Acquire(Thread* thread) {
UNIMPLEMENTED();
}

View file

@ -39,6 +39,8 @@ public:
bool ShouldWait(const Thread* thread) const override;
bool IsSignaled() const override;
void Acquire(Thread* thread) override;
std::shared_ptr<ClientSession> Client() {

View file

@ -32,6 +32,7 @@
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_wrap.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/transfer_memory.h"
#include "core/hle/kernel/writable_event.h"
@ -433,23 +434,6 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
return ERR_INVALID_HANDLE;
}
/// Default thread wakeup callback for WaitSynchronization
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<SynchronizationObject> object,
std::size_t index) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
if (reason == ThreadWakeupReason::Timeout) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
return true;
}
ASSERT(reason == ThreadWakeupReason::Signal);
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
return true;
};
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address,
u64 handle_count, s64 nano_seconds) {
@ -473,10 +457,10 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
}
auto* const thread = system.CurrentScheduler().GetCurrentThread();
auto& kernel = system.Kernel();
using ObjectPtr = Thread::ThreadSynchronizationObjects::value_type;
Thread::ThreadSynchronizationObjects objects(handle_count);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
for (u64 i = 0; i < handle_count; ++i) {
const Handle handle = memory.Read32(handles_address + i * sizeof(Handle));
@ -489,47 +473,10 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
objects[i] = object;
}
// Find the first object that is acquirable in the provided list of objects
auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) {
return !object->ShouldWait(thread);
});
if (itr != objects.end()) {
// We found a ready object, acquire it and set the result value
SynchronizationObject* object = itr->get();
object->Acquire(thread);
*index = static_cast<s32>(std::distance(objects.begin(), itr));
return RESULT_SUCCESS;
}
// No objects were ready to be acquired, prepare to suspend the thread.
// If a timeout value of 0 was provided, just return the Timeout error code instead of
// suspending the thread.
if (nano_seconds == 0) {
return RESULT_TIMEOUT;
}
if (thread->IsSyncCancelled()) {
thread->SetSyncCancelled(false);
return ERR_SYNCHRONIZATION_CANCELED;
}
for (auto& object : objects) {
object->AddWaitingThread(SharedFrom(thread));
}
thread->SetSynchronizationObjects(std::move(objects));
thread->SetStatus(ThreadStatus::WaitSynch);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
system.PrepareReschedule(thread->GetProcessorID());
return RESULT_TIMEOUT;
auto& synchronization = kernel.Synchronization();
auto [result, handle_result] = synchronization.WaitFor(objects, nano_seconds);
*index = handle_result;
return result;
}
/// Resumes a thread waiting on WaitSynchronization

View file

@ -0,0 +1,86 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
/// Default thread wakeup callback for WaitSynchronization
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<SynchronizationObject> object,
std::size_t index) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
if (reason == ThreadWakeupReason::Timeout) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
return true;
}
ASSERT(reason == ThreadWakeupReason::Signal);
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
return true;
};
Synchronization::Synchronization(Core::System& system) : system{system} {}
void Synchronization::SignalObject(SynchronizationObject& obj) const {
if (obj.IsSignaled()) {
obj.WakeupAllWaitingThreads();
};
}
std::pair<ResultCode, Handle> Synchronization::WaitFor(
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
auto* const thread = system.CurrentScheduler().GetCurrentThread();
// Find the first object that is acquirable in the provided list of objects
auto itr = std::find_if(sync_objects.begin(), sync_objects.end(),
[thread](const std::shared_ptr<SynchronizationObject>& object) {
return object->IsSignaled();
});
if (itr != sync_objects.end()) {
// We found a ready object, acquire it and set the result value
SynchronizationObject* object = itr->get();
object->Acquire(thread);
u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
return {RESULT_SUCCESS, index};
}
// No objects were ready to be acquired, prepare to suspend the thread.
// If a timeout value of 0 was provided, just return the Timeout error code instead of
// suspending the thread.
if (nano_seconds == 0) {
return {RESULT_TIMEOUT, 0};
}
if (thread->IsSyncCancelled()) {
thread->SetSyncCancelled(false);
return {ERR_SYNCHRONIZATION_CANCELED, 0};
}
for (auto& object : sync_objects) {
object->AddWaitingThread(SharedFrom(thread));
}
thread->SetSynchronizationObjects(std::move(sync_objects));
thread->SetStatus(ThreadStatus::WaitSynch);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
system.PrepareReschedule(thread->GetProcessorID());
return {RESULT_TIMEOUT, 0};
}
} // namespace Kernel

View file

@ -0,0 +1,34 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <utility>
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Core {
class System;
} // namespace Core
namespace Kernel {
class KernelCore;
class SynchronizationObject;
class Synchronization {
public:
Synchronization(Core::System& system);
void SignalObject(SynchronizationObject& obj) const;
std::pair<ResultCode, Handle> WaitFor(
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds);
private:
Core::System& system;
};
} // namespace Kernel

View file

@ -10,6 +10,7 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/kernel/thread.h"
@ -18,6 +19,10 @@ namespace Kernel {
SynchronizationObject::SynchronizationObject(KernelCore& kernel) : Object{kernel} {}
SynchronizationObject::~SynchronizationObject() = default;
void SynchronizationObject::Signal() {
kernel.Synchronization().SignalObject(*this);
}
void SynchronizationObject::AddWaitingThread(std::shared_ptr<Thread> thread) {
auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
if (itr == waiting_threads.end())

View file

@ -30,6 +30,13 @@ public:
/// Acquire/lock the object for the specified thread if it is available
virtual void Acquire(Thread* thread) = 0;
/// Signal this object
virtual void Signal();
virtual bool IsSignaled() const {
return is_signaled;
}
/**
* Add a thread to wait on this object
* @param thread Pointer to thread to add
@ -60,6 +67,9 @@ public:
/// Get a const reference to the waiting threads list for debug use
const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
protected:
bool is_signaled{}; // Tells if this sync object is signalled;
private:
/// Threads waiting for this object to become available
std::vector<std::shared_ptr<Thread>> waiting_threads;

View file

@ -31,6 +31,10 @@ bool Thread::ShouldWait(const Thread* thread) const {
return status != ThreadStatus::Dead;
}
bool Thread::IsSignaled() const {
return status == ThreadStatus::Dead;
}
void Thread::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
@ -45,7 +49,7 @@ void Thread::Stop() {
kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
callback_handle = 0;
SetStatus(ThreadStatus::Dead);
WakeupAllWaitingThreads();
Signal();
// Clean up any dangling references in objects that this thread was waiting for
for (auto& wait_object : wait_objects) {

View file

@ -146,6 +146,7 @@ public:
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
bool IsSignaled() const override;
/**
* Gets the thread's current priority

View file

@ -22,7 +22,6 @@ EventPair WritableEvent::CreateEventPair(KernelCore& kernel, std::string name) {
writable_event->name = name + ":Writable";
writable_event->readable = readable_event;
readable_event->name = name + ":Readable";
readable_event->signaled = false;
return {std::move(readable_event), std::move(writable_event)};
}
@ -40,7 +39,7 @@ void WritableEvent::Clear() {
}
bool WritableEvent::IsSignaled() const {
return readable->signaled;
return readable->IsSignaled();
}
} // namespace Kernel