Kernel: Separate runnable thread queues by priority

This patch introduces three separate thread queues, one for each thread
priority available to userspace (Low, Normal and High.)

Each queue operates in a round-robin fashion, but we now always prefer
to schedule the highest priority thread that currently wants to run.

There are tons of tweaks and improvements that we can and should make
to this mechanism, but I think this is a step in the right direction.

This makes WindowServer significantly more responsive while one of its
clients is burning CPU. :^)
This commit is contained in:
Andreas Kling 2019-12-27 00:46:38 +01:00
parent 23e16a3e2e
commit abdd5aa08a
Notes: sideshowbarker 2024-07-19 10:38:24 +09:00
2 changed files with 62 additions and 37 deletions

View file

@ -6,6 +6,10 @@
#include <Kernel/RTC.h>
#include <Kernel/Scheduler.h>
//#define LOG_EVERY_CONTEXT_SWITCH
//#define SCHEDULER_DEBUG
//#define SCHEDULER_RUNNABLE_DEBUG
SchedulerData* g_scheduler_data;
void Scheduler::init_thread(Thread& thread)
@ -15,7 +19,8 @@ void Scheduler::init_thread(Thread& thread)
void Scheduler::update_state_for_thread(Thread& thread)
{
auto& list = g_scheduler_data->thread_list_for_state(thread.state());
ASSERT_INTERRUPTS_DISABLED();
auto& list = g_scheduler_data->thread_list_for_state_and_priority(thread.state(), thread.priority());
if (list.contains(thread))
return;
@ -23,16 +28,27 @@ void Scheduler::update_state_for_thread(Thread& thread)
list.append(thread);
}
//#define LOG_EVERY_CONTEXT_SWITCH
//#define SCHEDULER_DEBUG
//#define SCHEDULER_RUNNABLE_DEBUG
template<typename Callback>
static inline IterationDecision for_each_runnable_with_priority(ThreadPriority priority, Callback callback)
{
ASSERT_INTERRUPTS_DISABLED();
auto& tl = g_scheduler_data->m_runnable_threads[(u8)priority - (u8)ThreadPriority::First];
for (auto it = tl.begin(); it != tl.end();) {
auto& thread = *it;
it = ++it;
if (callback(thread) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}
static u32 time_slice_for(ThreadPriority priority)
{
// One time slice unit == 1ms
switch (priority) {
case ThreadPriority::High:
return 50;
return 20;
case ThreadPriority::Normal:
return 15;
case ThreadPriority::Low:
@ -352,36 +368,41 @@ bool Scheduler::pick_next()
return IterationDecision::Continue;
});
dbgprintf("Runnables:\n");
Scheduler::for_each_runnable([](Thread& thread) -> IterationDecision {
auto& process = thread.process();
dbgprintf("[K%x] %-12s %s(%u:%u) @ %w:%x\n", &process, thread.state_string(), process.name().characters(), process.pid(), thread.tid(), thread.tss().cs, thread.tss().eip);
return IterationDecision::Continue;
});
for (u8 priority = (u8)ThreadPriority::Last; priority >= (u8)ThreadPriority::First; --priority) {
dbgprintf("Runnables (%s):\n", to_string((ThreadPriority)priority));
for_each_runnable_with_priority((ThreadPriority)priority, [](Thread& thread) -> IterationDecision {
auto& process = thread.process();
dbgprintf("[K%x] %-12s %s(%u:%u) @ %w:%x\n", &process, thread.state_string(), process.name().characters(), process.pid(), thread.tid(), thread.tss().cs, thread.tss().eip);
return IterationDecision::Continue;
});
}
#endif
auto& runnable_list = g_scheduler_data->m_runnable_threads;
if (runnable_list.is_empty())
return context_switch(*g_colonel);
for (u8 priority = (u8)ThreadPriority::Last; priority >= (u8)ThreadPriority::First; --priority) {
auto& runnable_list = g_scheduler_data->m_runnable_threads[priority - (u8)ThreadPriority::First];
if (runnable_list.is_empty())
continue;
auto* previous_head = runnable_list.first();
for (;;) {
// Move head to tail.
runnable_list.append(*runnable_list.first());
auto* thread = runnable_list.first();
auto* previous_head = runnable_list.first();
for (;;) {
// Move head to tail.
runnable_list.append(*runnable_list.first());
auto* thread = runnable_list.first();
if (!thread->process().is_being_inspected() && (thread->state() == Thread::Runnable || thread->state() == Thread::Running)) {
if (!thread->process().is_being_inspected() && (thread->state() == Thread::Runnable || thread->state() == Thread::Running)) {
#ifdef SCHEDULER_DEBUG
dbgprintf("switch to %s(%u:%u) @ %w:%x\n", thread->process().name().characters(), thread->process().pid(), thread->tid(), thread->tss().cs, thread->tss().eip);
dbgprintf("switch to %s(%u:%u) @ %w:%x\n", thread->process().name().characters(), thread->process().pid(), thread->tid(), thread->tss().cs, thread->tss().eip);
#endif
return context_switch(*thread);
}
return context_switch(*thread);
}
if (thread == previous_head) {
// Back at process_head, nothing wants to run. Send in the colonel!
return context_switch(*g_colonel);
if (thread == previous_head)
break;
}
}
// Nothing wants to run. Send in the colonel!
return context_switch(*g_colonel);
}
bool Scheduler::donate_to(Thread* beneficiary, const char* reason)
@ -450,9 +471,10 @@ bool Scheduler::context_switch(Thread& thread)
current->set_state(Thread::Runnable);
#ifdef LOG_EVERY_CONTEXT_SWITCH
dbgprintf("Scheduler: %s(%u:%u) -> %s(%u:%u) %w:%x\n",
dbgprintf("Scheduler: %s(%u:%u) -> %s(%u:%u) [%s] %w:%x\n",
current->process().name().characters(), current->process().pid(), current->tid(),
thread.process().name().characters(), thread.process().pid(), thread.tid(),
to_string(thread.priority()),
thread.tss().cs, thread.tss().eip);
#endif
}

View file

@ -40,7 +40,7 @@ enum class ThreadPriority : u8 {
Low,
Normal,
High,
First = Idle,
First = Low,
Last = High,
};
@ -501,13 +501,15 @@ const LogStream& operator<<(const LogStream&, const Thread&);
struct SchedulerData {
typedef IntrusiveList<Thread, &Thread::m_runnable_list_node> ThreadList;
ThreadList m_runnable_threads;
static constexpr size_t num_thread_priorities = (size_t)ThreadPriority::Last - (size_t)ThreadPriority::First + 1;
ThreadList m_runnable_threads[num_thread_priorities];
ThreadList m_nonrunnable_threads;
ThreadList& thread_list_for_state(Thread::State state)
ThreadList& thread_list_for_state_and_priority(Thread::State state, ThreadPriority priority)
{
if (Thread::is_runnable_state(state))
return m_runnable_threads;
return m_runnable_threads[(u8)priority - (u8)ThreadPriority::First];
return m_nonrunnable_threads;
}
};
@ -516,12 +518,13 @@ template<typename Callback>
inline IterationDecision Scheduler::for_each_runnable(Callback callback)
{
ASSERT_INTERRUPTS_DISABLED();
auto& tl = g_scheduler_data->m_runnable_threads;
for (auto it = tl.begin(); it != tl.end();) {
auto& thread = *it;
it = ++it;
if (callback(thread) == IterationDecision::Break)
return IterationDecision::Break;
for (auto& tl : g_scheduler_data->m_runnable_threads) {
for (auto it = tl.begin(); it != tl.end();) {
auto& thread = *it;
it = ++it;
if (callback(thread) == IterationDecision::Break)
return IterationDecision::Break;
}
}
return IterationDecision::Continue;