1
Operating Systems Project 2
資工三 B94902032 陳縕儂、資工三 B94902007 張詩平
目的:將原先的 FCFS 的排程方式改成 Mutilevel Round-Robin 的排程方式
These files we have modified:
(1) code/machine/stats.h
加了兩個新的 pubic 變數,分別是 mrrTicks 和 timeslice,mrrTicks 紀錄 current thread 執行的 tick 總數,timeslice 紀錄 time quantum,為了判定 current thread 執行的 tick 數是否已經到了 time slice,若是的話,則要讓下一個 thread執行。原有的 totalTicks 為總共執行的 tick 數,之後會用到它。
(2) code/threads/thread.h
在 Thread 的 class 中加了一個 function MrrSched,以及兩個 variable,分別是 priority 和 load,紀錄單一一個 thread 的 priority 和 remaining time。
(3) code/threads/main.cc
在 main 中判斷參數 argv[i]有沒有-S 的存在,並設定一個 flag 去紀錄。若有-S,則將 flag 設為 true,之後將-S 後方的參數(一個 file 的 name)傳進 kernel->currentThread->MrrSched()中,並且讓 MrrScahed()去讀 input file。
class Thread { public:
void MrrSched(char *ParameterFile);
int priority; // save a thread's priority int load; // save a thread's remaining time };
class Statistics { public:
int totalTicks; // Total time running Nachos int mrrTicks; // the time unit
int timeslice; // the time slice
Statistics(); // initialize everything to zero
void Print(); // print collected statistics };
int
main(int argc, char **argv) {
int road;
bool ParameterFileFlag = false;
:
for (i = 1; i < argc; i++) { :
else if (strcmp(argv[i], "-S") == 0) { ParameterFileFlag = TRUE;
road = i;
} }
:
if (ParameterFileFlag) {
kernel->currentThread->MrrSched(argv[road + 1]);
} : }
2
(4) code/threads/thread.cc
新增一個新的 function,void Thread::MrrSched (char *ParameterFile),首先是先將傳進來的 file 裡面的 data 讀進 來,放入 kernel->stats->timeslice 和 num 裡面,然後接著就 new 出每個 thread,並且對每個 thread 分別存它們 自己的 priority 和 load,然後傳進 t->Fork((VoidFunctionPtr)SimpleThread, (void *)i)中去 schedule 之。
對原有的 SimpleThread 這個 function 做修改,當 current thread 的 remaining time 還沒被減到 0 時,就要一直 去 run 此 thread,其中每 run 一個 tick 的時間,就會將 current thread 的 info output 出來,同時也計算總共執 行的 tick,每 run 完一次,就將 kernel->stats->totalTicks 加 1。
※由於我們這次的 scheduling algorithm 是 Round-Robin 的 scheduling,所以我們必須要 invoke 一個
interrupt(kernel->interrupt->OneMrrTick())去隨時判斷此時的 current thread 是否已經執行了 time slice 的時間。
若已經執行了 time slice 的 tick 數,就應該要放棄 CPU,並且換下一個 thread 去 run。
(5) code/machine/interrupt.h
在 interrupt 的 class 新增一個新的 public function 為 OneMrrTick()。為了去紀錄現在總共執行的 tick 數及 current thread的 remaining time,並且判斷當執行的 tick 到達 time quantum 則要放棄 CPU 換給下一個 thread 執行。
void
Thread::MrrSched (char *ParameterFile) {
FILE *fin = fopen(ParameterFile, "r"); //Parse the parameter file Thread *t;
int num;
int i;
fscanf(fin, "%d", &kernel->stats->timeslice);
fscanf(fin, "%d", &num);
for(i = 0; i < num; i ++) {
t = new Thread("forked thread"); //create thread
fscanf(fin, "%d%d", &t->priority, &t->load); //set parameter: priority, remaining execution time t->Fork((VoidFunctionPtr)SimpleThread, (void *)i);
}
kernel->currentThread->Yield(); //* Give up CPU
}
static void
SimpleThread(int which) {
while(kernel->currentThread->load > 0){
printf("MrrThread %c at %2d. ", which + 65, kernel->stats->totalTicks);
kernel->stats->totalTicks ++;
printf("(%c's priority = %d, %c's load = %d)\n",
which + 65, kernel->currentThread->priority, which + 65, kernel->currentThread->load);
kernel->interrupt->OneMrrTick();
} }
class Interrupt { : public:
void OneMrrTick();
: };
3
(6) code/machine/interrupt.cc
新增一個OneMrrTick的function,每當執行時,mrrTicks會加1去紀錄此時執行的tick數,並且將current thread的 load減1。當有任何一個thread做完的時候(load = 0),就將mrrTicks歸零,為了避免下一個thread在跑的時候,執 行的tick數會多加上了前一個thread所用的tick數(因為前一個thread可能還沒到time slice的tick就已經結束),所 以在此thread結束時,就把mrrTicks歸零,重新計算。
※如果kernel->stats->mrrTicks可以被kernel->stats->timeslice整除的話,代表現在執行的tick數已經到了time slice 的限制(假如timeslice = 2,mrrTicks = 2、4、6…),由於我們使用的是Round-Robin的scheduling algorithm,所以 當執行的tick數已到了time quantum時,current thread就應該要放棄CPU,並且去找下一個thread執行(利用 kernel->currentThread->Yield()讓呼叫它的thread suspend並且找尋下一個thread去執行)。
(7) code/threads/scheduler.h
在scheduler的class中,增加一個public的List array,大小為11,代表總共有11個readylist,每個list分別放置相同 priority的thread(priority是1~10,main會被放置在0中),當我們去schedule的時候,先將一個list裡面的thread都 做完,才會做下一個list的。(先將priority相同的用RR的方式run完,才做下一個level的list中的thread)。
(8) code/threads/scheduler.cc
在一開始,先new出11個List,分別當做不同priority的readylist。
void
Interrupt::OneMrrTick() {
kernel->stats->mrrTicks ++; // time of execution kernel->currentThread->load --; // remaining time
if(kernel->currentThread->load == 0){
kernel->stats->mrrTicks = 0;
}
if(kernel->stats->mrrTicks % kernel->stats->timeslice == 0){
kernel->currentThread->Yield();
} }
class Scheduler { :
public:
List<Thread *> *mrrList[11];
: };
Scheduler::Scheduler() {
int i;
for(i = 0; i <= 10; i ++){
mrrList[i] = new List<Thread *>;
}
toBeDestroyed = NULL;
}
4
將傳進來的thread加在它的priority對應的list中(mrrList[i]),若前面已有thread存在,則加在後面(Append)。
從priority = 1的開始排程,若此list還沒有empty的話,代表它要繼續做下一個應該要執行的thread,故利用 RemoveFront()把最前面的thread回傳回去。然後一直到此list空了以後,才跑下一個level的list裡面的thread。當 所有的priority都跑完以後,再去執行main,因為main的priority會等於0,所以它會被放到mrrList[0]裡面的readylist 中,所以我們去看mrrList[0]是否為空的,若非,則繼續去執行之。
(9) code/threads/thread.cc
當有thread呼叫Yield()後,加一個判斷,如果它的load不等於0的話(代表它還沒有做完),而且它所在的list卻是 empty,則將下一個要執行的thread設為它自己,讓它繼續擁有CPU。
void
Scheduler::ReadyToRun (Thread *thread) {
ASSERT(kernel->interrupt->getLevel() == IntOff);
DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
thread->setStatus(READY);
mrrList[thread->priority]->Append(thread);
}
Thread *
Scheduler::FindNextToRun () {
int i;
ASSERT(kernel->interrupt->getLevel() == IntOff);
for(i = 1; i <= 10; i ++){
if(mrrList[i]->IsEmpty() == 0){
return mrrList[i]->RemoveFront();
} }
if(mrrList[0]->IsEmpty() == 0){
return mrrList[0]->RemoveFront();
}
return NULL;
}
void
Thread::Yield () {
Thread *nextThread;
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
ASSERT(this == kernel->currentThread);
DEBUG(dbgThread, "Yielding thread: " << name);
if(kernel->currentThread->load != 0 && kernel->scheduler->mrrList[kernel->currentThread->priority]->IsEmpty() == 1){
nextThread = kernel->currentThread;
return;
}
nextThread = kernel->scheduler->FindNextToRun();
if (nextThread != NULL) {
kernel->scheduler->ReadyToRun(this);
kernel->scheduler->Run(nextThread, FALSE);
}
(void) kernel->interrupt->SetLevel(oldLevel);
}
5
(10) code/threads/kernel.cc & code/machine/interrupt.cc
根據Project2的power point第23頁,mark這兩個file裡的兩行code。分別是alarm = new Alarm(randomSlice)及 OneTick()。
中途所遭遇到的困難
一開始就想好要將不同priority的thread放到不同的readylist中,然後照順序分別對每個list去做。
問題1:
較後執行的thread,會將前一個已經結束的thread的執行tick數也加在mrrTicks中,所以可能會造成thread B才跑 一單位時間,就換下一個thread的情況。解決這個問題的方法上面已經解釋過了,就是在interrupt中判斷此時 thread是否已經做完(load是否為0),若是的話,代表它已經要結束了,所以就將mrrTicks歸零,避免影響到下一 個執行的thread。
問題2:
一開始不知道main會被放到priority是0的readylist中,所以先前在scheduler的時候就一直有錯誤。所以後來就將 priority是0的readylist在其它thread已經跑完之後,最後再執行。
問題3:
在 Scheduler::FindNextToRun ()裡面,每次都判斷 current thread 的 list 是否為空的,若為空的則做下一個 level 的 thread。但是如果此 level 還沒有 empty,但是它的 readylist 中只剩它自己,所以當我們利用
mrrList[i]->RemoveFront()去找 list 中最前面的 thread 時,同時會先將 current thread 丟到 list 的最後面,而 mrrList[i]->IsEmpty()因為沒有馬上找到最前面的 thread,所以它會以為此 list 已經空了(但實際上不是),結果此 thread就被跳過了。解決此問題的方法就是在 Yield()中加的一些判斷。因為當上述情況發生,而可能產生錯誤 之時,代表此 thread 還沒結束(load 不等於 0),而且 list 又被判定為 empty,此時原本會 Yield()掉,然後把 CPU 換給下一個 thread,此 thread 就不會結束了。所以當這些判斷情況成立的時候,我們將 nextThread 設為 currentThread,如此一來,此 thread 就可以繼續執行而不會被跳過了^0^。
※寫完此project以後,覺得了解不少其中內部運作的情形,還滿有成就感的。