# MP3 – CPU scheduling
# Team10
## Team member:李侑庭(110062172), 盧思樺(110062272)
| 部分 | 李侑庭 | 盧思樺 |
| --- | --- | --- |
| Trace code | 50% | 50% |
| Implement | 70% | 30% |
| Report | 70% | 30% |
| Testing | 30% | 70% |
# Part 1:Trace code
## 1-1. New→Ready
### (1) Kernel::ExecAll()
- **execfileNum** represent the file number need to be executed.
- Call **Exec** function to execute every file in execfile table.
- Call **Finish()** to relinquish CPU.
```
void Kernel::ExecAll()
{
for (int i=1;i<=execfileNum;i++) {
int a = Exec(execfile[i]);
}
currentThread->Finish();
}
```
### (2) Kernel::Exec(char*)
- Create new thread and maintain the thread table.
- Create address space for new thread.
- Call **fork()**.
- Return the id of executing thread.
```
int Kernel::Exec(char* name)
{
t[threadNum] = new Thread(name, threadNum);
t[threadNum]->space = new AddrSpace();
t[threadNum]->Fork((VoidFunctionPtr) &ForkExecute, (void *)t[threadNum]);
threadNum++;
return threadNum-1;
}
```
### (3) Thread::Fork(VoidFunctionPtr func, void *arg)
- **func** store the function pointer.
- Call **StackAllocate(func, arg)** to allocate stack.
- Call **interrupt->SetLevel()** to disable interrupt before calling scheduler.
- Call **scheduler->ReadyToRun(this)** to make this thread ready and put it into ready queue.
- Restore the interrupt level to its previous state.
```
void Thread::Fork(VoidFunctionPtr func, void *arg)
{
Interrupt *interrupt = kernel->interrupt;
Scheduler *scheduler = kernel->scheduler;
IntStatus oldLevel;
StackAllocate(func, arg);
oldLevel = interrupt->SetLevel(IntOff);
scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts
// are disabled!
(void) interrupt->SetLevel(oldLevel);
}
```
### (4) Thread::StackAllocate(VoidFunctionPtr func, void *arg)
- Call **AllocBoundedArray()** function to allocate and initialize stack.
- Change stack pointer by the instruction set.
### (5) Scheduler::ReadyToRun(Thread*)
- set thread status to ready.
- append this thread to readyList.
```
void Scheduler::ReadyToRun (Thread *thread)
{
thread->setStatus(READY);
readyList->Append(thread);
}
```
## 1-2. Running→Ready
### (1) Machine::Run()
- Execute instructions one by one in user-level after leaving the kernel-level program, and advance the simulated time.
```clike
void Machine::Run()
{
Instruction *instr = new Instruction; //storage for decoded instruction
kernel->interrupt->setStatus(UserMode); //transfer control to user mode
for (;;)
{
OneInstruction(instr); //execute one decoded instruction from a user-level program
kernel->interrupt->OneTick(); //advance simulated time
if (singleStep && (runUntilTime <= kernel->stats->totalTicks))
Debugger();
}
}
```
### (2) Interrupt::OneTick()
- Advance simulated time by the status.
- Turn off interrupts.
- Call **CheckIfDue()** function to check pending interrupts with false as parameter.
- Turn on interrupts.
- If the timer device want to a context switch, do it.
### (3) Thread::Yield()
- Call **FindNextToRun()** funtction to get the next thread to run.
- If nextThread is not null, call **ReadyToRun()** function to put this thread back to readyList and call **Run()** function to run the next thread.
- Pass **FALSE** as **finishing parameter** in **Run()** funtion to prevent destory this thread.
- Turn off interrupts.
```
void Thread::Yield()
{
Thread *nextThread;
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
nextThread = kernel->scheduler->FindNextToRun();
if (nextThread != NULL)
{
kernel->scheduler->ReadyToRun(this);
kernel->scheduler->Run(nextThread, FALSE);
}
(void)kernel->interrupt->SetLevel(oldLevel);
}
```
### (4) Scheduler::FindNextToRun()
- return the next thread in readyList and remove it from readyList
- If readyList is empty, return NULL.
```
Thread* Scheduler::FindNextToRun()
{
if (readyList->IsEmpty())
{
return NULL;
}
else
{
return readyList->RemoveFront();
}
}
```
### (5) Scheduler::ReadyToRun(Thread *thread)
- set thread status to ready.
- append this thread to readyList.
### (6) Scheduler::Run(Thread *nextThread, bool finishing)
- In Running→Ready finishing parameter is FALSE.
- **oldThread**: represents current runing thread.
- If oldThread is finishing, assign it to toBeDestroyed to destroy it.
- If oldThread space is not NULL,call **SaveUserState()** and **SaveState()** to save the CPU state.
- Check whether **oldThread** is stack overflow.
- Update current running thread to **nextThread**,**nextThread** is the upcoming thread to execute, and set **nextThread** status to running.
- Call SWITCH() to context switch.
- Call **CheckToBeDestroyed()** to delete oldThread if it is not NULL.
- If the space of the running thread is not NULL,call **RestoreUserState()** and **RestoreState()** to restore the CPU state.
```
void Scheduler::Run (Thread *nextThread, bool finishing)
{
Thread *oldThread = kernel->currentThread;
if (finishing) {
toBeDestroyed = oldThread;
}
if (oldThread->space != NULL) {
oldThread->SaveUserState();
oldThread->space->SaveState();
}
oldThread->CheckOverflow();
kernel->currentThread = nextThread;
nextThread->setStatus(RUNNING);
SWITCH(oldThread, nextThread);
CheckToBeDestroyed();
if (oldThread->space != NULL) {
oldThread->RestoreUserState();
oldThread->space->RestoreState();
}
}
```
## 1-3. Running→Waiting (Note: only need to consider console output as an example)
### (1) SynchConsoleOutput::PutChar(char ch)
- Use **lock->Acquire()** function to lock resource.Only the calling thread can use resource, the other threads will be blocked until lock release.
- Call **PutChar()** function and pass parameter char ch.
- Use **waitfor->P()** function to make the subsequent characters wait.
- Release lock.
```
void SynchConsoleOutput::PutChar(char ch)
{
lock->Acquire();
consoleOutput->PutChar(ch);
waitFor->P();
lock->Release();
}
```
### (2) Semaphore::P()
- Thread wait until semaphore value > 0.
- If semaphore value == 0, then put this thread into wait queue and call sleep() function on the thread until semaphore value > 0.
- semaphore value - 1
- Turn off interrupts.
```
void Semaphore::P()
{
Interrupt *interrupt = kernel->interrupt;
Thread *currentThread = kernel->currentThread;
IntStatus oldLevel = interrupt->SetLevel(IntOff);
while (value == 0)
{
queue->Append(currentThread);
currentThread->Sleep(FALSE);
}
value--;
(void)interrupt->SetLevel(oldLevel);
}
```
### (3) List<T>::Append(T)
- Like doubly linked list insert operation.
- Put item into a ListElement which with a pointer to next element.
- If nothing is in list, let first and last is this element.
- Else let the last element's next point to this ListElement and update last.
- numInList is the number of elements in list.
```
void List<T>::Append(T item)
{
ListElement<T> *element = new ListElement<T>(item);
if (IsEmpty())
{
first = element;
last = element;
}
else
{
last->next = element;
last = element;
}
numInList++;
}
```
### (4) Thread::Sleep(bool finishing)
- In Running→Waiting finishing parameter is FALSE.
- this thread will relinquish CPU.
- Set thread status to BLOCKED.
- If the ready queue of the scheduler is empty, call kernel->interrupt->Idle() function to advance simulated time until the next scheduled hardware interrupt.
- call **Scheduler::Run()** with parameters nextThread and finishing.
```
void Thread::Sleep (bool finishing)
{
Thread *nextThread;
status = BLOCKED;
while ((nextThread = kernel->scheduler->FindNextToRun()) == NULL) {
kernel->interrupt->Idle();
}
kernel->scheduler->Run(nextThread, finishing);
}
```
### (5) Scheduler::FindNextToRun()
- return the next thread in readyList and remove it from readyList
- If readyList is empty, return null
### (6) Scheduler::Run(Thread *nextThread, bool finishing)
- In Running→Waiting finishing parameter is FALSE.
## 1-4. Waiting→Ready (Note: only need to consider console output as an example)
### (1) Semaphore::V()
- If any thread in wait queue, call ReadyToRun() function to put this thread into ready list.
- Turn on interrupts.
```
void Semaphore::V()
{
Interrupt *interrupt = kernel->interrupt;
IntStatus oldLevel = interrupt->SetLevel(IntOff);
if (!queue->IsEmpty())
{
kernel->scheduler->ReadyToRun(queue->RemoveFront());
}
value++;
(void)interrupt->SetLevel(oldLevel);
}
```
### (2) Scheduler::ReadyToRun(Thread *thread)
- set thread status to ready.
- append this thread to readyList.
## 1-5. Running→Terminated (Note: start from the Exit system call is called)
### (1) ExceptionHandler(ExceptionType) case SC_Exit
- Handle exceptions based on the 'which' argument and the register values.
- In the case of a system call, this function will utilize the code stored in register 2 to determine how to handle this system call and put back the result into register 2. Otherwise, it will only output messages..
- For the SC_Exit, it output the value of register 4 and call currentThread->Finish() to terminated the executed thread.
### (2) Thread::Finish()
- Disable interrupt.
- Call **Sleep()** to current thread.
```
void Thread::Finish ()
{
(void) kernel->interrupt->SetLevel(IntOff);
Sleep(TRUE);
}
```
### (3) Thread::Sleep(bool finishing)
- In Running→Terminated finishing parameter is TRUE.
- this thread will relinquish CPU.
- Set thread status to BLOCKED.
- If the ready queue of the scheduler is empty, call kernel->interrupt->Idle() function to advance simulated time until the next scheduled hardware interrupt.
- call **Scheduler::Run()** with parameters nextThread and finishing.
### (4) Scheduler::FindNextToRun()
- Return the next thread in readyList and remove it from readyList
- If readyList is empty, return null
### (5) Scheduler::Run(Thread *nextThread, bool finishing)
- In Running→Terminated finishing parameter is TRUE.
## 1-6. Ready→Running
### (1) Scheduler::FindNextToRun()
- return the next thread in readyList and remove it from readyList
- If readyList is empty, return NULL.
### (2) Scheduler::Run(Thread *nextThread, bool finishing)
- In Ready→Running finishing parameter can be TRUE or FALSE.
### (3) SWITCH(Thread* t1, Thread* t2)
- parameter t1 is in 4(%esp), t2 is in 8(%esp) and return address is in 0(%esp)
- save the value of eax in _eax_save register.
- move pointer to thread 1(old thread) into eax.
- save the value of registers of t1 ebx, ecx, edx, esi, edi, ebp and esp into memory by the value of eax.
- get the eax from _eax_save register and save it into memory.
- get the return address from 0(esp) in memory and store it in PC.
- move pointer to thread 2(new thread) into eax.
- get new value for eax into ebx from memory and save the value of ebx in _eax_save register.
- get new value of t2 ebx, ecx, edx, esi, edi, ebp and esp from memory into register by the value of eax.
- get the return address into eax from PC and save it in 4(%esp).
- move the value from _eax_save into eax.
- set PC to the memory address pointed by the value of register esp.
```
** eax points to startup function (interrupt enable)
** edx contains inital argument to thread function
** esi points to thread function
** edi point to Thread::Finish()
SWITCH:
movl %eax,_eax_save # save the value of eax
movl 4(%esp),%eax # move pointer to t1 into eax
movl %ebx,_EBX(%eax) # save registers
movl %ecx,_ECX(%eax)
movl %edx,_EDX(%eax)
movl %esi,_ESI(%eax)
movl %edi,_EDI(%eax)
movl %ebp,_EBP(%eax)
movl %esp,_ESP(%eax) # save stack pointer
movl _eax_save,%ebx # get the saved value of eax
movl %ebx,_EAX(%eax) # store it
movl 0(%esp),%ebx # get return address from stack into ebx
movl %ebx,_PC(%eax) # save it into the pc storage
movl 8(%esp),%eax # move pointer to t2 into eax
movl _EAX(%eax),%ebx # get new value for eax into ebx
movl %ebx,_eax_save # save it
movl _EBX(%eax),%ebx # retore old registers
movl _ECX(%eax),%ecx
movl _EDX(%eax),%edx
movl _ESI(%eax),%esi
movl _EDI(%eax),%edi
movl _EBP(%eax),%ebp
movl _ESP(%eax),%esp # restore stack pointer
movl _PC(%eax),%eax # restore return address into eax
movl %eax,4(%esp) # copy over the ret address on the stack
movl _eax_save,%eax
ret
```
### (4) (depends on the previous process state, e.g.,[New, Running, Waiting]→Ready
* **New → Ready** indicates this thread enters the ready queue for the first time.
- **Ready → Running → Ready** means it trigger interrupt during the execution of this thread and the control of CPU will be given to another thread.
- **Running → Waiting → Ready** means oldThread finishes it's job and wait for IO resources.After complete IO, this thread will be push into ready queue.
### (5) for loop in Machine::Run()
- Execute instructions one by one in user-level after leaving the kernel-level program, and advance the simulated time.
```clike
void Machine::Run()
{
Instruction *instr = new Instruction; //storage for decoded instruction
kernel->interrupt->setStatus(UserMode); //transfer control to user mode
for (;;)
{
OneInstruction(instr); //execute one decoded instruction from a user-level program
kernel->interrupt->OneTick(); //advance simulated time
if (singleStep && (runUntilTime <= kernel->stats->totalTicks))
Debugger();
}
}
```
# Part 2:Implement
## 2-1. Implement a multilevel feedback queue scheduler with aging mechanism as described below:
### thread.h
- compute and log CPU burst time and ready queue waiting time.
- declaring getter/setter method for needed attributes.
```
public:
...
double getTi() { return (Ti); }
double getRemaining_burst_time() { return (Ti - getBurst_time()); }
double getBurst_time() { return (burst_time); }
int getPriority() { return (priority); }
int getStart_waiting_tick() { return (start_waiting_tick); }
int getStart_waiting_state_tick() {return (start_waiting_state_tick);}
int getStart_running_tick() { return (start_running_tick); }
void setTi(double x) { Ti = x; }
void setPriority(int x) { priority = x; }
void setBurst_time(double x) { burst_time = x; }
void setStart_waiting_tick(int x) { start_waiting_tick = x; }
void setStart_running_tick(int x) { start_running_tick = x; }
void setStart_waiting_state_tick(int x) {start_waiting_state_tick = x;}
...
private:
...
int priority;
int start_waiting_tick; // in ready queue
int start_running_tick;
int start_waiting_state_tick;
double Ti, burst_time;
...
```
### thread.cc
- when the thread from running to waiting state.
- update the CPU burst time.
- compute next approximated CPU burst time .
- save start_waiting_state_tick to check if the CPU burst_time is be updated.
```
void Thread::Sleep(bool finishing)
{
...
if (!finishing )
{
this->burst_time += (double)kernel->stats->userTicks - this->getStart_running_tick();
this->setStart_waiting_state_tick(kernel->stats->userTicks);
this->setStart_running_tick(kernel->stats->userTicks);
DEBUG(dbgSch, "[D] Tick [" << kernel->stats->totalTicks << "]: Thread [" << this->getID() << "] update approximate burst time, from: [" << this->Ti << "], add [" << this->getBurst_time() << "], to [" << (double)0.5 * this->getBurst_time() + 0.5 * this->Ti << "]");
double new_Ti = 0.5 * (double)this->getBurst_time() + 0.5 * (double)this->Ti;
this->setTi(new_Ti);
this->setBurst_time(0);
}
...
}
```
### scheduler.h
- declaring l1,l2,l3 queue in **Scheduler.h**
```
class Scheduler{
public:
...
SortedList<Thread *>* get_l1(){return this->l1;};
SortedList<Thread *>* get_l2(){return this->l2;};
List<Thread *>* get_l3(){return this->l3;};
...
private:
...
SortedList<Thread *> *l1;
SortedList<Thread *> *l2;
List<Thread *> *l3;
...
}
```
### scheduler.cc
- implementing custom compare function for L1 and L2.
- If x's remaining burst time is greater than y's remaining burst time,return -1.
- Otherwise,return 1.
- Same rule for L2,but L2 compares threads' priorities for sorting.
- using comp_l1 and comp_l2 as parameter to sortedlist to sort list by t
```
int comp_l1(Thread *x, Thread *y) // if return value < 0, then x is the first.
{
if (x->getRemaining_burst_time() > y->getRemaining_burst_time())
{
return 1;
}
else
{
return -1;
}
}
int comp_l2(Thread *x, Thread *y) // if return value < 0, then x is the first.
{
if (x->getPriority() < y->getPriority())
{
return 1;
}
else
{
return -1;
}
}
Scheduler::Scheduler(){
l1 = new SortedList<Thread *>(comp_l1);
l2 = new SortedList<Thread *>(comp_l2);
l3 = new List<Thread *>;
}
```
### Scheduler::ReadyToRun(Thread *)
- Inserting thread to corresponding queue according to it's priority.
```
void Scheduler::ReadyToRun(Thread *thread)
{
ASSERT(kernel->interrupt->getLevel() == IntOff);
DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
// cout << "Putting thread on ready list: " << thread->getName() << endl ;
thread->setStatus(READY);
thread->setStart_waiting_tick(kernel->stats->totalTicks);
if (thread->getPriority() >= 100)
{
l1->Insert(thread);
DEBUG(dbgSch, "[A] Tick [" << kernel->stats->totalTicks << "]: Thread [" << thread->getID() << "] is inserted into queue L[1]");
}
else if (thread->getPriority() >= 50)
{
l2->Insert(thread);
DEBUG(dbgSch, "[A] Tick [" << kernel->stats->totalTicks << "]: Thread [" << thread->getID() << "] is inserted into queue L[2]");
}
else
{
l3->Append(thread);
DEBUG(dbgSch, "[A] Tick [" << kernel->stats->totalTicks << "]: Thread [" << thread->getID() << "] is inserted into queue L[3]");
}
}
```
### Scheduler::FindNextToRun()
- In Scheduler::FindNextToRun(),choose a thread to execute according multilevel feedback queue's strategy.
```
Thread* Scheduler::FindNextToRun()
{
if (!l1->IsEmpty())
{
DEBUG(
dbgSch, "[B] Tick [" << kernel->stats->totalTicks << "]: Thread [" << l1->Front()->getID() << "] is removed from queue L[1]");
return l1->RemoveFront();
}
else if (!l2->IsEmpty())
{
DEBUG(
dbgSch, "[B] Tick [" << kernel->stats->totalTicks << "]: Thread [" << l2->Front()->getID() << "] is removed from queue L[2]");
return l2->RemoveFront();
}
else if (!l3->IsEmpty())
{
DEBUG(
dbgSch, "[B] Tick [" << kernel->stats->totalTicks << "]: Thread [" << l3->Front()->getID() << "] is removed from queue L[3]");
return l3->RemoveFront();
}
else
{
return NULL;
}
}
```
### Scheduler::Run(Thread* , bool)
- check the start_running_tick after the start_waiting_state_tick to avoid update CPU burst time two times.
- calculating the CPU burst time of current thread.
- set next thread's running time to current userTicks.
```
Scheduler::Run(Thread *nextThread, bool finishing){
...
if (oldThread->getStart_waiting_state_tick() < oldThread->getStart_running_tick() || nextThread->getStart_running_tick() == 0)
{
double accumulated_ticks = (double)kernel->stats->userTicks - oldThread->getStart_running_tick();
oldThread->setBurst_time(accumulated_ticks + oldThread->getBurst_time());
oldThread->setStart_running_tick(kernel->stats->userTicks);
DEBUG(dbgSch, "[E] Tick [" << kernel->stats->totalTicks << "]: Thread [" << nextThread->getID() << "] is now selected for execution, thread [" << oldThread->getID() << "] is replaced, and it has executed [" << (oldThread->getBurst_time()) << "] ticks");
}
nextThread->setStart_running_tick(kernel->stats->userTicks);
...
}
```
### Scheduler::~Scheduler()
- delete all the feedback queues.
```
Scheduler::~Scheduler()
{
delete l1, l2, l3;
}
```
### alarm.cc
- update CPU burst time.
- loop over l1, l2 and l3 queue to find the thread need to aging(waiting time over 1500 ticks).
- in the loop of l1, we also check if there exists a thread with smaller remaining CPU burst time than the current executing thread.
- to decide whether the current executing thread should be preempted, check the current executed thread priority and check its remaining CPU burst time.
```
// aging
SortedList<Thread *> *l1(kernel->scheduler->get_l1());
SortedList<Thread *> *l2(kernel->scheduler->get_l2());
List<Thread *> *l3(kernel->scheduler->get_l3());
for (ListIterator<Thread *> it(l3); !it.IsDone(); it.Next())
{
if (kernel->stats->totalTicks - it.Item()->getStart_waiting_tick() >= 1500)
{
DEBUG(dbgSch, "[C] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] changes its priority from [" << it.Item()->getPriority() << "] to [" << it.Item()->getPriority() + 10 << "]");
it.Item()->setStart_waiting_tick(kernel->stats->totalTicks);
it.Item()->setPriority(it.Item()->getPriority() + 10);
}
if (it.Item()->getPriority() > 49)
{
l3->Remove(it.Item());
DEBUG(
dbgSch, "[B] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] is removed from queue L[3]");
l2->Insert(it.Item());
DEBUG(dbgSch, "[A] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] is inserted into queue L[2]");
}
}
for (ListIterator<Thread *> it(l2); !it.IsDone(); it.Next())
{
if (kernel->stats->totalTicks - it.Item()->getStart_waiting_tick() >= 1500)
{
DEBUG(dbgSch, "[C] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] changes its priority from [" << it.Item()->getPriority() << "] to [" << it.Item()->getPriority() + 10 << "]");
it.Item()->setStart_waiting_tick(kernel->stats->totalTicks);
it.Item()->setPriority(it.Item()->getPriority() + 10);
}
if (it.Item()->getPriority() > 99)
{
l2->Remove(it.Item());
DEBUG(
dbgSch, "[B] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] is removed from queue L[2]");
l1->Insert(it.Item());
DEBUG(dbgSch, "[A] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] is inserted into queue L[1]");
}
}
bool need_to_preempt = false;
kernel->currentThread->setBurst_time(kernel->stats->userTicks - kernel->currentThread->getStart_running_tick() + kernel->currentThread->getBurst_time());
kernel->currentThread->setStart_running_tick(kernel->stats->userTicks);
for (ListIterator<Thread *> it(l1); !it.IsDone(); it.Next())
{
if (kernel->stats->totalTicks - it.Item()->getStart_waiting_tick() >= 1500)
{
if (it.Item()->getPriority() + 10 >= 149)
{
DEBUG(dbgSch, "[C] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] changes its priority from [" << it.Item()->getPriority() << "] to [" << 149 << "]");
it.Item()->setStart_waiting_tick(kernel->stats->totalTicks);
it.Item()->setPriority(149);
}
else
{
DEBUG(dbgSch, "[C] Tick [" << kernel->stats->totalTicks << "]: Thread [" << it.Item()->getID() << "] changes its priority from [" << it.Item()->getPriority() << "] to [" << it.Item()->getPriority() + 10 << "]");
it.Item()->setStart_waiting_tick(kernel->stats->totalTicks);
it.Item()->setPriority(it.Item()->getPriority() + 10);
}
}
if (kernel->currentThread->getRemaining_burst_time() > it.Item()->getRemaining_burst_time())
{
//cout << kernel->currentThread->getTi() << " " << it.Item()->getTi() << "\\n";
need_to_preempt = true;
}
}
// preempt
if (status != IdleMode)
{
if (kernel->currentThread->getPriority() <= 49)
{
if (!l1->IsEmpty() || !l2->IsEmpty())
{
interrupt->YieldOnReturn();
}
if (kernel->currentThread->getBurst_time() >= 100)
{
interrupt->YieldOnReturn();
}
}
else if (kernel->currentThread->getPriority() < 100 && kernel->currentThread->getPriority() >= 50)
{
if (!l1->IsEmpty())
{
interrupt->YieldOnReturn();
}
}
else if (kernel->currentThread->getPriority() <= 149 && kernel->currentThread->getPriority() >= 100)
{
if(need_to_preempt){
interrupt->YieldOnReturn();
}
}
}
```
## 2-2. Add a command line argument -ep for nachos to initialize priority of process.
### kernel.h
- adding filepriority array to record file priority in kernel constructor
```
class Kernel{
private:
...
int filepriority[10];
...
}
```
### [kernel.cc](http://kernel.cc/)
- adding new argument **ep** in kernel constructor
- using **execfile** and **filepriority** to read file name and file priority.
```
Kernel::Kernel(int argc, char **argv){
...
else if (strcmp(argv[i], "-ep") == 0)
{
execfile[++execfileNum] = argv[++i];
filepriority[execfileNum] = atoi(argv[++i]);
}
...
}
int Kernel::Exec(char *name){
...
t[threadNum]->setPriority(filepriority[threadNum]);
...
}
```
## 2-3. Add a debugging flag z and use the DEBUG('z', expr) macro (defined in debug.h) to print following messages. Replace {...} to the corresponding value.
### debug.h
### (1)
- add following line to debug.h
```
...
const char dbgSch = 'z';
...
```
- insert following line to Scheduler::ReadyToRun() according the thread's id and queue level.
```
DEBUG(dbgScheduler, "[A] Tick [" << kernel->stats->totalTicks << "]:
Thread [" << thread->getID()<< "] is inserted
into queue L[queue level]");
```
### (2)
- insert following line to Scheduler::FindNextToRun()
```
DEBUG(dbgScheduler, "[B] Tick [" << kernel->stats->totalTicks << "]:
Thread [" << thread->getID()<< "]
is removed from queue L[queue level]");
```
### (3)
- insert following line to Alarm::CallBack() when changing thread's priority.
```
DEBUG(dbgScheduler, "[C] Tick [" << kernel->stats->totalTicks << "]:
Thread [" << this->getID()
<< "] changes its priority from [" << oldPriority << "] to ["
<< newPriority << "]");
```
### (4)
- insert following line to Thread::Sleep(bool) when updating approximate burst time.
```
DEBUG(dbgSch, "[D] Tick [" << kernel->stats->totalTicks <<
"]: Thread [" << this->getID() << "] update approximate burst time, from:
[" << this->Ti << "], add [" << this->getBurst_time() << "], to
[" << (double)0.5 * this->getBurst_time() + 0.5 * this->Ti << "]");
```
### (5)
- insert following line to Scheduler::Run()