Split 335 2/7 Fix/kernel scheduling races (#329)

This commit is contained in:
Bruno 2026-10-03 13:28:27 +02:00 committed by GitHub
parent b9c233adef
commit 4e3a9cbbdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 32 deletions

View File

@ -209,7 +209,7 @@ void KScheduler::OnThreadStart()
u64 KScheduler::UpdateHighestPriorityThread(KThread * highest_thread)
{
if (KThread * prev_highest_thread = m_state.highest_priority_thread;
if (KThread * prev_highest_thread = m_state.highest_priority_thread.load(std::memory_order_acquire);
prev_highest_thread != highest_thread) [[likely]]
{
if (prev_highest_thread != nullptr) [[likely]]
@ -233,7 +233,7 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread * highest_thread)
}
}
m_state.highest_priority_thread = highest_thread;
m_state.highest_priority_thread.store(highest_thread, std::memory_order_release);
m_state.needs_scheduling = true;
return (1ULL << m_core_id);
}
@ -458,7 +458,7 @@ void KScheduler::ScheduleImpl()
// Load the appropriate thread pointers for scheduling.
KThread * const cur_thread{GetCurrentThreadPointer(m_kernel)};
KThread * highest_priority_thread{m_state.highest_priority_thread};
KThread * highest_priority_thread{m_state.highest_priority_thread.load(std::memory_order_acquire)};
// Check whether there are runnable interrupt tasks.
if (m_state.interrupt_task_runnable)
@ -562,7 +562,7 @@ void KScheduler::ScheduleImplFiber()
std::atomic_thread_fence(std::memory_order_seq_cst);
// Refresh the highest priority thread.
highest_priority_thread = m_state.highest_priority_thread;
highest_priority_thread = m_state.highest_priority_thread.load(std::memory_order_acquire);
}
// Reload the guest thread context.
@ -794,12 +794,6 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore & kernel)
KThread & cur_thread = GetCurrentThread(kernel);
KProcess & cur_process = GetCurrentProcess(kernel);
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
// Get a reference to the priority queue.
auto & priority_queue = GetPriorityQueue(kernel);
@ -807,6 +801,12 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore & kernel)
{
KScopedSchedulerLock sl{kernel};
// The process count is shared by all cores and changes under this lock.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable)
{
@ -839,12 +839,6 @@ void KScheduler::YieldWithCoreMigration(KernelCore & kernel)
KThread & cur_thread = GetCurrentThread(kernel);
KProcess & cur_process = GetCurrentProcess(kernel);
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
// Get a reference to the priority queue.
auto & priority_queue = GetPriorityQueue(kernel);
@ -852,6 +846,12 @@ void KScheduler::YieldWithCoreMigration(KernelCore & kernel)
{
KScopedSchedulerLock sl{kernel};
// The process count is shared by all cores and changes under this lock.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable)
{
@ -872,7 +872,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore & kernel)
if (KThread * running_on_suggested_core =
(suggested_core >= 0)
? kernel.Scheduler(suggested_core).m_state.highest_priority_thread
? kernel.Scheduler(suggested_core).m_state.highest_priority_thread.load(std::memory_order_acquire)
: nullptr;
running_on_suggested_core != suggested)
{
@ -939,12 +939,6 @@ void KScheduler::YieldToAnyThread(KernelCore & kernel)
KThread & cur_thread = GetCurrentThread(kernel);
KProcess & cur_process = GetCurrentProcess(kernel);
// If the thread's yield count matches, there's nothing for us to do.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
// Get a reference to the priority queue.
auto & priority_queue = GetPriorityQueue(kernel);
@ -952,6 +946,12 @@ void KScheduler::YieldToAnyThread(KernelCore & kernel)
{
KScopedSchedulerLock sl{kernel};
// The process count is shared by all cores and changes under this lock.
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount())
{
return;
}
const auto cur_state = cur_thread.GetRawState();
if (cur_state == ThreadState::Runnable)
{

View File

@ -143,7 +143,7 @@ private:
bool interrupt_task_runnable{false};
bool should_count_idle{false};
u64 idle_count{0};
KThread* highest_priority_thread{nullptr};
std::atomic<KThread*> highest_priority_thread{nullptr};
void* idle_thread_stack{nullptr};
std::atomic<KThread*> prev_thread{nullptr};
KInterruptTaskManager* interrupt_task_manager{nullptr};

View File

@ -690,11 +690,19 @@ Result KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) {
// Check if the thread is currently running.
bool thread_is_current{};
s32 thread_core;
for (thread_core = 0; thread_core < static_cast<s32>(Hardware::NUM_CPU_CORES);
++thread_core) {
if (m_kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) {
thread_is_current = true;
break;
if (!m_kernel.IsMulticore() && m_kernel.CurrentScheduler() != nullptr &&
this == GetCurrentThreadPointer(m_kernel)) {
// In SC, inactive schedulers may still reference the caller after preemption.
// Only its executing core matters; it already owns its context lock there.
thread_core = static_cast<s32>(m_kernel.CurrentPhysicalCoreIndex());
thread_is_current = true;
} else {
for (thread_core = 0; thread_core < static_cast<s32>(Hardware::NUM_CPU_CORES);
++thread_core) {
if (m_kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) {
thread_is_current = true;
break;
}
}
}
@ -710,6 +718,13 @@ Result KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) {
// Wait until the thread isn't pinned any more.
m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel));
GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue));
} else if (!m_kernel.IsMulticore() && m_kernel.CurrentScheduler() != nullptr &&
thread_core != static_cast<s32>(m_kernel.CurrentPhysicalCoreIndex())) {
// SC preemption saves/unlocks a thread's context but leaves the virtual
// scheduler's current pointer in place. The inactive virtual core is already
// quiescent; waiting for its scheduler to run would block the sole CPU thread.
std::unique_lock context_lock{m_context_guard, std::try_to_lock};
retry_update = !context_lock.owns_lock();
} else {
// If the thread isn't pinned, release the scheduler lock and retry until it's
// not current.

View File

@ -238,12 +238,14 @@ void PhysicalCore::LogBacktrace()
void PhysicalCore::Idle()
{
std::unique_lock lk{m_guard};
m_on_interrupt.wait(lk, [this] { return m_is_interrupted; });
m_on_interrupt.wait(lk, [this] {
return m_is_interrupted.load(std::memory_order_acquire);
});
}
bool PhysicalCore::IsInterrupted() const
{
return m_is_interrupted;
return m_is_interrupted.load(std::memory_order_acquire);
}
void PhysicalCore::Interrupt()

View File

@ -3,6 +3,7 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <memory>
@ -74,7 +75,7 @@ private:
std::condition_variable m_on_interrupt;
ICpuCore * m_cpucore{};
KThread * m_current_thread{};
bool m_is_interrupted{};
std::atomic_bool m_is_interrupted{};
bool m_is_single_core{};
};