hle: kernel: KThread: Ensure host (dummy) threads block on locking.
- But do not enter the priority queue, as otherwise they will be scheduled. - Allows dummy threads to use guest synchronization primitives.
This commit is contained in:
parent
f6815086a1
commit
615fb40416
4 changed files with 89 additions and 0 deletions
|
@ -45,6 +45,7 @@ concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) {
|
||||||
|
|
||||||
{ t.GetActiveCore() } -> Common::ConvertibleTo<s32>;
|
{ t.GetActiveCore() } -> Common::ConvertibleTo<s32>;
|
||||||
{ t.GetPriority() } -> Common::ConvertibleTo<s32>;
|
{ t.GetPriority() } -> Common::ConvertibleTo<s32>;
|
||||||
|
{ t.IsDummyThread() } -> Common::ConvertibleTo<bool>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority>
|
template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority>
|
||||||
|
@ -349,24 +350,49 @@ public:
|
||||||
|
|
||||||
// Mutators.
|
// Mutators.
|
||||||
constexpr void PushBack(Member* member) {
|
constexpr void PushBack(Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->PushBack(member->GetPriority(), member);
|
this->PushBack(member->GetPriority(), member);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void Remove(Member* member) {
|
constexpr void Remove(Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->Remove(member->GetPriority(), member);
|
this->Remove(member->GetPriority(), member);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void MoveToScheduledFront(Member* member) {
|
constexpr void MoveToScheduledFront(Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
|
this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr KThread* MoveToScheduledBack(Member* member) {
|
constexpr KThread* MoveToScheduledBack(Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
|
return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
|
||||||
member);
|
member);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First class fancy operations.
|
// First class fancy operations.
|
||||||
constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
|
constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(IsValidPriority(prev_priority));
|
ASSERT(IsValidPriority(prev_priority));
|
||||||
|
|
||||||
// Remove the member from the queues.
|
// Remove the member from the queues.
|
||||||
|
@ -383,6 +409,11 @@ public:
|
||||||
|
|
||||||
constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
|
constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
|
||||||
Member* member) {
|
Member* member) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the new information.
|
// Get the new information.
|
||||||
const s32 priority = member->GetPriority();
|
const s32 priority = member->GetPriority();
|
||||||
const AffinityMaskType& new_affinity = member->GetAffinityMask();
|
const AffinityMaskType& new_affinity = member->GetAffinityMask();
|
||||||
|
@ -412,6 +443,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
|
constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
|
||||||
|
// This is for host (dummy) threads that we do not want to enter the priority queue.
|
||||||
|
if (member->IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the new information.
|
// Get the new information.
|
||||||
const s32 new_core = member->GetActiveCore();
|
const s32 new_core = member->GetActiveCore();
|
||||||
const s32 priority = member->GetPriority();
|
const s32 priority = member->GetPriority();
|
||||||
|
|
|
@ -406,6 +406,9 @@ void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduli
|
||||||
} else {
|
} else {
|
||||||
RescheduleCores(kernel, cores_needing_scheduling);
|
RescheduleCores(kernel, cores_needing_scheduling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case to ensure dummy threads that are waiting block.
|
||||||
|
current_thread->IfDummyThreadTryWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
||||||
|
|
|
@ -1075,12 +1075,46 @@ ResultCode KThread::Sleep(s64 timeout) {
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KThread::IfDummyThreadTryWait() {
|
||||||
|
if (!IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetState() != ThreadState::Waiting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until we can grab the lock.
|
||||||
|
KScopedSpinLock lk{dummy_wait_lock};
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThread::IfDummyThreadBeginWait() {
|
||||||
|
if (!IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the thread will block when IfDummyThreadTryWait is called.
|
||||||
|
dummy_wait_lock.Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThread::IfDummyThreadEndWait() {
|
||||||
|
if (!IsDummyThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the thread will no longer block.
|
||||||
|
dummy_wait_lock.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
void KThread::BeginWait(KThreadQueue* queue) {
|
void KThread::BeginWait(KThreadQueue* queue) {
|
||||||
// Set our state as waiting.
|
// Set our state as waiting.
|
||||||
SetState(ThreadState::Waiting);
|
SetState(ThreadState::Waiting);
|
||||||
|
|
||||||
// Set our wait queue.
|
// Set our wait queue.
|
||||||
wait_queue = queue;
|
wait_queue = queue;
|
||||||
|
|
||||||
|
// Special case for dummy threads to ensure they block.
|
||||||
|
IfDummyThreadBeginWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
|
void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
|
||||||
|
@ -1106,6 +1140,9 @@ void KThread::EndWait(ResultCode wait_result_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_queue->EndWait(this, wait_result_);
|
wait_queue->EndWait(this, wait_result_);
|
||||||
|
|
||||||
|
// Special case for dummy threads to wakeup if necessary.
|
||||||
|
IfDummyThreadEndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -558,6 +558,10 @@ public:
|
||||||
return thread_type;
|
return thread_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsDummyThread() const {
|
||||||
|
return GetThreadType() == ThreadType::Dummy;
|
||||||
|
}
|
||||||
|
|
||||||
void SetWaitObjectsForDebugging(const std::span<KSynchronizationObject*>& objects) {
|
void SetWaitObjectsForDebugging(const std::span<KSynchronizationObject*>& objects) {
|
||||||
wait_objects_for_debugging.clear();
|
wait_objects_for_debugging.clear();
|
||||||
wait_objects_for_debugging.reserve(objects.size());
|
wait_objects_for_debugging.reserve(objects.size());
|
||||||
|
@ -632,6 +636,14 @@ public:
|
||||||
return condvar_key;
|
return condvar_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dummy threads (used for HLE host threads) cannot wait based on the guest scheduler, and
|
||||||
|
// therefore will not block on guest kernel synchronization primitives. These methods handle
|
||||||
|
// blocking as needed.
|
||||||
|
|
||||||
|
void IfDummyThreadTryWait();
|
||||||
|
void IfDummyThreadBeginWait();
|
||||||
|
void IfDummyThreadEndWait();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t PriorityInheritanceCountMax = 10;
|
static constexpr size_t PriorityInheritanceCountMax = 10;
|
||||||
union SyncObjectBuffer {
|
union SyncObjectBuffer {
|
||||||
|
@ -750,6 +762,7 @@ private:
|
||||||
bool resource_limit_release_hint{};
|
bool resource_limit_release_hint{};
|
||||||
StackParameters stack_parameters{};
|
StackParameters stack_parameters{};
|
||||||
KSpinLock context_guard{};
|
KSpinLock context_guard{};
|
||||||
|
KSpinLock dummy_wait_lock{};
|
||||||
|
|
||||||
// For emulation
|
// For emulation
|
||||||
std::shared_ptr<Common::Fiber> host_context{};
|
std::shared_ptr<Common::Fiber> host_context{};
|
||||||
|
|
Loading…
Reference in a new issue