Schedule interactive threads fairly under load.

Preempted threads are now removed from the runnable list until every
other thread has been preempted or the system goes idle. This ensures
threads that roundtrip to other threads get a full chance to perform
their work cooperatively without being starved by CPU intensive threads
whenever they yield.
This commit is contained in:
Jonas 'Sortie' Termansen 2024-01-14 21:51:31 +01:00
parent 08d03b2190
commit dc86ee0f7c
1 changed files with 54 additions and 6 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2015, 2021-2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2015, 2021-2022, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* scheduler.cpp
* Decides the order to execute threads in and switching between them.
* Decides the order to execute threads in and switches between them.
*/
#include <sys/types.h>
@ -267,6 +267,7 @@ static void SwitchRegisters(struct interrupt_context* intctx,
static Thread* idle_thread;
static Thread* first_runnable_thread;
static Thread* first_preempted_thread;
static Thread* true_current_thread;
static Process* init_process;
@ -323,15 +324,23 @@ static Thread* PopNextThread(bool yielded)
return result;
}
// Switch to the next runnable thread that hasn't been preempted.
if ( first_runnable_thread )
{
result = first_runnable_thread;
first_runnable_thread = first_runnable_thread->scheduler_list_next;
}
else
// If all threads have been preempted, allow them all to run again.
else if ( first_preempted_thread )
{
result = idle_thread;
first_runnable_thread = first_preempted_thread;
first_preempted_thread = NULL;
result = first_runnable_thread;
first_runnable_thread = first_runnable_thread->scheduler_list_next;
}
// Otherwise there is nothing to run.
else
result = idle_thread;
return result;
}
@ -359,6 +368,37 @@ static void RealSwitch(struct interrupt_context* intctx, bool yielded)
void Switch(struct interrupt_context* intctx)
{
Thread* thread = CurrentThread();
if ( thread->state == ThreadState::RUNNABLE )
{
// Remove the thread from the list of runnable threads.
if ( thread == first_runnable_thread )
{
first_runnable_thread = thread->scheduler_list_next;
if ( thread == first_runnable_thread )
first_runnable_thread = NULL;
}
else if ( thread == first_preempted_thread )
{
first_preempted_thread = thread->scheduler_list_next;
if ( thread == first_preempted_thread )
first_preempted_thread = NULL;
}
assert(thread->scheduler_list_prev);
assert(thread->scheduler_list_next);
thread->scheduler_list_prev->scheduler_list_next = thread->scheduler_list_next;
thread->scheduler_list_next->scheduler_list_prev = thread->scheduler_list_prev;
thread->scheduler_list_prev = NULL;
thread->scheduler_list_next = NULL;
// Insert the thread into the list of preempted threads.
if ( first_preempted_thread == NULL )
first_preempted_thread = thread;
thread->scheduler_list_prev = first_preempted_thread->scheduler_list_prev;
thread->scheduler_list_next = first_preempted_thread;
first_preempted_thread->scheduler_list_prev = thread;
thread->scheduler_list_prev->scheduler_list_next = thread;
}
RealSwitch(intctx, false);
}
@ -437,9 +477,17 @@ void SetThreadState(Thread* thread, ThreadState state, bool wake_only)
state != ThreadState::RUNNABLE )
{
if ( thread == first_runnable_thread )
{
first_runnable_thread = thread->scheduler_list_next;
if ( thread == first_runnable_thread )
first_runnable_thread = NULL;
if ( thread == first_runnable_thread )
first_runnable_thread = NULL;
}
else if ( thread == first_preempted_thread )
{
first_preempted_thread = thread->scheduler_list_next;
if ( thread == first_preempted_thread )
first_preempted_thread = NULL;
}
assert(thread->scheduler_list_prev);
assert(thread->scheduler_list_next);
thread->scheduler_list_prev->scheduler_list_next = thread->scheduler_list_next;