Skip to content

Commit

Permalink
Kernel: Refactor scheduler to use dynamic thread priorities
Browse files Browse the repository at this point in the history
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. :^)
  • Loading branch information
awesomekling committed Dec 30, 2019
1 parent 816d3e6 commit 50677bf
Show file tree
Hide file tree
Showing 16 changed files with 109 additions and 149 deletions.
30 changes: 11 additions & 19 deletions Applications/SystemMonitor/ProcessModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ String ProcessModel::column_name(int column) const
return "User";
case Column::Priority:
return "Pr";
case Column::EffectivePriority:
return "EPr";
case Column::Virtual:
return "Virtual";
case Column::Physical:
Expand Down Expand Up @@ -108,7 +110,9 @@ GModel::ColumnMetadata ProcessModel::column_metadata(int column) const
case Column::State:
return { 75, TextAlignment::CenterLeft };
case Column::Priority:
return { 16, TextAlignment::CenterLeft };
return { 16, TextAlignment::CenterRight };
case Column::EffectivePriority:
return { 16, TextAlignment::CenterRight };
case Column::User:
return { 50, TextAlignment::CenterLeft };
case Column::Virtual:
Expand Down Expand Up @@ -177,16 +181,9 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const
case Column::User:
return thread.current_state.user;
case Column::Priority:
if (thread.current_state.priority == "Idle")
return 0;
if (thread.current_state.priority == "Low")
return 1;
if (thread.current_state.priority == "Normal")
return 2;
if (thread.current_state.priority == "High")
return 3;
ASSERT_NOT_REACHED();
return 3;
return thread.current_state.priority;
case Column::EffectivePriority:
return thread.current_state.effective_priority;
case Column::Virtual:
return (int)thread.current_state.amount_virtual;
case Column::Physical:
Expand Down Expand Up @@ -249,15 +246,9 @@ GVariant ProcessModel::data(const GModelIndex& index, Role role) const
case Column::User:
return thread.current_state.user;
case Column::Priority:
if (thread.current_state.priority == "Idle")
return String::empty();
if (thread.current_state.priority == "High")
return *m_high_priority_icon;
if (thread.current_state.priority == "Low")
return *m_low_priority_icon;
if (thread.current_state.priority == "Normal")
return *m_normal_priority_icon;
return thread.current_state.priority;
case Column::EffectivePriority:
return thread.current_state.effective_priority;
case Column::Virtual:
return pretty_byte_size(thread.current_state.amount_virtual);
case Column::Physical:
Expand Down Expand Up @@ -338,6 +329,7 @@ void ProcessModel::update()
state.tid = thread.tid;
state.times_scheduled = thread.times_scheduled;
state.priority = thread.priority;
state.effective_priority = thread.effective_priority;
state.state = thread.state;
sum_times_scheduled += thread.times_scheduled;
{
Expand Down
4 changes: 3 additions & 1 deletion Applications/SystemMonitor/ProcessModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ProcessModel final : public GModel {
CPU,
State,
Priority,
EffectivePriority,
User,
PID,
TID,
Expand Down Expand Up @@ -71,7 +72,8 @@ class ProcessModel final : public GModel {
String name;
String state;
String user;
String priority;
u32 priority;
u32 effective_priority;
size_t amount_virtual;
size_t amount_resident;
size_t amount_dirty_private;
Expand Down
3 changes: 2 additions & 1 deletion Kernel/FileSystem/ProcFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,8 @@ Optional<KBuffer> procfs$all(InodeIdentifier)
thread_object.add("times_scheduled", thread.times_scheduled());
thread_object.add("ticks", thread.ticks());
thread_object.add("state", thread.state_string());
thread_object.add("priority", to_string(thread.priority()));
thread_object.add("priority", thread.priority());
thread_object.add("effective_priority", thread.effective_priority());
thread_object.add("syscall_count", thread.syscall_count());
thread_object.add("inode_faults", thread.inode_faults());
thread_object.add("zero_faults", thread.zero_faults());
Expand Down
11 changes: 4 additions & 7 deletions Kernel/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2831,10 +2831,10 @@ int Process::sys$sched_setparam(pid_t pid, const struct sched_param* param)
if (!is_superuser() && m_euid != peer->m_uid && m_uid != peer->m_uid)
return -EPERM;

if (param->sched_priority < (int)ThreadPriority::First || param->sched_priority > (int)ThreadPriority::Last)
if (param->sched_priority < THREAD_PRIORITY_MIN || param->sched_priority > THREAD_PRIORITY_MAX)
return -EINVAL;

peer->any_thread().set_priority((ThreadPriority)param->sched_priority);
peer->any_thread().set_priority((u32)param->sched_priority);
return 0;
}

Expand Down Expand Up @@ -3078,13 +3078,10 @@ int Process::sys$create_thread(void* (*entry)(void*), void* argument, const Sysc

// FIXME: return EAGAIN if Thread::all_threads().size() is greater than PTHREAD_THREADS_MAX

ThreadPriority requested_thread_priority = static_cast<ThreadPriority>(params->m_schedule_priority);
if (requested_thread_priority < ThreadPriority::First || requested_thread_priority > ThreadPriority::Last)
int requested_thread_priority = params->m_schedule_priority;
if (requested_thread_priority < THREAD_PRIORITY_MIN || requested_thread_priority > THREAD_PRIORITY_MAX)
return -EINVAL;

if (requested_thread_priority != ThreadPriority::Normal && !is_superuser())
return -EPERM;

bool is_thread_joinable = (0 == params->m_detach_state);

// FIXME: Do something with guard pages?
Expand Down
2 changes: 0 additions & 2 deletions Kernel/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,6 @@ class ProcessInspectionHandle {
Process& m_process;
};

const char* to_string(ThreadPriority);

extern InlineLinkedList<Process>* g_processes;

template<typename Callback>
Expand Down
109 changes: 47 additions & 62 deletions Kernel/Scheduler.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <AK/QuickSort.h>
#include <AK/TemporaryChange.h>
#include <Kernel/Arch/i386/PIT.h>
#include <Kernel/FileSystem/FileDescription.h>
Expand All @@ -21,43 +22,20 @@ 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;

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;
Expand Down Expand Up @@ -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())
continue;
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(); });

