mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-11 10:18:15 +09:00
Kernel: Refactor scheduler to use dynamic thread priorities
Threads now have numeric priorities with a base priority in the 1-99 range. Whenever a runnable thread is *not* scheduled, its effective priority is incremented by 1. This is tracked in Thread::m_extra_priority. The effective priority of a thread is m_priority + m_extra_priority. When a runnable thread *is* scheduled, its m_extra_priority is reset to zero and the effective priority returns to base. This means that lower-priority threads will always eventually get scheduled to run, once its effective priority becomes high enough to exceed the base priority of threads "above" it. The previous values for ThreadPriority (Low, Normal and High) are now replaced as follows: Low -> 10 Normal -> 30 High -> 50 In other words, it will take 20 ticks for a "Low" priority thread to get to "Normal" effective priority, and another 20 to reach "High". This is not perfect, and I've used some quite naive data structures, but I think the mechanism will allow us to build various new and interesting optimizations, and we can figure out better data structures later on. :^)
This commit is contained in:
parent
816d3e6208
commit
50677bf806
Notes:
sideshowbarker
2024-07-19 10:31:54 +09:00
Author: https://github.com/awesomekling
Commit: 50677bf806
16 changed files with 109 additions and 149 deletions
|
@ -1,3 +1,4 @@
|
|||
#include <AK/QuickSort.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <Kernel/Arch/i386/PIT.h>
|
||||
#include <Kernel/FileSystem/FileDescription.h>
|
||||
|
@ -21,7 +22,7 @@ void Scheduler::init_thread(Thread& thread)
|
|||
void Scheduler::update_state_for_thread(Thread& thread)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
auto& list = g_scheduler_data->thread_list_for_state_and_priority(thread.state(), thread.priority());
|
||||
auto& list = g_scheduler_data->thread_list_for_state(thread.state());
|
||||
|
||||
if (list.contains(thread))
|
||||
return;
|
||||
|
@ -29,35 +30,12 @@ void Scheduler::update_state_for_thread(Thread& thread)
|
|||
list.append(thread);
|
||||
}
|
||||
|
||||
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)
|
||||
static u32 time_slice_for(const Thread& thread)
|
||||
{
|
||||
// One time slice unit == 1ms
|
||||
switch (priority) {
|
||||
case ThreadPriority::High:
|
||||
return 20;
|
||||
case ThreadPriority::Normal:
|
||||
return 15;
|
||||
case ThreadPriority::Low:
|
||||
return 5;
|
||||
case ThreadPriority::Idle:
|
||||
if (&thread == g_colonel)
|
||||
return 1;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
return 10;
|
||||
}
|
||||
|
||||
Thread* current;
|
||||
|
@ -364,46 +342,54 @@ bool Scheduler::pick_next()
|
|||
#ifdef SCHEDULER_RUNNABLE_DEBUG
|
||||
dbgprintf("Non-runnables:\n");
|
||||
Scheduler::for_each_nonrunnable([](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);
|
||||
dbgprintf(" %-12s %s(%u:%u) @ %w:%x\n", thread.state_string(), thread.name().characters(), thread.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;
|
||||
});
|
||||
}
|
||||
dbgprintf("Runnables:\n");
|
||||
Scheduler::for_each_runnable([](Thread& thread) -> IterationDecision {
|
||||
dbgprintf(" %3u/%2u %-12s %s(%u:%u) @ %w:%x\n", thread.effective_priority(), thread.priority(), thread.state_string(), thread.name().characters(), thread.pid(), thread.tid(), thread.tss().cs, thread.tss().eip);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
#endif
|
||||
|
||||
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())
|
||||
Vector<Thread*, 128> sorted_runnables;
|
||||
for_each_runnable([&sorted_runnables](auto& thread) {
|
||||
sorted_runnables.append(&thread);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
quick_sort(sorted_runnables.begin(), sorted_runnables.end(), [](auto& a, auto& b) { return a->effective_priority() >= b->effective_priority(); });
|
||||
|
||||
Thread* thread_to_schedule = nullptr;
|
||||
|
||||
for (auto* thread : sorted_runnables) {
|
||||
if (thread->process().is_being_inspected())
|
||||
continue;
|
||||
|
||||
auto* previous_head = runnable_list.first();
|
||||
for (;;) {
|
||||
// Move head to tail.
|
||||
runnable_list.append(*runnable_list.first());
|
||||
auto* thread = runnable_list.first();
|
||||
ASSERT(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);
|
||||
#endif
|
||||
return context_switch(*thread);
|
||||
}
|
||||
|
||||
if (thread == previous_head)
|
||||
break;
|
||||
if (!thread_to_schedule) {
|
||||
thread->m_extra_priority = 0;
|
||||
thread_to_schedule = thread;
|
||||
} else {
|
||||
thread->m_extra_priority++;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing wants to run. Send in the colonel!
|
||||
return context_switch(*g_colonel);
|
||||
|
||||
if (!thread_to_schedule)
|
||||
thread_to_schedule = g_colonel;
|
||||
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
dbgprintf("switch to %s(%u:%u) @ %w:%x\n",
|
||||
thread_to_schedule->name().characters(),
|
||||
thread_to_schedule->pid(),
|
||||
thread_to_schedule->tid(),
|
||||
thread_to_schedule->tss().cs,
|
||||
thread_to_schedule->tss().eip);
|
||||
#endif
|
||||
|
||||
return context_switch(*thread_to_schedule);
|
||||
}
|
||||
|
||||
bool Scheduler::donate_to(Thread* beneficiary, const char* reason)
|
||||
|
@ -417,7 +403,7 @@ bool Scheduler::donate_to(Thread* beneficiary, const char* reason)
|
|||
if (!beneficiary || beneficiary->state() != Thread::Runnable || ticks_left <= 1)
|
||||
return yield();
|
||||
|
||||
unsigned ticks_to_donate = min(ticks_left - 1, time_slice_for(beneficiary->priority()));
|
||||
unsigned ticks_to_donate = min(ticks_left - 1, time_slice_for(*beneficiary));
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
dbgprintf("%s(%u:%u) donating %u ticks to %s(%u:%u), reason=%s\n", current->process().name().characters(), current->pid(), current->tid(), ticks_to_donate, beneficiary->process().name().characters(), beneficiary->pid(), beneficiary->tid(), reason);
|
||||
#endif
|
||||
|
@ -459,7 +445,7 @@ void Scheduler::switch_now()
|
|||
|
||||
bool Scheduler::context_switch(Thread& thread)
|
||||
{
|
||||
thread.set_ticks_left(time_slice_for(thread.priority()));
|
||||
thread.set_ticks_left(time_slice_for(thread));
|
||||
thread.did_schedule();
|
||||
|
||||
if (current == &thread)
|
||||
|
@ -472,10 +458,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) [%s] %w:%x\n",
|
||||
dbgprintf("Scheduler: %s(%u:%u) -> %s(%u:%u) [%u] %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.priority(),
|
||||
thread.tss().cs, thread.tss().eip);
|
||||
#endif
|
||||
}
|
||||
|
@ -552,8 +538,7 @@ void Scheduler::initialize()
|
|||
s_redirection.selector = gdt_alloc_entry();
|
||||
initialize_redirection();
|
||||
s_colonel_process = Process::create_kernel_process(g_colonel, "colonel", nullptr);
|
||||
// Make sure the colonel uses a smallish time slice.
|
||||
g_colonel->set_priority(ThreadPriority::Idle);
|
||||
g_colonel->set_priority(THREAD_PRIORITY_MIN);
|
||||
load_task_register(s_redirection.selector);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue