main()
in file main.cc
under threads
directory performs the following key operations.
Initialize()
in file system.cc
under threads
directory
to initialize Nachos global data structure. For example, set random yield or not based on parameters,
and create an interrupt object and a scheduler object etc.
ThreadTest()
in the file threadtest.cc
under threads
directory.
This creates two Nachos threads to execute. This calls two function methods Thread::Fork()
and
Thread::Yield()
.
currentThread->Finish()
in file thread.cc which calls
Thread:Sleep()
, similar to Thread:Yield().
If the procedure "main" returns, then the program "nachos"
will exit (as any other normal program
would). But there may be other
threads on the ready list. We switch
to those threads by saying that the
"main" thread is finished, preventing
it from returning.
Thread::Fork()
is defined in
thread.cc
under threads
directory.
It calls
StackAllocate(func, arg)
defined in
thread.cc
under threads
directory.
This function allocates stack space and initialize the thread control block and the register state information.
InitialPCState
slot of the thread control block.
This address will be used as the entrance point when this thread starts to execute, which will be discussed in two
assembly functions SWITCH() and ThreadRoot() below.
ThreadTest()
in the file threadtest.cc
calls currentThread->Yield()
to yield CPU to another ready thread and
Nachos schedules another thread for execution.
Thread::Yield()
.
is defined in
thread.cc
under threads
directory.
FindNextToRun()
method defined in
scheduler.cc
under threads
directory
finds next thread in the ready queue to run. That essentially removes the first one in the ready queue.
ReadyToRun()
in
scheduler.cc
under threads
directory
marks the selected thread to be ready and append it to the end of ready list.
Run()
in
scheduler.cc
under threads
directory
executes the selected thread.
It does context switch from old thread to new thread by calling assembly code
SWITCH(oldThread, nextThread)
switch.s
under threads
directory.
SWITCH() shifts execution from the old thread to new thread. It saves register state
for the old thread and loads the new data from the new thread's control block.
For example, if the host hardware is MIPS (actually not as
we run on the Intel chip. I use MIPS because its instructions are easy to read.)
# a0 -- pointer to old Thread
# a1 -- pointer to new Thread
SWITCH:
sw sp, SP(a0) # save new stack pointer
sw s0, S0(a0) # save all the callee-save registers
sw s1, S1(a0)
sw s2, S2(a0)
sw s3, S3(a0)
sw s4, S4(a0)
sw s5, S5(a0)
sw s6, S6(a0)
sw s7, S7(a0)
sw fp, FP(a0) # save frame pointer
sw ra, PC(a0) # save return address
lw sp, SP(a1) # load the new stack pointer
lw s0, S0(a1) # load the callee-save registers
lw s1, S1(a1)
lw s2, S2(a1)
lw s3, S3(a1)
lw s4, S4(a1)
lw s5, S5(a1)
lw s6, S6(a1)
lw s7, S7(a1)
lw fp, FP(a1)
lw ra, PC(a1) # load the return address
j ra
.end SWITCH
- Constants
SP, S0, ..., PC
etc are defined in
switch.h
under threads
directory. For example, SP=0
for MIPS host.
Notice that 0 means the first position stackTop
in the thread control block which is defined in
thread.h
- What does
SWITCH()
finally call after saving and loading context?
Namely what does the return address "ra" point to before executing "j ra"?
Notice that StackAllocate(func, arg)
has assigned
machineState[PCState] = (int) ThreadRoot;
machineState[InitialPCState] = (int) func;
For MIPS host case,
PCState= PC/4 -1=9
InitialPCstate= S0/4-1=0
Thus SWITCH() calls "j ra" which is ThreadRoot. Then ThreadRoot() will call "func" saved and loaded from InitialPCState.
ThreadRoot:
jal StartupPC # call startup procedure
move a0, InitialArg
jal InitialPC # call main procedure
jal WhenDonePC # when were done, call clean up procedure
Tao Yang