# OS 2020 HW2 Nachos Report ## Motivation ### System Call 先按照作業的說明操作,在製作sleep list的時候若能依照起床時間排序將會省事不少。 ### CPU Scheduling 根據指示,若有實作多個scheduling,則應該要在`threads/main.cc`中設計可以提供選擇的功能。 ***threads/main.cc*** : ```C++ ... int main(int argc, char **argv) { int i; char *debugArg = ""; // before anything else, initialize the debugging system for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-d") == 0) { ASSERT(i + 1 < argc); // next argument is debug string debugArg = argv[i + 1]; i++; } else if (strcmp(argv[i], "-u") == 0) { cout << "Partial usage: nachos [-z -d debugFlags]\n"; } else if (strcmp(argv[i], "-z") == 0) { cout << copyright; } } debug = new Debug(debugArg); DEBUG(dbgThread, "Entering main"); kernel = new KernelType(argc, argv); kernel->Initialize(); CallOnUserAbort(Cleanup); // if user hits ctl-C kernel->SelfTest(); kernel->Run(); return 0; } ``` 從這邊可以看到debug flag的選擇以及其他執行選項的選擇都在此處理,因此將scheduling的選擇也放在此處理感覺是個合理的選項。至於如何把選擇的結果傳給`scheduler`則是另一個問題。這邊先看到`debug`是一個明顯的選擇後傳遞資訊的部分,這是利用一個全域變數在做資訊的傳遞,因為`debug`的顯示部分相對其他結構是比較獨立的,所以在這邊並沒有太大的問題。但如果傳遞資訊給`scheduler`也使用這種方法可能就造成全域變數的混亂,因此感覺最有可能的方式是透過`kernel->Initialize()`帶入參數。修改`/threads/main.cc` ***main.cc*** : ```C++ ... int main(int argc, char **argv) { int i; char *debugArg = ""; SchedulerType scheType = RR; // before anything else, initialize the debugging system for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-d") == 0) { ASSERT(i + 1 < argc); // next argument is debug string debugArg = argv[i + 1]; i++; } else if (strcmp(argv[i], "-u") == 0) { cout << "Partial usage: nachos [-z -d debugFlags]\n"; } else if (strcmp(argv[i], "-z") == 0) { cout << copyright; } else if (strcmp(argv[i], "-sche") == 0) { ASSERT(i + 1 < argc); // next argument is scheduler type string if (strcmp(argv[i + 1], "RR") == 0) { cout << "Using Round Robin now." << endl; scheType = RR; } else if (strcmp(argv[i + 1], "FCFS") == 0) { cout << "Using First-Come-First-Service now." << endl; scheType = FCFS; } else if (strcmp(argv[i + 1], "SJF") == 0) { cout << "Using Shortest-Job-First now." << endl; scheType = SJF; } else if (strcmp(argv[i + 1], "Priority") == 0) { cout << "Using Priority now." << endl; scheType = PRIORITY; } else { cout << "No matching scheduler type! Using Round Robin by default.\n"; scheType = RR; } i++; } } debug = new Debug(debugArg); DEBUG(dbgThread, "Entering main"); kernel = new KernelType(argc, argv); kernel->Initialize(scheType); CallOnUserAbort(Cleanup); // if user hits ctl-C kernel->SelfTest(); kernel->Run(); return 0; } ``` 接著因為要實作Priority, SJF, FCFS的關係,必須給每個thread一個priority, CPU burst time, start time的欄位,在`threads/thread.h`中添加這些member以及他們的get, set method。這邊看到其實`Thread`裡面還有分成user code和kernel code的使用部分,我認為scheduling應該是屬於kernel code的部分,所以就沒有把這些欄位放在user program那區。 ***threads/thread.h*** : ```C++ ... class Thread { private: ... public: ... int getBurstTime() { return burstTime; } int getSchePriority() { return schePriority; } int getStartTime() { return startTime; } void setBurstTime(int x) { burstTime = x; } void setStartTime(int x) { startTime = x; } void setSchePriority(int x) { schePriority = x; } private: ... int schePriority; // the execution priority for the thread // the higher the more important int burstTime; // the CPU burst time of the thread int startTime; // the start time of the thread #ifdef USER_PROGRAM // A thread running a user program actually has *two* sets of CPU registers -- // one for its state while executing user code, one for its state // while executing kernel code. ... #endif }; ... ``` 根據助教指示,在`threads/thread.cc`中的`Thread::SelfTest()`加上自己要測試的程式。 ***thread.cc*** : ```C++ ... //---------------------------------------------------------------------- // SimulateTimeThread // Simulate time running causing decrease of the thread's // burst time. //---------------------------------------------------------------------- static void SimulateTimeThread() { Thread *t = kernel->currentThread; while (t->getBurstTime() > 0) { t->setBurstTime(t->getBurstTime() - 1); // CPU burst time decrease cout << "*** thread " << t->getName() << ": remaining time " << t->getBurstTime() << endl; kernel->interrupt->OneTick(); // simulate time (may // cause context switch) } } ... //---------------------------------------------------------------------- // Thread::SelfTest // Set up a ping-pong between two threads, by forking a thread // to call SimpleThread, and then calling SimpleThread ourselves. //---------------------------------------------------------------------- void Thread::SelfTest() { DEBUG(dbgThread, "Entering Thread::SelfTest"); const int thread_num = 3; char *name[thread_num] = {"A", "B", "C"}; int priority[thread_num] = {7, 4, 6}; int burst[thread_num] = {5, 19, 3}; int start[thread_num] = {1, 2, 3}; Thread *t; int i = 0; for (i = 0; i < thread_num; i ++) { t = new Thread(name[i]); t->setSchePriority(priority[i]); t->setBurstTime(burst[i]); t->setStartTime(start[i]); t->Fork((VoidFunctionPtr) SimulateTimeThread, (void *)NULL); } } ... ``` ## Implementation ### System Call 依照助教指示,在`userprog/syscall.h`之中定義`Sleep`的system call number。 ***userprog/syscall.h*** : ```c++ ... #define SC_PrintInt 11 #define SC_Sleep 12 // tu added ... /* OS HW2 * Let the thread sleep. */ void Sleep(int time); //tu add ... ``` 再來是在`test/start.s`中準備register。 ***test/start.s*** : ```assembly ... .globl ThreadFork .ent ThreadFork ThreadFork: addiu $2,$0,SC_ThreadFork syscall j $31 .end ThreadFork .globl ThreadYield .ent ThreadYield ThreadYield: addiu $2,$0,SC_ThreadYield syscall j $31 .end ThreadYield .globl PrintInt .ent PrintInt PrintInt: addiu $2,$0,SC_PrintInt syscall j $31 .end PrintInt .globl Sleep .ent Sleep Sleep: addiu $2,$0,SC_Sleep syscall j $31 .end Sleep ... ``` 組合語言沒有特別研究,但前面的幾個都一樣就跟著做吧。 之後在`userprog/exception.cc`加入`Sleep`的Exception case。 ***userprog/exception.cc*** : ```C++ ... case SC_PrintInt: val=kernel->machine->ReadRegister(4); cout << "Print integer:" <<val << endl; return; case SC_Sleep: val=kernel->machine->ReadRegister(4); cout << "Sleep for " << val << endl; kernel->alarm->WaitUntil(val); return; ... ``` 一樣照抄上面的格式,然後根據助教的提示,利用`kernel->alarm->WaitUntil()`。 依照助教的說明,先看看這份`threads/alarm.h`的註解,裡面寫到此檔案並沒有被完全實作,因此去看看`threads/alarm.cc`,發現裡面只有實作constructer以及`CallBack()`。在開始動手之前,先想想運作情形,以及需要的東西。運作的時候可能會有很多的thread去睡覺,所以應該要有一個List儲存這些睡眠中的thread。而不同的thread起床時間也不一定相同,所以也需要儲存他們各自的起床時間。綜上所述,應該是要有一個class把thread跟他的起床時間包在一起,之後再丟進List裡面儲存。在interrupt的時候,必須要看是哪些thread該起床了,如果讓thread依照起床的時間排序感覺會比較容易實作,所以使用`lib/list.h`中的`SortedList`儲存。 ***threads/alarm.h*** : ```C++ ... #include "thread.h" ... private: Timer *timer; // the hardware timer device void CallBack(); // called when the hardware // timer generates an interrupt class SleepingThread { public: SleepingThread(Thread *t, int x); Thread *sleepingT; int wakeTime; }; SortedList<SleepingThread *> *sleepSer; static int SleepingCompare(SleepingThread *x, SleepingThread *y); }; ... ``` ***threads/alarm.cc*** : ```C++ ... //---------------------------------------------------------------------- // SleepingCompare // Compare to sleeping threads based on which should wake up first. //---------------------------------------------------------------------- int SleepingCompare (Alarm::SleepingThread *x, Alarm::SleepingThread *y) { if (x->wakeTime < y->wakeTime) { return -1; } else if (x->wakeTime > y->wakeTime) { return 1; } else { return 0; } } //---------------------------------------------------------------------- // Alarm::Alarm // Initialize a software alarm clock. Start up a timer device // // "doRandom" -- if true, arrange for the hardware interrupts to // occur at random, instead of fixed, intervals. //---------------------------------------------------------------------- Alarm::Alarm(bool doRandom) { timer = new Timer(doRandom, this); // imitate the 'pending' in /code/machine/interrupt.cc Interrupt() sleepSer = new SortedList<SleepingThread *>(SleepingCompare); } //---------------------------------------------------------------------- // Alarm::~Alarm // De-allocate the data structures needed by the sleeping thread // simulation, and release the timer. // (imitate the '~Interrupt()' in /code/machine/interrupt.cc) //---------------------------------------------------------------------- Alarm::~Alarm() { delete timer; while (!sleepSer->IsEmpty()) { delete sleepSer->RemoveFront(); } delete sleepSer; } ``` 而`SortedList`的宣告及用法則是參考自`machine/interrupt.h`以及`machine/inerrupt.cc`之中`pending`相關的宣告以及method。 ***machine/interrupt.h*** : ```C++ ... SortedList<PendingInterrupt *> *pending; ... ``` ***machine/interrupt.cc*** : ```C++ ... //---------------------------------------------------------------------- // PendingCompare // Compare to interrupts based on which should occur first. //---------------------------------------------------------------------- static int PendingCompare (PendingInterrupt *x, PendingInterrupt *y) { if (x->when < y->when) { return -1; } else if (x->when > y->when) { return 1; } else { return 0; } } //---------------------------------------------------------------------- // Interrupt::Interrupt // Initialize the simulation of hardware device interrupts. // // Interrupts start disabled, with no interrupts pending, etc. //---------------------------------------------------------------------- Interrupt::Interrupt() { level = IntOff; pending = new SortedList<PendingInterrupt *>(PendingCompare); inHandler = FALSE; yieldOnReturn = FALSE; status = SystemMode; } //---------------------------------------------------------------------- // Interrupt::~Interrupt // De-allocate the data structures needed by the interrupt simulation. //---------------------------------------------------------------------- Interrupt::~Interrupt() { while (!pending->IsEmpty()) { delete pending->RemoveFront(); } delete pending; } //---------------------------------------------------------------------- // Alarm::SleepingThread::SleepingThread // Initializing the information of sleeping thread. // Including the thread and its sleeping time. //---------------------------------------------------------------------- Alarm::SleepingThread::SleepingThread(Thread *t, int x) { sleepingT = t; wakeTime = x; } ... ``` 在實作`Waituntil(int x)`的時候遇到一個問題,這個傳入的參數應該是代表幾秒,還是代表interrupt的次數呢?從`interrupt.cc`之中的`SetInterrupt()`找到`TimerTicks`這種東西,同時也找到`Schedule(...)`這個看起來可以當作提示的method。所以接著看Schedule的實作以及註解。 ***machine/interrupt.cc*** : ```C++ ... //---------------------------------------------------------------------- // Interrupt::Schedule // Arrange for the CPU to be interrupted when simulated time // reaches "now + when". // // Implementation: just put it on a sorted list. // // NOTE: the Nachos kernel should not call this routine directly. // Instead, it is only called by the hardware device simulators. // // "toCall" is the object to call when the interrupt occurs // "fromNow" is how far in the future (in simulated time) the // interrupt is to occur // "type" is the hardware device that generated the interrupt //---------------------------------------------------------------------- void Interrupt::Schedule(CallBackObj *toCall, int fromNow, IntType type) { int when = kernel->stats->totalTicks + fromNow; PendingInterrupt *toOccur = new PendingInterrupt(toCall, when, type); DEBUG(dbgInt, "Scheduling interrupt handler the " << intTypeNames[type] << " at time = " << when); ASSERT(fromNow > 0); pending->Insert(toOccur); } ... ``` 看起來`WaitUntil(int x)`的`x`到底代表的意義,在這個實作中可以得到回答。從這個實作中看起來,原作者的那個`x`代表的應該是tick,所以實作`WaitUntil(int x)`時的邏輯應該與`Schedule(...)`類似。在執行這個`WaitUntil(int x)`的時候比較要小心的事情是,不能讓這個method執行到一半被interrupt,這樣可能會錯過起床的時間,也可能導致起床的時間跟原本預期的不同。 ***threads/alarm.cc*** : ```C++ ... //---------------------------------------------------------------------- // Alarm::WatiUntil // Suspend execution until time > now + x // This function should not be interrupted. //---------------------------------------------------------------------- void Alarm::WaitUntil(int x) { // make sure interrupts were disabled IntStatus oriStat = kernel->interrupt->SetLevel(IntOff); // get the current thread & calculate wake up time Thread *currentT = kernel->currentThread; int wakeTick = kernel->stats->totalTicks + x; SleepingThread *st = new SleepingThread(currentT, wakeTick); cout << "A thread goes to sleep." << endl; // let the thread sleep, and put it to the sleeping series sleepSer->Insert( st ); currentT->Sleep(false); // cannot reach the program after it? // let the interrupt status as before kernel->interrupt->SetLevel(oriStat); } ... ``` 至此,睡覺的功能好像做得差不多了,接著要開始做起床的功能,也就是`CallBack()`的部分了。 ***threads/alarm.cc*** : ```C++ ... //---------------------------------------------------------------------- // Alarm::CallBack // Software interrupt handler for the timer device. The timer device is // set up to interrupt the CPU periodically (once every TimerTicks). // This routine is called each time there is a timer interrupt, // with interrupts disabled. // // Note that instead of calling Yield() directly (which would // suspend the interrupt handler, not the interrupted thread // which is what we wanted to context switch), we set a flag // so that once the interrupt handler is done, it will appear as // if the interrupted thread called Yield at the point it is // was interrupted. // // For now, just provide time-slicing. Only need to time slice // if we're currently running something (in other words, not idle). // Also, to keep from looping forever, we check if there's // nothing on the ready list, and there are no other pending // interrupts. In this case, we can safely halt. //---------------------------------------------------------------------- void Alarm::CallBack() { Interrupt *interrupt = kernel->interrupt; MachineStatus status = interrupt->getStatus(); // detect if any threads need to wake up bool sleepFlag = true; ListIterator<SleepingThread *> iter(sleepSer); while(!iter.IsDone()) { SleepingThread *st = iter.Item(); if (st->wakeTime <= kernel->stats->totalTicks) { sleepFlag = false; cout << "Thread wake up." << endl; kernel->scheduler->ReadyToRun(st->sleepingThread); iter.Next(); sleepSer->Remove(st); } else { break; } // because sleepSer is sortedList } DEBUG(dbgTu, "# elements in sleep serial: " << sleepSer->NumInList()) if (status == IdleMode && sleepFlag && sleepSer->IsEmpty()) { // is it time to quit? if (!interrupt->AnyFutureInterrupts()) { timer->Disable(); // turn off the timer } } else { // there's someone to preempt interrupt->YieldOnReturn(); // context switch } } ... ``` ### CPU Scheduling 首先先修復在`/threads/main.cc`中修改函式行為所造成的問題。因為`kernel`之中的`Initialize(...)`這個method有了新的參數,因此要去`kernel`之中增加新的method。先觀察一下這個`kernel`的型別,發現他屬於`KernelType *`。而`KernelType`又在`/threads/main.h`之中有定義。 ***threads/main.h*** : ```C++ ... #ifdef NETWORK // THREADS && USER_PROGRAM && NETWORK #include "netkernel.h" #define KernelType NetKernel class NetKernel; #else #ifdef USER_PROGRAM // THREADS && USER_PROGRAM #include "userkernel.h" #define KernelType UserProgKernel class UserProgKernel; #else #include "kernel.h" // THREADS #define KernelType ThreadedKernel class ThreadedKernel; #endif #endif ... ``` 問題是只看`/threads/main.h`沒辦法知道到底是哪一個`Kernel`,所以我直接make,看error message應該就可以知道了。 ***error message*** : ``` ... ../threads/main.cc:99:32: error: no matching function for call to ‘ThreadedKernel::Initialize(SchedulerType&)’ kernel->Initialize(scheType); ^ ../threads/main.cc:99:32: note: candidate is: In file included from ../threads/main.h:25:0, from ../threads/main.cc:21: ../userprog/userkernel.h:26:10: note: void ThreadedKernel::Initialize() void Initialize(); // initialize the kernel -- separated ^ ... ``` 看來缺的是`ThreadedKernel::Initialize(...)`。 ***threads/kernel.h*** : ```C++ ... class ThreadedKernel { public: ThreadedKernel(int argc, char **argv); // Interpret command line arguments ~ThreadedKernel(); // deallocate the kernel void Initialize(SchedulerType type); void Initialize(); // initialize the kernel -- separated // from constructor because // refers to "kernel" as a global ... ``` ***threads/kernel.cc*** : ```C++ ... //---------------------------------------------------------------------- // ThreadedKernel::Initialize // Initialize Nachos global data structures. Separate from the // constructor because some of these refer to earlier initialized // data via the "kernel" global variable. (Bypassing the scheduler // type) //---------------------------------------------------------------------- void ThreadedKernel::Initialize(SchedulerType type) { stats = new Statistics(); // collect statistics interrupt = new Interrupt; // start up interrupt handling scheduler = new Scheduler(type); // initialize the ready queue alarm = new Alarm(randomSlice); // start up time slicing // We didn't explicitly allocate the current thread we are running in. // But if it ever tries to give up the CPU, we better have a Thread // object to save its state. currentThread = new Thread("main"); currentThread->setStatus(RUNNING); interrupt->Enable(); } ... ``` 實作照抄前人的方法,然後make看看有沒有問題。 ***error message*** : ``` ... ../threads/main.cc:99:32: error: no matching function for call to ‘UserProgKernel::Initialize(SchedulerType&)’ kernel->Initialize(scheType); ^ ../threads/main.cc:99:32: note: candidate is: In file included from ../threads/main.h:21:0, from ../threads/main.cc:21: ../userprog/userkernel.h:26:10: note: void UserProgKernel::Initialize() void Initialize(); // initialize the kernel ^ ... ``` 看來`UserProgKernel`也要改。 ***userprog/userkernel.h*** : ```C++ ... class UserProgKernel : public ThreadedKernel { public: UserProgKernel(int argc, char **argv); // Interpret command line arguments ~UserProgKernel(); // deallocate the kernel void Initialize(SchedulerType type); // initialize the kernel void Initialize(); // initialize the kernel ... ``` ***userprog/userkernel.cc*** : ```C++ ... //---------------------------------------------------------------------- // UserProgKernel::Initialize // Initialize Nachos global data structures. //---------------------------------------------------------------------- void UserProgKernel::Initialize() { ThreadedKernel::Initialize(); // init multithreading machine = new Machine(debugUserProg); fileSystem = new FileSystem(); #ifdef FILESYS synchDisk = new SynchDisk("New SynchDisk"); #endif // FILESYS } //---------------------------------------------------------------------- // UserProgKernel::Initialize // Initialize Nachos global data structures. //---------------------------------------------------------------------- void UserProgKernel::Initialize(SchedulerType type) { ThreadedKernel::Initialize(type); // init multithreading machine = new Machine(debugUserProg); fileSystem = new FileSystem(); #ifdef FILESYS synchDisk = new SynchDisk("New SynchDisk"); #endif // FILESYS } ... ``` 老樣子,實作照抄前人的方法。 ***error message*** : ``` ... ../threads/main.cc:99:32: error: no matching function for call to ‘NetKernel::Initialize(SchedulerType&)’ kernel->Initialize(scheType); ^ ../threads/main.cc:99:32: note: candidate is: In file included from ../threads/main.h:16:0, from ../threads/main.cc:21: ../network/netkernel.h:26:10: note: void NetKernel::Initialize() void Initialize(); // initialize the kernel ^ ... ``` 看起來compiler是每個都會檢查的樣子。 ***/network/netkernel.h*** : ```C++ ... class NetKernel : public UserProgKernel { public: NetKernel(int argc, char **argv); // Interpret command line arguments ~NetKernel(); // deallocate the kernel void Initialize(SchedulerType type); // initialize the kernel void Initialize(); // initialize the kernel ... ``` ***/network/netkernel.cc*** : ```C++ ... //---------------------------------------------------------------------- // NetKernel::Initialize // Initialize Nachos global data structures. //---------------------------------------------------------------------- void NetKernel::Initialize() { UserProgKernel::Initialize(); // init other kernel data structs postOfficeIn = new PostOfficeInput(10); postOfficeOut = new PostOfficeOutput(reliability, 10); } //---------------------------------------------------------------------- // NetKernel::Initialize // Initialize Nachos global data structures. //---------------------------------------------------------------------- void NetKernel::Initialize(SchedulerType type) { UserProgKernel::Initialize(type); // init other kernel data structs postOfficeIn = new PostOfficeInput(10); postOfficeOut = new PostOfficeOutput(reliability, 10); } ... ``` 遵循古禮,make之後就沒有問題了。 接下來就是開始弄`Scheduler`的部分了。觀察`threads/thread.cc`中的`Thread::Yield()`以及`threads/scheduler.cc`中的`Scheduler::FindNextToRun ()`後,可以發現`Scheduler`決定thread執行的先後順序的方法,就是根據`Scheduler`的`readyList`中的thread順序。那也就是說只要把thread按照某些規則放進`readyList`中,就可以完成Priority, SJF, FCFS。而三者分別是根據priority, CPU burst time, start time的大小決定先後順序,看來該是`SortedList`出馬的時候了。 先在`threads/schdeduler.h`中宣告,然後在`threads/scheduler.cc`中實作後改寫`readyList`的內容。 ***threads/schdeduler.h*** : ```C++ ... int PriorityCompare (Thread *a, Thread *b); int SJFCompare (Thread *a, Thread *b); int FCFSCompare (Thread *a, Thread *b); ... ``` ***threads/schdeduler.cc*** : ```C++ ... Scheduler::Scheduler(SchedulerType type) { schedulerType = type; switch(type) { case RR: readyList = new List<Thread *>; break; case PRIORITY: readyList = new SortedList<Thread *>(PriorityCompare); break; case SJF: readyList = new SortedList<Thread *>(SJFCompare); break; case FCFS: readyList = new SortedList<Thread *>(FCFSCompare); break; } toBeDestroyed = NULL; } ... //---------------------------------------------------------------------- // PriorityCompare // Compare to threads based on their priority. //---------------------------------------------------------------------- int PriorityCompare(Thread *a, Thread *b) { if (a->getPriority() == b->getPriority()) { return 0; } else if (a->getPriority() > b->getPriority()) { return 1; } else { return -1; } } //---------------------------------------------------------------------- // SJFCompare // Compare to threads based on their CPU burst time. //---------------------------------------------------------------------- int SJFCompare(Thread *a, Thread *b) { if (a->getBurstTime() == b->getBurstTime()) { return 0; } else if (a->getBurstTime() > b->getBurstTime()) { return 1; } else { return -1; } } //---------------------------------------------------------------------- // FCFSCompare // Compare to threads based on their start time. //---------------------------------------------------------------------- int FCFSCompare(Thread *a, Thread *b) { if (a->startTime() == b->startTime()) { return 0; } else if (a->startTime() > b->startTime()) { return 1; } else { return -1; } } ... ``` ## Result ### System Call #### Experiment result 寫了兩支程式分別有不同的睡眠時間,`sleeptest1`的睡眠時間是`sleeptest2`的10倍,所以預計在`sleeptest1`起床的間隔中,`sleeptest2`會起床9~10次。無法確定的原因是因為,如果是多核心執行時,兩支程式在同一個tick進入睡眠,那`sleeptest1`起床的時間理論上會和`sleeptest2`在同一個tick,這就造成了我無法確定兩者的輸出順序。 ***test/sleeptest1.c*** : ```C #include "syscall.h" int main() { int i = 0; for ( i = 0; i < 5; i += 1) { Sleep(100000); PrintInt(1); } return 0; } ``` ***test/sleeptest2.c*** : ```C #include "syscall.h" int main() { int i = 0; for ( i = 0; i < 20; i += 1) { Sleep(10000); PrintInt(2); } return 0; } ``` 輸入`./userprog/nachos -e ./test/sleeptest1 -e ./test/sleeptest2` ``` Total threads number is 2 Thread ./test/sleeptest1 is executing. Thread ./test/sleeptest2 is executing. Sleep for 100000 A thread goes to sleep. Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:1 Sleep for 100000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:2 Sleep for 10000 A thread goes to sleep. Thread wake up. Print integer:1 Sleep for 100000 A thread goes to sleep. Thread wake up. Print integer:2 return value:0 Thread wake up. Print integer:1 Sleep for 100000 A thread goes to sleep. Thread wake up. Print integer:1 Sleep for 100000 A thread goes to sleep. Thread wake up. Print integer:1 return value:0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 500600, idle 499441, system 550, user 609 Disk I/O: reads 0, writes 0 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 ``` #### 觀察 在debug的時候發現在中放在`currentT->Sleep(false);`這行程式後面的區域好像不會被執行到。會發現這件事情是因為執行未修改的程式(見下方)時,發現結果是thread沉沉睡去,沒有要醒來的意思,而且也沒有輸出`A thread goes to sleep.`這行文字。在把輸出資訊的那行移至Sleep上方後,輸出的結果就跑出來了。雖然輸出正常了,但是thread還是沒有起床的意思,因此推測可能是在`CallBack()`出問題了,但是看了看`sleepflag`的邏輯好像找不出錯誤,那會讓nachos結束的可能就只有`sleepSer.IsEmpty()`了。於是加上一行DEBUG檢查裡面到底有沒有東西,結果是真的沒有東西,那問題很明顯就出在`WaitUntil(int x)`中的`sleepSer->Insert( st )`,顯然是這行程式並沒有起作用,有了之前輸出消失的經驗,把`sleepSer->Insert( st );`換到`currentT->Sleep(false);`的上方還真的有用,接著就一切正常了。 ***threads/alarm.cc*** : ```C++ ... //---------------------------------------------------------------------- // Alarm::WatiUntil // Suspend execution until time > now + x // This function should not be interrupted. //---------------------------------------------------------------------- void Alarm::WaitUntil(int x) { // make sure interrupts were disabled IntStatus oriStat = kernel->interrupt->SetLevel(IntOff); // get the current thread & calculate wake up time Thread *currentT = kernel->currentThread; int wakeTick = kernel->stats->totalTicks + x; SleepingThread *st = new SleepingThread(currentT, wakeTick); // let the thread sleep, and put it to the sleeping series currentT->Sleep(false); cout << "A thread goes to sleep." << endl; sleepSer->Insert( st ); // let the interrupt status as before kernel->interrupt->SetLevel(oriStat); } ... ``` ### CPU Scheduling #### Experiment result `Thread::SelfTest()`內容 ```C++ ... char *name[thread_num] = {"A", "B", "C"}; int priority[thread_num] = {7, 4, 6}; int burst[thread_num] = {5, 19, 3}; int start[thread_num] = {1, 2, 3}; ... ``` 執行`./threads/nachos -sch RR`,結果如下 ``` Using Round Robin now. *** thread A: remaining time 4 *** thread A: remaining time 3 *** thread A: remaining time 2 *** thread B: remaining time 18 *** thread B: remaining time 17 *** thread B: remaining time 16 *** thread B: remaining time 15 *** thread B: remaining time 14 *** thread B: remaining time 13 *** thread B: remaining time 12 *** thread B: remaining time 11 *** thread B: remaining time 10 *** thread C: remaining time 2 *** thread C: remaining time 1 *** thread C: remaining time 0 *** thread A: remaining time 1 *** thread A: remaining time 0 *** thread B: remaining time 9 *** thread B: remaining time 8 *** thread B: remaining time 7 *** thread B: remaining time 6 *** thread B: remaining time 5 *** thread B: remaining time 4 *** thread B: remaining time 3 *** thread B: remaining time 2 *** thread B: remaining time 1 *** thread B: remaining time 0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! ... ``` 執行`./threads/nachos -sche SJF`,結果如下 ``` Using Shortest-Job-First now. *** thread C: remaining time 2 *** thread C: remaining time 1 *** thread C: remaining time 0 *** thread A: remaining time 4 *** thread A: remaining time 3 *** thread A: remaining time 2 *** thread A: remaining time 1 *** thread A: remaining time 0 *** thread B: remaining time 18 *** thread B: remaining time 17 *** thread B: remaining time 16 *** thread B: remaining time 15 *** thread B: remaining time 14 *** thread B: remaining time 13 *** thread B: remaining time 12 *** thread B: remaining time 11 *** thread B: remaining time 10 *** thread B: remaining time 9 *** thread B: remaining time 8 *** thread B: remaining time 7 *** thread B: remaining time 6 *** thread B: remaining time 5 *** thread B: remaining time 4 *** thread B: remaining time 3 *** thread B: remaining time 2 *** thread B: remaining time 1 *** thread B: remaining time 0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! ... ``` 執行`./threads/nachos -sche FCFS`,結果如下 ``` user@user-VirtualBox:~/Documents/nachos/nachos-4.0/code$ ./threads/nachos -sche FCFS Using First-Come-First-Service now. *** thread A: remaining time 4 *** thread A: remaining time 3 *** thread A: remaining time 2 *** thread A: remaining time 1 *** thread A: remaining time 0 *** thread B: remaining time 18 *** thread B: remaining time 17 *** thread B: remaining time 16 *** thread B: remaining time 15 *** thread B: remaining time 14 *** thread B: remaining time 13 *** thread B: remaining time 12 *** thread B: remaining time 11 *** thread B: remaining time 10 *** thread B: remaining time 9 *** thread B: remaining time 8 *** thread B: remaining time 7 *** thread B: remaining time 6 *** thread B: remaining time 5 *** thread B: remaining time 4 *** thread B: remaining time 3 *** thread B: remaining time 2 *** thread B: remaining time 1 *** thread B: remaining time 0 *** thread C: remaining time 2 *** thread C: remaining time 1 *** thread C: remaining time 0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! ... ``` 執行`./threads/nachos -sche Priority`,結果如下 ``` Using Priority now. *** thread B: remaining time 18 *** thread B: remaining time 17 *** thread B: remaining time 16 *** thread B: remaining time 15 *** thread B: remaining time 14 *** thread B: remaining time 13 *** thread B: remaining time 12 *** thread B: remaining time 11 *** thread B: remaining time 10 *** thread B: remaining time 9 *** thread B: remaining time 8 *** thread B: remaining time 7 *** thread B: remaining time 6 *** thread B: remaining time 5 *** thread B: remaining time 4 *** thread B: remaining time 3 *** thread B: remaining time 2 *** thread B: remaining time 1 *** thread B: remaining time 0 *** thread C: remaining time 2 *** thread C: remaining time 1 *** thread C: remaining time 0 *** thread A: remaining time 4 *** thread A: remaining time 3 *** thread A: remaining time 2 *** thread A: remaining time 1 *** thread A: remaining time 0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! ... ``` ### 雜項 #### Makefile 增加`sleeptest1.c`以及`sleeptest2.c`的時候要如何一起編譯呢?修改`Makefile`中的`all`以及在最後加上該檔案的東東。老樣子,就是抄前人已經寫好的部分再稍微修改。 ***test/Makefile*** : ```makefile ... all: halt shell matmult sort test1 test2 test3 sleeptest1 sleeptest2 ... sleeptest1: sleeptest1.o start.o $(LD) $(LDFLAGS) start.o sleeptest1.o -o sleeptest1.coff ../bin/coff2noff sleeptest1.coff sleeptest1 sleeptest2: sleeptest2.o start.o $(LD) $(LDFLAGS) start.o sleeptest2.o -o sleeptest2.coff ../bin/coff2noff sleeptest2.coff sleeptest2 ``` #### Constructor in constructor (C++) 在與那些`Kernel`們奮鬥時,常常會想為甚麼要把同樣的程式碼複製貼上呢?想起初學程式時老師的告誡,如果寫程式要用到複製貼上,那就不是好程式。明明default constructor就是那些帶參數的constructor的特例,是不是可以重複使用呢? ***sample.cpp*** : ```C++ #include <iostream> using namespace std; class A{ public: A(int x) {a = x;} A() { A(1000); } int a; }; int main() { A *a1 = new A(5); A *a2 = new A(); cout << a1->a << " " << a2->a; return 0; } ``` ***output*** : ``` 5 0 ``` 看起來是不行。[ISO CPP FAQ](https://isocpp.org/wiki/faq/ctors#init-methods) 看看這邊的FAQ,對於亂搞一通的我氣得跟甚麼一樣。 我相信人的惰性以及對程式的美的要求,一定有一個好的解法。而且記憶中依稀記得C++中constructor是可以呼叫另一個constructor的。於是找到了下面這個解。這是在C++11之後可以使用的解法。[stack overflow](https://stackoverflow.com/questions/308276/can-i-call-a-constructor-from-another-constructor-do-constructor-chaining-in-c) ***sample.cpp*** : ```C++ #include <iostream> using namespace std; class A{ public: A(int x) {a = x;} A() : A(1000) { } int a; }; int main() { A *a1 = new A(5); A *a2 = new A(); cout << a1->a << " " << a2->a; return 0; } ``` ***output*** : ``` 5 1000 ``` ## Reference [助教投影片](https://hackmd.io/@2xu_sb9JT2KDaAH-UKS7PA/HkqwUPuOw) [偉大的學長姐](http://blog.terrynini.tw/tw/OS-NachOS-HW1/) [也是偉大的學長姐](https://morris821028.github.io/2014/05/24/lesson/hw-nachos4/) [也是偉大的學長姐](https://morris821028.github.io/2014/05/30/lesson/hw-nachos4-2/) [ISO CPP FAQ](https://isocpp.org/wiki/faq/ctors#init-methods) [stack overflow constructor](https://stackoverflow.com/questions/308276/can-i-call-a-constructor-from-another-constructor-do-constructor-chaining-in-c)