# 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()