OS161 provides a simple round-robin scheduler by default. It works like this:
-
hardclock
from$OS161_SRC/kern/thread/clock.c
will be periodically called (from hardware clock interrupt handler) -
Two functions may be called there after:
schedule
to change the order the threads in ready queue, which currently does nothingthread_consider_migraton
to enable thread migration among CPU cores
-
Then it will call
thread_yield
to cause the current thread yield to another thread
We need to play with the schedule
function to give interactive threads higher
priority.
Why give priority to interactive threads?
There are two reasons about this (at least the two in my mind) :
-
Your time is more valuable than computer's. So in general, we should first serve those threads that interact with you. For example, you don't want to wait the computer in a shell while it's busy doing backup, right?
-
Interactive threads tend to be I/O bound, which means they often get stuck waiting for input or output. So they normally fail to consume their granted time slice. Thus we can switch to computation bound threads when they stuck and boost computer utilization.
How can we know whether a thread is interactive or not?
As said above, interactive threads are normally I/O bound. So they often need to sleep a lot.
In $OS161_SRC/kern/thread/thread.c
, we can see that thread_switch
is used to actually
switch between threads. The first argument is newstate
, which give some hints
about the current thread.
If newstate
is S_READY
, it means that current thread has consumed all
its time slice and is forced to yield to another thread (by hardware clock).
So we can guess that it's not interactive, or, it's computation intensive.
However, if newstate
is S_SLEEP
, then it means current thread offers to
yield to another thread, maybe waiting for I/O or a mutex. Thus we can guess
that this thread is more interactive, or, it's I/O intensive.
So by the newstate
, we can make a good guess of current thread.
How to implement it?
Multi-Level Feedback Queue seems to be a good enough algorithm in this case.
We can add a priority field in struct thread
and initiate it as medium
priority in thread_create
. Then in thread_swith
, we can adjust current
thread's priority by the newstate
.
- If it's
S_SLEEP
then we increase current thread's priority. - Otherwise, if it's
S_READY
then we decrease current thread's priority.
Of course, we can only support a finite priority level here, so be careful
with boundary case. For example, if current thread is
already the highest priority and still request S_SLEEP
, then we just leave it
in that priority.
Then in schedule
, we need to find the thread with highest priority
among all the threads in curcpu->c_runqueue
, and bring it to head.
Current CPU's run queue is organized as a double linked list with head
element. $OS161_SRC/kern/include/threadlist.h
provides several useful interface to
let us manipulate the list. Find a maximum/minimum number among a list
is so simple that I won't provide any details here. But note that the
head element is just a place holder. So you may want to start from
curcpu->c_runqueue.tl_head.tln_Next
and stop when elem->tln_next == NULL
.
Once find the thread, we need to bring it to list head so we can
leave thread_switch
unchanged. A threadlist_remove
followed by
threadlist_addhead
will be sufficient here.
One problem of MLFQ is starvation. So you may want to periodically reset all threads' priority to medium level for fairness.
That's all. Here's just a work solution. Much work has be done if you want better scheduling for performance.