Draft Version
Goals
1.
Providing interface to start/terminate task/threads
2. Mechanism for communication between threads/task
This
subsystem should provide some interface/function to create task. Task is
collection of one or more threads. Task can’t run it self. Thread is the core
of execution and it stores the execution snapshot. Each thread has kernel and
user stack.

1.
Running
2.
Ready
3.
Terminating
4.
Waiting

A thread state is in running
state, when it’s running on one of the processors. This state can be reached
only from ready state when the scheduler selects it for running.
A
thread state is in ready state when it’s ready to run and waiting for scheduler
to allot it a processor.
A
thread state is in waiting state when the thread blocks voluntarily. This state
can be reached only from running state.
|
State
Transition |
Ready |
Running |
Waiting |
Terminating |
|
Ready |
NA |
Scheduler
moves the thread to run queue |
NA |
The
task is terminating prematurely, so all the threads are getting terminated. |
|
Running |
Thread
gets preempted because quantum expired |
NA |
Thread
blocks because IO, mutex, sleep etc |
Thread
finished its work. |
|
Waiting |
Kernel
finds the condition on which thread is waiting is signaled so it puts the
thread back into the run queue. |
NA |
NA |
The
task is terminating prematurely, so all the threads are getting terminated. |
|
Terminating |
NA |
NA |
NA |
NA |
State
of a task is the state of that of it’s most active
thread.
If
the process is swapped out, it is in this state.
This
is a transient state when the process is in the context of moving from swap to
memory.
When
at least one of it’s threads are running, process is
in active state.
If
all the threads of a process are in wait state, then the process is in wait
state.
A
process state is in zombie state when one of it’s
thread is terminating and finds that it’s the last thread in the process and
that it’s parent is not waiting for it’s status. It is the responsibility of
the parent to do the last rites (freeing task structure and kernel stack) for it’s dead children. So if the parent has not issued a wait
call, it won’t be able to get the status of it’s child
and hence some valuable cpu resources will go unused. Hence the state of the
process switches to zombie when such a condition arises and a Housekeeping
thread from kernel, periodically flushes all such
threads in zombie state. Note that state of the thread in such a process will
be terminating and will not change to zombie.
Scheduling
Scheduler exposes nothing to other
systems. If exposed, then each system can have its own variant and will result
in an inconsistent system. Also the scheduling activity happens for every clock
tick. Hence it's unreasonable to expose scheduler functionality to other
systems.
Scheduler
is affected only by the PM (process/thread) structures.
Round Robin Policy with Priority Queues is chosen as
scheduler policy.
Scheduler defines the following
fields which should be included in the thread structure. Based on the state of
a thread, it can be either in RunQueue or OtherQueue.
A scheduler sub system has its own
array’s for maintaining the run queues called priority queues. Scheduler uses
this array to select a thread to run.
Every
thread is associated with a scheduler_data structure.
This structure represents the thread inside scheduler and has enough information
like: pointer to thread, priority level, etc..
When a thread is created, this
structure is also created and passed onto scheduler to add into it’s internal database.
1.
AddThreadToSchedulerQueue()
This function adds the given thread to the RunQueue.
Kernel calls this function when it creates a thread and wants to put it in the
run queue or when thread becomes runnable.
2.
RemoveThreadFromSchedulerQueue()
This function removes a thread from the RunQueue.
Kernel calls this function when a thread is blocking or when it is terminating.
3.
SetThreadPriorityInfo()
Sets the given thread’s priority information like class, level etc
4.
GetThreadPriorityInfo()
Gets the given thread’s priority information like class, level etc
5.
Schedule()
Selects and returns a new thread to run. It is the responsibility of the kernel
to run the thread.
Scheduler
maintains an array of priority queues (PQ). The scheduler maintains 5 different
priority classes; inside each priority class there are 5 different priority
levels. So the number of elements in the priority queue array is 5*5 = 25.
·
Very low
·
Low
·
Medium
·
High
·
Very High
·
Very low
·
Low
·
Medium
·
High
·
Very High
Threads are put into one of these
queues and it can be reassigned to any of the queues at any time during it’s lifetime. Threads at level n are preferred over threads
of level n+1.
Scheduler exposes priority classes
to user; priority levels are not exposed. So the administrator can select any
of these 5 different classes of priority. User can decide to give more/less
priority to the thread by selecting suitable classes.
By default a user process inherits
the priority of it’s parent and the threads of the
same process can have different priorities. This is useful in situations where
we design a process to have one thread to handle I/O operations.
Main
objective of ACE scheduler is fair execution. So it tries it’s best to not let
any thread starve for long time. This is done by moving threads between priority
queues. So if a thread is found starving in
a priority queue at level n, then it’s automatically moved to level n-1.
However movement of any thread is restrained to it’s
class.
TimeSlice quota expiry:
After thread’s time quantum expires, scheduler is
called by the kernel to schedule a new thread.
Procedure to select a new thread:
The
order of search for a new thread begins from current level and ends at level n
where n is the least level(25). So
threads from lower priority level are not run, until all threads from higher
priority level are done with execution.
When
a thread finishes it’s timeslice quota, processor is
unhappy that the thread utilized it’s full quota without relinquishing control
over the CPU. Hence it revaluates the sched_bonus
value of the priority queue in which the thread resides:
If the value is equal to -5. Then the priority
queue is moved to lower(n+1) priority queue slot and sched_bonus value is reinitialized to 0. This is called depromotion.
Note that we only swap the priority queue pointers.
Else
the sched_bonus value is decremented.
Whenever
timeslice expires, sched_bonus value of each priority
queue(5 queues) in the same class is recalculated.
Preemption:
While a thread is running, if a new thread is added
to the ready queue, following steps are performed:If the new thread has got a lesser priority than the
running thread, it keeps waiting in the ready queue.Else
if the new thread has got a greater priority than the running thread, following
steps are performed:The
currently running thread is moved back to it’s ready queue and the priority queue
in which the thread resides is recalculated.The new higher
priority thread is selected to run..
Priority recalculation:
When
a thread X is preempted by a higher priority thread Y, scheduler denies thread X of it’s cpu
quota. Hence it is adjusted by increasing the sched_bonus
value of the corresponding priority queue. If this value reaches +5, then we
move the thread from level n to higher level n-1. This is called promotion.
Note:
A scenario where a new thread is added to the ready queue is:
A
thread has issued an I/O and hence it’s on wait queue. When the I/O is
completed, I/O device raises an interrupt and the ISR puts the corresponding
thread in ready queue. So if this is a high priority thread, then it will
preempt the currently running thread on that cpu.
Key points on this scheduling
policy
1. A thread in low
priority queue will starve for sometime, only if multiple threads at higher priority
queue keep executing. This will not happen for a long time, because the sched_bonus value will be decremented for the higher
priority queue whenever the timeslice expires on one of it’s
threads. Hence an entire priority queue is depromoted
if it is CPU hungry.
2. Similarly if threads in
a priority queue are I/O bound, then the priority queue is promoted.
3. Note that promotion and
depromotion happens only within a class.
4. A thread from lower
priority queue can starve forever, if threads in higher priority queue keep on
executing and move between waiting and ready state. But this is highly unpredictable and
impossible to program from user, because a user cannot predict when the
scheduler selects a particular thread to run. Hence the scheduling policy is
false proof.
5. During promotion and depromotion, if the value of sched_bonus
reaches maximum or minimum, it is left at that extreme value itself without any
alteration.
Things
to be done on context switch:
1.
Call processor dependent code to do the following:
a.
Save registers of running thread.
b.
Load registers of newly selected thread.
c.
Change the processor structure fields to reflect newly
selected thread credentials.
d.
Invalidate TLB and cache.
2.
Call MM to switch the virtual memory layout.
Communication
between kernel-threads and user-threads happens only through signal mechanism.
Threads inside a process will not need any explicit mechanism and hence for
communication between user threads of different process, shared memory or
message passing has to be used.
Task (To share bulk data, shared memory option should be given.)
Since
this form of communication is common for all threads of a process, all
information/data structures are maintained per process.
1. Message Passing
All task
has a queue to store messages where kernel posts all the messages from other
task's thread into this message queue. It is the responsibility of the task to
clear the message queue, to avoid overflow.
2. Shared Memory
Only access permission and
statistics are maintained in PM; all other details are maintained in VM.

