mirror of
https://github.com/yuzu-mirror/yuzu.git
synced 2024-11-03 10:49:59 +00:00
Merge pull request #202 from bunnei/scheduler-cleanup
Scheduler cleanup
This commit is contained in:
commit
23fe6f5be3
11 changed files with 239 additions and 379 deletions
|
@ -28,8 +28,6 @@ add_library(core STATIC
|
||||||
hle/config_mem.h
|
hle/config_mem.h
|
||||||
hle/ipc.h
|
hle/ipc.h
|
||||||
hle/ipc_helpers.h
|
hle/ipc_helpers.h
|
||||||
hle/kernel/address_arbiter.cpp
|
|
||||||
hle/kernel/address_arbiter.h
|
|
||||||
hle/kernel/client_port.cpp
|
hle/kernel/client_port.cpp
|
||||||
hle/kernel/client_port.h
|
hle/kernel/client_port.h
|
||||||
hle/kernel/client_session.cpp
|
hle/kernel/client_session.cpp
|
||||||
|
@ -55,6 +53,8 @@ add_library(core STATIC
|
||||||
hle/kernel/process.h
|
hle/kernel/process.h
|
||||||
hle/kernel/resource_limit.cpp
|
hle/kernel/resource_limit.cpp
|
||||||
hle/kernel/resource_limit.h
|
hle/kernel/resource_limit.h
|
||||||
|
hle/kernel/scheduler.cpp
|
||||||
|
hle/kernel/scheduler.h
|
||||||
hle/kernel/server_port.cpp
|
hle/kernel/server_port.cpp
|
||||||
hle/kernel/server_port.h
|
hle/kernel/server_port.h
|
||||||
hle/kernel/server_session.cpp
|
hle/kernel/server_session.cpp
|
||||||
|
|
|
@ -133,7 +133,7 @@ void System::Reschedule() {
|
||||||
}
|
}
|
||||||
|
|
||||||
reschedule_pending = false;
|
reschedule_pending = false;
|
||||||
Kernel::Reschedule();
|
Core::System::GetInstance().Scheduler().Reschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
|
@ -141,19 +141,20 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
|
|
||||||
switch (Settings::values.cpu_core) {
|
switch (Settings::values.cpu_core) {
|
||||||
case Settings::CpuCore::Unicorn:
|
case Settings::CpuCore::Unicorn:
|
||||||
cpu_core = std::make_unique<ARM_Unicorn>();
|
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||||
break;
|
break;
|
||||||
case Settings::CpuCore::Dynarmic:
|
case Settings::CpuCore::Dynarmic:
|
||||||
default:
|
default:
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
cpu_core = std::make_unique<ARM_Dynarmic>();
|
cpu_core = std::make_shared<ARM_Dynarmic>();
|
||||||
#else
|
#else
|
||||||
cpu_core = std::make_unique<ARM_Unicorn>();
|
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
|
||||||
gpu_core = std::make_unique<Tegra::GPU>();
|
gpu_core = std::make_unique<Tegra::GPU>();
|
||||||
|
|
||||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/kernel/scheduler.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/perf_stats.h"
|
#include "core/perf_stats.h"
|
||||||
|
@ -107,6 +108,10 @@ public:
|
||||||
return *gpu_core;
|
return *gpu_core;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::Scheduler& Scheduler() {
|
||||||
|
return *scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
PerfStats perf_stats;
|
PerfStats perf_stats;
|
||||||
FrameLimiter frame_limiter;
|
FrameLimiter frame_limiter;
|
||||||
|
|
||||||
|
@ -140,9 +145,8 @@ private:
|
||||||
/// AppLoader used to load the current executing application
|
/// AppLoader used to load the current executing application
|
||||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||||
|
|
||||||
///< ARM11 CPU core
|
std::shared_ptr<ARM_Interface> cpu_core;
|
||||||
std::unique_ptr<ARM_Interface> cpu_core;
|
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||||
|
|
||||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||||
|
|
||||||
/// When true, signals that a reschedule should happen
|
/// When true, signals that a reschedule should happen
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "core/hle/kernel/address_arbiter.h"
|
|
||||||
#include "core/hle/kernel/errors.h"
|
|
||||||
#include "core/hle/kernel/thread.h"
|
|
||||||
#include "core/memory.h"
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Kernel namespace
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
AddressArbiter::AddressArbiter() {}
|
|
||||||
AddressArbiter::~AddressArbiter() {}
|
|
||||||
|
|
||||||
SharedPtr<AddressArbiter> AddressArbiter::Create(std::string name) {
|
|
||||||
SharedPtr<AddressArbiter> address_arbiter(new AddressArbiter);
|
|
||||||
|
|
||||||
address_arbiter->name = std::move(name);
|
|
||||||
|
|
||||||
return address_arbiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, s32 value,
|
|
||||||
u64 nanoseconds) {
|
|
||||||
switch (type) {
|
|
||||||
|
|
||||||
// Signal thread(s) waiting for arbitrate address...
|
|
||||||
case ArbitrationType::Signal:
|
|
||||||
// Negative value means resume all threads
|
|
||||||
if (value < 0) {
|
|
||||||
ArbitrateAllThreads(address);
|
|
||||||
} else {
|
|
||||||
// Resume first N threads
|
|
||||||
for (int i = 0; i < value; i++)
|
|
||||||
ArbitrateHighestPriorityThread(address);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Wait current thread (acquire the arbiter)...
|
|
||||||
case ArbitrationType::WaitIfLessThan:
|
|
||||||
if ((s32)Memory::Read32(address) < value) {
|
|
||||||
Kernel::WaitCurrentThread_ArbitrateAddress(address);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ArbitrationType::WaitIfLessThanWithTimeout:
|
|
||||||
if ((s32)Memory::Read32(address) < value) {
|
|
||||||
Kernel::WaitCurrentThread_ArbitrateAddress(address);
|
|
||||||
GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ArbitrationType::DecrementAndWaitIfLessThan: {
|
|
||||||
s32 memory_value = Memory::Read32(address);
|
|
||||||
if (memory_value < value) {
|
|
||||||
// Only change the memory value if the thread should wait
|
|
||||||
Memory::Write32(address, (s32)memory_value - 1);
|
|
||||||
Kernel::WaitCurrentThread_ArbitrateAddress(address);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: {
|
|
||||||
s32 memory_value = Memory::Read32(address);
|
|
||||||
if (memory_value < value) {
|
|
||||||
// Only change the memory value if the thread should wait
|
|
||||||
Memory::Write32(address, (s32)memory_value - 1);
|
|
||||||
Kernel::WaitCurrentThread_ArbitrateAddress(address);
|
|
||||||
GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Kernel, "unknown type=%d", type);
|
|
||||||
return ERR_INVALID_ENUM_VALUE_FND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The calls that use a timeout seem to always return a Timeout error even if they did not put
|
|
||||||
// the thread to sleep
|
|
||||||
if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
|
|
||||||
type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) {
|
|
||||||
|
|
||||||
return RESULT_TIMEOUT;
|
|
||||||
}
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Kernel
|
|
|
@ -1,60 +0,0 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "core/hle/kernel/kernel.h"
|
|
||||||
#include "core/hle/result.h"
|
|
||||||
|
|
||||||
// Address arbiters are an underlying kernel synchronization object that can be created/used via
|
|
||||||
// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR
|
|
||||||
// applications use them as an underlying mechanism to implement thread-safe barriers, events, and
|
|
||||||
// semphores.
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Kernel namespace
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
enum class ArbitrationType : u32 {
|
|
||||||
Signal,
|
|
||||||
WaitIfLessThan,
|
|
||||||
DecrementAndWaitIfLessThan,
|
|
||||||
WaitIfLessThanWithTimeout,
|
|
||||||
DecrementAndWaitIfLessThanWithTimeout,
|
|
||||||
};
|
|
||||||
|
|
||||||
class AddressArbiter final : public Object {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates an address arbiter.
|
|
||||||
*
|
|
||||||
* @param name Optional name used for debugging.
|
|
||||||
* @returns The created AddressArbiter.
|
|
||||||
*/
|
|
||||||
static SharedPtr<AddressArbiter> Create(std::string name = "Unknown");
|
|
||||||
|
|
||||||
std::string GetTypeName() const override {
|
|
||||||
return "Arbiter";
|
|
||||||
}
|
|
||||||
std::string GetName() const override {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const HandleType HANDLE_TYPE = HandleType::AddressArbiter;
|
|
||||||
HandleType GetHandleType() const override {
|
|
||||||
return HANDLE_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name; ///< Name of address arbiter object (optional)
|
|
||||||
|
|
||||||
ResultCode ArbitrateAddress(ArbitrationType type, VAddr address, s32 value, u64 nanoseconds);
|
|
||||||
|
|
||||||
private:
|
|
||||||
AddressArbiter();
|
|
||||||
~AddressArbiter() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Kernel
|
|
134
src/core/hle/kernel/scheduler.cpp
Normal file
134
src/core/hle/kernel/scheduler.cpp
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright 2018 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
|
#include "core/hle/kernel/scheduler.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
|
||||||
|
|
||||||
|
Scheduler::~Scheduler() {
|
||||||
|
for (auto& thread : thread_list) {
|
||||||
|
thread->Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Scheduler::HaveReadyThreads() {
|
||||||
|
return ready_queue.get_first() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread* Scheduler::GetCurrentThread() const {
|
||||||
|
return current_thread.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread* Scheduler::PopNextReadyThread() {
|
||||||
|
Thread* next = nullptr;
|
||||||
|
Thread* thread = GetCurrentThread();
|
||||||
|
|
||||||
|
if (thread && thread->status == THREADSTATUS_RUNNING) {
|
||||||
|
// We have to do better than the current thread.
|
||||||
|
// This call returns null when that's not possible.
|
||||||
|
next = ready_queue.pop_first_better(thread->current_priority);
|
||||||
|
if (!next) {
|
||||||
|
// Otherwise just keep going with the current thread
|
||||||
|
next = thread;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next = ready_queue.pop_first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::SwitchContext(Thread* new_thread) {
|
||||||
|
Thread* previous_thread = GetCurrentThread();
|
||||||
|
|
||||||
|
// Save context for previous thread
|
||||||
|
if (previous_thread) {
|
||||||
|
previous_thread->last_running_ticks = CoreTiming::GetTicks();
|
||||||
|
cpu_core->SaveContext(previous_thread->context);
|
||||||
|
|
||||||
|
if (previous_thread->status == THREADSTATUS_RUNNING) {
|
||||||
|
// This is only the case when a reschedule is triggered without the current thread
|
||||||
|
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
||||||
|
ready_queue.push_front(previous_thread->current_priority, previous_thread);
|
||||||
|
previous_thread->status = THREADSTATUS_READY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load context of new thread
|
||||||
|
if (new_thread) {
|
||||||
|
ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
|
||||||
|
"Thread must be ready to become running.");
|
||||||
|
|
||||||
|
// Cancel any outstanding wakeup events for this thread
|
||||||
|
new_thread->CancelWakeupTimer();
|
||||||
|
|
||||||
|
auto previous_process = Kernel::g_current_process;
|
||||||
|
|
||||||
|
current_thread = new_thread;
|
||||||
|
|
||||||
|
ready_queue.remove(new_thread->current_priority, new_thread);
|
||||||
|
new_thread->status = THREADSTATUS_RUNNING;
|
||||||
|
|
||||||
|
if (previous_process != current_thread->owner_process) {
|
||||||
|
Kernel::g_current_process = current_thread->owner_process;
|
||||||
|
SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu_core->LoadContext(new_thread->context);
|
||||||
|
cpu_core->SetTlsAddress(new_thread->GetTLSAddress());
|
||||||
|
} else {
|
||||||
|
current_thread = nullptr;
|
||||||
|
// Note: We do not reset the current process and current page table when idling because
|
||||||
|
// technically we haven't changed processes, our threads are just paused.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::Reschedule() {
|
||||||
|
Thread* cur = GetCurrentThread();
|
||||||
|
Thread* next = PopNextReadyThread();
|
||||||
|
|
||||||
|
if (cur && next) {
|
||||||
|
LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
|
||||||
|
} else if (cur) {
|
||||||
|
LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
|
||||||
|
} else if (next) {
|
||||||
|
LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchContext(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
|
||||||
|
thread_list.push_back(thread);
|
||||||
|
ready_queue.prepare(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::RemoveThread(Thread* thread) {
|
||||||
|
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||||
|
thread_list.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
|
||||||
|
ASSERT(thread->status == THREADSTATUS_READY);
|
||||||
|
ready_queue.push_back(priority, thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
|
||||||
|
ASSERT(thread->status == THREADSTATUS_READY);
|
||||||
|
ready_queue.remove(priority, thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
|
||||||
|
// If thread was ready, adjust queues
|
||||||
|
if (thread->status == THREADSTATUS_READY)
|
||||||
|
ready_queue.move(thread, thread->current_priority, priority);
|
||||||
|
else
|
||||||
|
ready_queue.prepare(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
73
src/core/hle/kernel/scheduler.h
Normal file
73
src/core/hle/kernel/scheduler.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2018 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/thread_queue_list.h"
|
||||||
|
#include "core/arm/arm_interface.h"
|
||||||
|
#include "core/hle/kernel/thread.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class Scheduler final {
|
||||||
|
public:
|
||||||
|
explicit Scheduler(ARM_Interface* cpu_core);
|
||||||
|
~Scheduler();
|
||||||
|
|
||||||
|
/// Returns whether there are any threads that are ready to run.
|
||||||
|
bool HaveReadyThreads();
|
||||||
|
|
||||||
|
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||||
|
void Reschedule();
|
||||||
|
|
||||||
|
/// Gets the current running thread
|
||||||
|
Thread* GetCurrentThread() const;
|
||||||
|
|
||||||
|
/// Adds a new thread to the scheduler
|
||||||
|
void AddThread(SharedPtr<Thread> thread, u32 priority);
|
||||||
|
|
||||||
|
/// Removes a thread from the scheduler
|
||||||
|
void RemoveThread(Thread* thread);
|
||||||
|
|
||||||
|
/// Schedules a thread that has become "ready"
|
||||||
|
void ScheduleThread(Thread* thread, u32 priority);
|
||||||
|
|
||||||
|
/// Unschedules a thread that was already scheduled
|
||||||
|
void UnscheduleThread(Thread* thread, u32 priority);
|
||||||
|
|
||||||
|
/// Sets the priority of a thread in the scheduler
|
||||||
|
void SetThreadPriority(Thread* thread, u32 priority);
|
||||||
|
|
||||||
|
/// Returns a list of all threads managed by the scheduler
|
||||||
|
const std::vector<SharedPtr<Thread>>& GetThreadList() const {
|
||||||
|
return thread_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Pops and returns the next thread from the thread queue
|
||||||
|
* @return A pointer to the next ready thread
|
||||||
|
*/
|
||||||
|
Thread* PopNextReadyThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the CPU's active thread context to that of the specified thread
|
||||||
|
* @param new_thread The thread to switch to
|
||||||
|
*/
|
||||||
|
void SwitchContext(Thread* new_thread);
|
||||||
|
|
||||||
|
/// Lists all thread ids that aren't deleted/etc.
|
||||||
|
std::vector<SharedPtr<Thread>> thread_list;
|
||||||
|
|
||||||
|
/// Lists only ready thread ids.
|
||||||
|
Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
|
||||||
|
|
||||||
|
SharedPtr<Thread> current_thread = nullptr;
|
||||||
|
|
||||||
|
ARM_Interface* cpu_core;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -483,7 +483,7 @@ static void ExitProcess() {
|
||||||
g_current_process->status = ProcessStatus::Exited;
|
g_current_process->status = ProcessStatus::Exited;
|
||||||
|
|
||||||
// Stop all the process threads that are currently waiting for objects.
|
// Stop all the process threads that are currently waiting for objects.
|
||||||
auto& thread_list = GetThreadList();
|
auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
|
||||||
for (auto& thread : thread_list) {
|
for (auto& thread : thread_list) {
|
||||||
if (thread->owner_process != g_current_process)
|
if (thread->owner_process != g_current_process)
|
||||||
continue;
|
continue;
|
||||||
|
@ -585,7 +585,7 @@ static void SleepThread(s64 nanoseconds) {
|
||||||
|
|
||||||
// Don't attempt to yield execution if there are no available threads to run,
|
// Don't attempt to yield execution if there are no available threads to run,
|
||||||
// this way we avoid a useless reschedule to the idle thread.
|
// this way we avoid a useless reschedule to the idle thread.
|
||||||
if (nanoseconds == 0 && !HaveReadyThreads())
|
if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Sleep current thread and check for next thread to schedule
|
// Sleep current thread and check for next thread to schedule
|
||||||
|
|
|
@ -41,14 +41,6 @@ void Thread::Acquire(Thread* thread) {
|
||||||
// us to simply use a pool index or similar.
|
// us to simply use a pool index or similar.
|
||||||
static Kernel::HandleTable wakeup_callback_handle_table;
|
static Kernel::HandleTable wakeup_callback_handle_table;
|
||||||
|
|
||||||
// Lists all thread ids that aren't deleted/etc.
|
|
||||||
static std::vector<SharedPtr<Thread>> thread_list;
|
|
||||||
|
|
||||||
// Lists only ready thread ids.
|
|
||||||
static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
|
|
||||||
|
|
||||||
static SharedPtr<Thread> current_thread;
|
|
||||||
|
|
||||||
// The first available thread id at startup
|
// The first available thread id at startup
|
||||||
static u32 next_thread_id;
|
static u32 next_thread_id;
|
||||||
|
|
||||||
|
@ -63,10 +55,6 @@ inline static u32 const NewThreadId() {
|
||||||
Thread::Thread() {}
|
Thread::Thread() {}
|
||||||
Thread::~Thread() {}
|
Thread::~Thread() {}
|
||||||
|
|
||||||
Thread* GetCurrentThread() {
|
|
||||||
return current_thread.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the specified thread is waiting on the specified address to be arbitrated
|
* Check if the specified thread is waiting on the specified address to be arbitrated
|
||||||
* @param thread The thread to test
|
* @param thread The thread to test
|
||||||
|
@ -86,7 +74,7 @@ void Thread::Stop() {
|
||||||
// Clean up thread from ready queue
|
// Clean up thread from ready queue
|
||||||
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
|
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
|
||||||
if (status == THREADSTATUS_READY) {
|
if (status == THREADSTATUS_READY) {
|
||||||
ready_queue.remove(current_priority, this);
|
Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
status = THREADSTATUS_DEAD;
|
status = THREADSTATUS_DEAD;
|
||||||
|
@ -109,112 +97,6 @@ void Thread::Stop() {
|
||||||
Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
|
Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread* ArbitrateHighestPriorityThread(u32 address) {
|
|
||||||
Thread* highest_priority_thread = nullptr;
|
|
||||||
u32 priority = THREADPRIO_LOWEST;
|
|
||||||
|
|
||||||
// Iterate through threads, find highest priority thread that is waiting to be arbitrated...
|
|
||||||
for (auto& thread : thread_list) {
|
|
||||||
if (!CheckWait_AddressArbiter(thread.get(), address))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (thread == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (thread->current_priority <= priority) {
|
|
||||||
highest_priority_thread = thread.get();
|
|
||||||
priority = thread->current_priority;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a thread was arbitrated, resume it
|
|
||||||
if (nullptr != highest_priority_thread) {
|
|
||||||
highest_priority_thread->ResumeFromWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
return highest_priority_thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArbitrateAllThreads(u32 address) {
|
|
||||||
// Resume all threads found to be waiting on the address
|
|
||||||
for (auto& thread : thread_list) {
|
|
||||||
if (CheckWait_AddressArbiter(thread.get(), address))
|
|
||||||
thread->ResumeFromWait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the CPU's active thread context to that of the specified thread
|
|
||||||
* @param new_thread The thread to switch to
|
|
||||||
*/
|
|
||||||
static void SwitchContext(Thread* new_thread) {
|
|
||||||
Thread* previous_thread = GetCurrentThread();
|
|
||||||
|
|
||||||
// Save context for previous thread
|
|
||||||
if (previous_thread) {
|
|
||||||
previous_thread->last_running_ticks = CoreTiming::GetTicks();
|
|
||||||
Core::CPU().SaveContext(previous_thread->context);
|
|
||||||
|
|
||||||
if (previous_thread->status == THREADSTATUS_RUNNING) {
|
|
||||||
// This is only the case when a reschedule is triggered without the current thread
|
|
||||||
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
|
||||||
ready_queue.push_front(previous_thread->current_priority, previous_thread);
|
|
||||||
previous_thread->status = THREADSTATUS_READY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load context of new thread
|
|
||||||
if (new_thread) {
|
|
||||||
ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
|
|
||||||
"Thread must be ready to become running.");
|
|
||||||
|
|
||||||
// Cancel any outstanding wakeup events for this thread
|
|
||||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle);
|
|
||||||
|
|
||||||
auto previous_process = Kernel::g_current_process;
|
|
||||||
|
|
||||||
current_thread = new_thread;
|
|
||||||
|
|
||||||
ready_queue.remove(new_thread->current_priority, new_thread);
|
|
||||||
new_thread->status = THREADSTATUS_RUNNING;
|
|
||||||
|
|
||||||
if (previous_process != current_thread->owner_process) {
|
|
||||||
Kernel::g_current_process = current_thread->owner_process;
|
|
||||||
SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::CPU().LoadContext(new_thread->context);
|
|
||||||
Core::CPU().SetTlsAddress(new_thread->GetTLSAddress());
|
|
||||||
} else {
|
|
||||||
current_thread = nullptr;
|
|
||||||
// Note: We do not reset the current process and current page table when idling because
|
|
||||||
// technically we haven't changed processes, our threads are just paused.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pops and returns the next thread from the thread queue
|
|
||||||
* @return A pointer to the next ready thread
|
|
||||||
*/
|
|
||||||
static Thread* PopNextReadyThread() {
|
|
||||||
Thread* next;
|
|
||||||
Thread* thread = GetCurrentThread();
|
|
||||||
|
|
||||||
if (thread && thread->status == THREADSTATUS_RUNNING) {
|
|
||||||
// We have to do better than the current thread.
|
|
||||||
// This call returns null when that's not possible.
|
|
||||||
next = ready_queue.pop_first_better(thread->current_priority);
|
|
||||||
if (!next) {
|
|
||||||
// Otherwise just keep going with the current thread
|
|
||||||
next = thread;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next = ready_queue.pop_first();
|
|
||||||
}
|
|
||||||
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WaitCurrentThread_Sleep() {
|
void WaitCurrentThread_Sleep() {
|
||||||
Thread* thread = GetCurrentThread();
|
Thread* thread = GetCurrentThread();
|
||||||
thread->status = THREADSTATUS_WAIT_SLEEP;
|
thread->status = THREADSTATUS_WAIT_SLEEP;
|
||||||
|
@ -229,8 +111,7 @@ void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) {
|
||||||
void ExitCurrentThread() {
|
void ExitCurrentThread() {
|
||||||
Thread* thread = GetCurrentThread();
|
Thread* thread = GetCurrentThread();
|
||||||
thread->Stop();
|
thread->Stop();
|
||||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
Core::System::GetInstance().Scheduler().RemoveThread(thread);
|
||||||
thread_list.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -308,31 +189,11 @@ void Thread::ResumeFromWait() {
|
||||||
|
|
||||||
wakeup_callback = nullptr;
|
wakeup_callback = nullptr;
|
||||||
|
|
||||||
ready_queue.push_back(current_priority, this);
|
|
||||||
status = THREADSTATUS_READY;
|
status = THREADSTATUS_READY;
|
||||||
|
Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority);
|
||||||
Core::System::GetInstance().PrepareReschedule();
|
Core::System::GetInstance().PrepareReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints the thread queue for debugging purposes
|
|
||||||
*/
|
|
||||||
static void DebugThreadQueue() {
|
|
||||||
Thread* thread = GetCurrentThread();
|
|
||||||
if (!thread) {
|
|
||||||
LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD");
|
|
||||||
} else {
|
|
||||||
LOG_DEBUG(Kernel, "0x%02X %u (current)", thread->current_priority,
|
|
||||||
GetCurrentThread()->GetObjectId());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& t : thread_list) {
|
|
||||||
u32 priority = ready_queue.contains(t.get());
|
|
||||||
if (priority != -1) {
|
|
||||||
LOG_DEBUG(Kernel, "0x%02X %u", priority, t->GetObjectId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a free location for the TLS section of a thread.
|
* Finds a free location for the TLS section of a thread.
|
||||||
* @param tls_slots The TLS page array of the thread's owner process.
|
* @param tls_slots The TLS page array of the thread's owner process.
|
||||||
|
@ -400,8 +261,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||||
|
|
||||||
SharedPtr<Thread> thread(new Thread);
|
SharedPtr<Thread> thread(new Thread);
|
||||||
|
|
||||||
thread_list.push_back(thread);
|
Core::System::GetInstance().Scheduler().AddThread(thread, priority);
|
||||||
ready_queue.prepare(priority);
|
|
||||||
|
|
||||||
thread->thread_id = NewThreadId();
|
thread->thread_id = NewThreadId();
|
||||||
thread->status = THREADSTATUS_DORMANT;
|
thread->status = THREADSTATUS_DORMANT;
|
||||||
|
@ -472,12 +332,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||||
void Thread::SetPriority(u32 priority) {
|
void Thread::SetPriority(u32 priority) {
|
||||||
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
||||||
"Invalid priority value.");
|
"Invalid priority value.");
|
||||||
// If thread was ready, adjust queues
|
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
||||||
if (status == THREADSTATUS_READY)
|
|
||||||
ready_queue.move(this, current_priority, priority);
|
|
||||||
else
|
|
||||||
ready_queue.prepare(priority);
|
|
||||||
|
|
||||||
nominal_priority = current_priority = priority;
|
nominal_priority = current_priority = priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,11 +346,7 @@ void Thread::UpdatePriority() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thread::BoostPriority(u32 priority) {
|
void Thread::BoostPriority(u32 priority) {
|
||||||
// If thread was ready, adjust queues
|
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
||||||
if (status == THREADSTATUS_READY)
|
|
||||||
ready_queue.move(this, current_priority, priority);
|
|
||||||
else
|
|
||||||
ready_queue.prepare(priority);
|
|
||||||
current_priority = priority;
|
current_priority = priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,25 +372,6 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HaveReadyThreads() {
|
|
||||||
return ready_queue.get_first() != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Reschedule() {
|
|
||||||
Thread* cur = GetCurrentThread();
|
|
||||||
Thread* next = PopNextReadyThread();
|
|
||||||
|
|
||||||
if (cur && next) {
|
|
||||||
LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
|
|
||||||
} else if (cur) {
|
|
||||||
LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
|
|
||||||
} else if (next) {
|
|
||||||
LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitchContext(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::SetWaitSynchronizationResult(ResultCode result) {
|
void Thread::SetWaitSynchronizationResult(ResultCode result) {
|
||||||
context.cpu_registers[0] = result.raw;
|
context.cpu_registers[0] = result.raw;
|
||||||
}
|
}
|
||||||
|
@ -562,25 +394,18 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current thread
|
||||||
|
*/
|
||||||
|
Thread* GetCurrentThread() {
|
||||||
|
return Core::System::GetInstance().Scheduler().GetCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
void ThreadingInit() {
|
void ThreadingInit() {
|
||||||
ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||||
|
|
||||||
current_thread = nullptr;
|
|
||||||
next_thread_id = 1;
|
next_thread_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadingShutdown() {
|
void ThreadingShutdown() {}
|
||||||
current_thread = nullptr;
|
|
||||||
|
|
||||||
for (auto& t : thread_list) {
|
|
||||||
t->Stop();
|
|
||||||
}
|
|
||||||
thread_list.clear();
|
|
||||||
ready_queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<SharedPtr<Thread>>& GetThreadList() {
|
|
||||||
return thread_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -249,28 +249,6 @@ private:
|
||||||
SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
|
SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
|
||||||
SharedPtr<Process> owner_process);
|
SharedPtr<Process> owner_process);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether there are any threads that are ready to run.
|
|
||||||
*/
|
|
||||||
bool HaveReadyThreads();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reschedules to the next available thread (call after current thread is suspended)
|
|
||||||
*/
|
|
||||||
void Reschedule();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrate the highest priority thread that is waiting
|
|
||||||
* @param address The address for which waiting threads should be arbitrated
|
|
||||||
*/
|
|
||||||
Thread* ArbitrateHighestPriorityThread(VAddr address);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrate all threads currently waiting.
|
|
||||||
* @param address The address for which waiting threads should be arbitrated
|
|
||||||
*/
|
|
||||||
void ArbitrateAllThreads(VAddr address);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current thread
|
* Gets the current thread
|
||||||
*/
|
*/
|
||||||
|
@ -302,9 +280,4 @@ void ThreadingInit();
|
||||||
*/
|
*/
|
||||||
void ThreadingShutdown();
|
void ThreadingShutdown();
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a const reference to the thread list for debug use
|
|
||||||
*/
|
|
||||||
const std::vector<SharedPtr<Thread>>& GetThreadList();
|
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "yuzu/debugger/wait_tree.h"
|
#include "yuzu/debugger/wait_tree.h"
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/condition_variable.h"
|
#include "core/hle/kernel/condition_variable.h"
|
||||||
#include "core/hle/kernel/event.h"
|
#include "core/hle/kernel/event.h"
|
||||||
#include "core/hle/kernel/mutex.h"
|
#include "core/hle/kernel/mutex.h"
|
||||||
|
@ -50,7 +51,7 @@ std::size_t WaitTreeItem::Row() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
|
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
|
||||||
const auto& threads = Kernel::GetThreadList();
|
const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList();
|
||||||
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
|
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
|
||||||
item_list.reserve(threads.size());
|
item_list.reserve(threads.size());
|
||||||
for (std::size_t i = 0; i < threads.size(); ++i) {
|
for (std::size_t i = 0; i < threads.size(); ++i) {
|
||||||
|
|
Loading…
Reference in a new issue