auto* previous_head = runnable_list.first();
for (;;) {
// Move head to tail.
runnable_list.append(*runnable_list.first());
auto* thread = runnable_list.first();
Thread* thread_to_schedule = nullptr;

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);
}
for (auto* thread : sorted_runnables) {
if (thread->process().is_being_inspected())
continue;

if (thread == previous_head)
break;
ASSERT(thread->state() == Thread::Runnable || thread->state() == Thread::Running);

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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion Kernel/Syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ struct SC_futex_params {

struct SC_create_thread_params {
unsigned int m_detach_state = 0; // JOINABLE or DETACHED
int m_schedule_priority = 2; // ThreadPriority::Normal
int m_schedule_priority = 30; // THREAD_PRIORITY_NORMAL
// FIXME: Implment guard pages in create_thread (unreadable pages at "overflow" end of stack)
// "If an implementation rounds up the value of guardsize to a multiple of {PAGESIZE},
// a call to pthread_attr_getguardsize() specifying attr shall store in the guardsize
Expand Down
17 changes: 0 additions & 17 deletions Kernel/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,23 +765,6 @@ const LogStream& operator<<(const LogStream& stream, const Thread& value)
return stream << value.process().name() << "(" << value.pid() << ":" << value.tid() << ")";
}

const char* to_string(ThreadPriority priority)
{
switch (priority) {
case ThreadPriority::Idle:
return "Idle";
case ThreadPriority::Low:
return "Low";
case ThreadPriority::Normal:
return "Normal";
case ThreadPriority::High:
return "High";
}
dbg() << "to_string(ThreadPriority): Invalid priority: " << (u32)priority;
ASSERT_NOT_REACHED();
return nullptr;
}

void Thread::wait_on(WaitQueue& queue, Thread* beneficiary, const char* reason)
{
bool did_unlock = unlock_process_if_locked();
Expand Down
Loading

0 comments on commit 50677bf

Please sign in to comment.