struct Process
{
/*process management fields*/
UINT32 Id; //Unique id for the
process
UINT32 ParentId; //Parent
id
TIME CreationTime; //Creation
date time of the process
UINT32 State; //Current state of the
process
UINT32 Flag;
UINT16 ReferenceCount; //number of objects referring
LOCK Lock; //synchronization mech for process
/*thread management*/
THREAD ThreadRunQueue; //threads
in the run queue
THREAD ThreadOtherQueue; //threads
in the state != run
/*vm related fields*/
V_MAP VirtualMap; //virtual_map
for this process
/*scheduler related fields*/
UINT16 MaxPriority;
UINT16 CurrentPriority;
THREAD RunQueue; //threads
in the run state
THREAD OtherQueue; //threads
in the other than run state
LOCK SchedulerLock;
/*IPC related fields*/
MESSAGE MessageQueue; //message box for this process
LOCK MessageLock;
};
struct Thread
{
/*management fields*/
LOCK Lock;
UINT32 State; //state of the thread
UINT32 Flag;
UINT16 ReferenceCount; //number of objects referring
/*queue management*/
THREAD NextThreadProcess; //next
thread in the same process
THREAD NextThreadCPU; //next
thread in the CPU
/*process related fields*/
PROCESS Process;
/*scheduler related fields*/
UINT16 MaxPriority;
UINT16 CurrentPriority;
TIME LastRun; //when
the thread last removed from runq
LOCK SchedulerLock;
/*execution context*/
CHAR* Stack;
CPU RunningCpu;
/*ipc
related fields*/
UINT32 Signal; //bit mask for signal
/*synchronization related*/
EVENT WaitEvent; //event
for which this thread is waiting
TIME TimeOut; //time
when a panic should called
};
Functions/Macros:
Returns current running process structure.
Returns
current running thread structure
Returns
current running CPU structure
Creates
a new process and starts a single thread and returns process structure.
Arguments:
·
ProgramName – Path and name of the
program to start
·
EntryPoint – Symbol name to start
execution of the first thread
Terminates
execution of a process and removes all associated data structures.
Arguments:
·
ProcessId
Returns process structure for a given process id.
Argument:
·
ProcessId
Sets
the priority of a process
Arguments:
·
ProcessId
·
Priority
Creates
a new thread and returns its address
Arguments:
·
EntryPoint
Pauses execution of a thread.
Arguments:
·
Thread
Starts/resumes execution of a thread.
Arguments:
·
Thread
Terminates
execution of a thread and removes all datastructures
associated with it.
Arguments:
·
Thread
Adds the given message in a process’s message queue.
Arguments:
·
ProcessId
·
Message
Removes
a message from the current process message queue and returns it.
Sets the signal bit for a thread.
Arguments:
·
Thread
Puts
the given thread in the pause state and waits for the given event to occur.
Arguments:
·
Thread
·
Event
Wakes
one or more threads waiting for the particular event.
Arguments:
·
Event
·
NumberOfThreadsToWakeup