在原始的 Nachos 中的 short term scheduling 採用的是 First Come, First Served (FCFS) 的機制, 在這個 Project 我們要修改這個機制成 "priority-FIFO with round-robin". 在這個機制中會有多個 Priority Queue, 每個 task 會給定 priority (0~255), priority 的數字越小說明越優先; 每次都會從 priority 較高的 queue 開始取 task 出來處理, 優先權較低的 queue 要等優先權較高的 queue 都處理完後才會輪到它們; 每個 queue 都使用 round-robin 機制來取出 task; 在這個機制中還有一個屬性 time splice, 每個 task 最多只會執行 time splice 的時間. 底下為範例:
接著運行說明如下:
Implementation:
這邊我的做法是在 ~/code/threads/thread.h 中的 class Thread (即上面的 Task) 新增了下面的屬性與方法:
- ~/code/threads/thread.h:
- class Thread {
- private:
- ...
- int priority; // New: priority to run. 0 is the highest.
- public:
- int RemainingExecutionTicks; // New: remaining execution ticks of the thread
- ...
- void MyScheduling(char*ParameterFile); // New: Parse the content of “ParameterFile”and schedule the threads defined in “ParameterFile.”
- void SetPriority(int p); // New: set priority
- int GetPriority(); // New: get priority
- void SetRemainingExecutionTicks(int t); // New: Set remaining execution ticks
- int GetRemainingExecutionTicks(); // New: Get the remaining execution ticks of the thread
- ...
- bool forceYield=false; // New: Force to yield no matter the priority
- int ext_time; // New: Execution time
- ...
- };
接著我們來看在 class Thread 新增的方法的實作, 首先直覺的 SetPriority, GetPriority, SetRemainingExecutionTicks 與 GetRemainingExecutionTicks 只是對屬性讀寫操作:
- //----------------------------------------------------------------------
- // Thread::SetPriority
- // Set priority of thread.
- //----------------------------------------------------------------------
- void
- Thread::SetPriority(int p)
- {
- priority = p;
- }
- //----------------------------------------------------------------------
- // Thread::GetPriority
- // Get priority of thread.
- //----------------------------------------------------------------------
- int
- Thread::GetPriority()
- {
- return priority;
- }
- // Thread::SetRemainingExecutionTicks(int t)
- // Set remaining execution ticks
- //----------------------------------------------------------------------
- void
- Thread::SetRemainingExecutionTicks(int t)
- {
- RemainingExecutionTicks = t;
- }
- //----------------------------------------------------------------------
- // int Thread::GetRemainingExecutionTicks()
- // Get remaining execution ticks
- //----------------------------------------------------------------------
- int
- Thread::GetRemainingExecutionTicks()
- {
- return RemainingExecutionTicks;
- }
第一行說明 Round-Robin 的 timeslice; 第二行說明有多少個 Task 會被輸入; 接著就是那些 Task 的描述包含 名稱, priority 與 執行時間.
所以也就是說在方法 MyScheduling 需要解析檔案並生成對應的 Thread 物件後開始執行這些 Tasks. 在 Thread 物件中我們透過該物件的方法 Fork 開始工作, 事實上就是把工作放到 ready queue 裡面然後讓 Schedule 物件處理後續的工作. 而原先的機制是 FCFS, 也就是先來的就先做並做到完. 而整個的工作流程如下:
而在 Fork 方法中我們需要傳入一個方法指標, 該方法指標也就是我們的 Task. 而在這個 Project 要求我們在這個方法需要做如下的動作:
- static void MyScheduleFun(int value) {
- while(kernel->currentThread->GetRemainingExecutionTicks()>0) {
- <1. Output current thread name and it’s remaining execution ticks
- e.g. Name RemainingExecutionTicks
- <2. Decrease the remaining execution ticks of the calling thread by one
- e.g. currentThread->RemainingExecutionTicks -- ;
- <3. Invoke kernel->interrupt->OneTick()
- }
- }
- //----------------------------------------------------------------------
- // MyScheduleFun
- // For testing of Thread::MyScheduling
- //----------------------------------------------------------------------
- static void
- MyScheduleFun(char *which)
- {
- // Testing here
- while(kernel->currentThread->GetRemainingExecutionTicks()>0) {
- cout << which << " " << kernel->currentThread->GetRemainingExecutionTicks() <<"\n";
- kernel->currentThread->RemainingExecutionTicks--;
- kernel->interrupt->OneTick();
- //kernel->currentThread->Yield();
- }
- }
- ~/code/threads/scheduler.h
- class Scheduler {
- public:
- ...
- Thread* TouchNextToRun(); // New: Return first thread of the ready list, if any. But not remove it from the ready list.
- ...
- private:
- //List
*readyList; // Mark to be replaced - SortedList
*readyList; // Update: queue of threads that are ready to run, but not running - ...
- };
- ~/code/threads/scheduler.cc
- ...
- //----------------------------------------------------------------------
- // ThreadCompare
- // Compare to pickup higher priority thread.
- //----------------------------------------------------------------------
- static int
- ThreadCompare (Thread *x, Thread *y)
- {
- if (x->GetPriority() < y->GetPriority()) { return -1; }
- else if (x->GetPriority() > y->GetPriority()) { return 1; }
- else { return 0; }
- }
- //----------------------------------------------------------------------
- // Scheduler::Scheduler
- // Initialize the list of ready but not running threads.
- // Initially, no ready threads.
- //----------------------------------------------------------------------
- Scheduler::Scheduler()
- {
- readyList = new SortedList
(ThreadCompare); // ThreadCompare 讓我們每次從 ready queue 取出來都是 priority 最高的! - //readyList = new List
(); - toBeDestroyed = NULL;
- }
- ...
- //----------------------------------------------------------------------
- // Scheduler::ReadyToRun
- // Mark a thread as ready, but not running.
- // Put it on the ready list, for later scheduling onto the CPU.
- //
- // "thread" is the thread to be put on the ready list.
- //----------------------------------------------------------------------
- void
- Scheduler::ReadyToRun (Thread *thread)
- {
- ASSERT(kernel->interrupt->getLevel() == IntOff);
- DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
- thread->setStatus(READY);
- readyList->Insert(thread); // SortedList 改使用 Insert 取代 Append!
- //readyList->Append(thread);
- }
- ...
上面紅色部分是我們還沒做的, 其中 Round-Robin 要求我們必須在指定 Time slice 一到就切換同 priority queue 中的 task. 切換的動作在剛剛我們改變 Scheduler 的 ready list 便可達成, 但是如何在 "指定 Time slice" 切換? 因此我們需要知道某個 task 總共執行多少時間, 這邊我們用 Thread 上面屬性 ext_time 來記錄, 但怎麼 update 這個屬性呢? 於是我寫了一個 Timer 在每一個 TimeTick 就去更新目前正在執行 Thread 的執行時間, 我在 alarm.h 新增該 SchedulerRoundRobin 類別:
- ~/code/threads/alarm.h
- ...
- class SchedulerRoundRobin: public CallBackObj{
- public:
- SchedulerRoundRobin(int ts);
- ~SchedulerRoundRobin(){delete timer;};
- Thread *monitorThd = NULL;
- int timeSlice = 1;
- private:
- Timer *timer; // the hardware timer device
- void CallBack(); // called when the hardware timer generates an interrupt
- };
- ~/code/threads/alarm.cc
- ...
- SchedulerRoundRobin::SchedulerRoundRobin(int ts)
- {
- timer = new Timer(false, this, ts);
- timeSlice = ts;
- }
- void
- SchedulerRoundRobin::CallBack()
- {
- Thread *curThd = kernel->currentThread;
- curThd->ext_time++;
- DEBUG(dbJohn, "\t[Test] SchedulerRR CallBack: Current Thread=" << curThd->name << " (" << curThd->ext_time << ")...\n");
- if(monitorThd==NULL || monitorThd!=curThd)
- {
- monitorThd = curThd;
- monitorThd->forceYield = true;
- }
- else if(monitorThd == curThd)
- {
- monitorThd->forceYield = true;
- //monitorThd->Yield();
- }
- }
就上面需求改寫如下:
- ~/code/threads/thread.cc
- void
- Thread::Yield ()
- {
- Thread *nextThread;
- IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
- ASSERT(this == kernel->currentThread);
- DEBUG(dbgThread, "Yielding thread: " << name);
- nextThread = kernel->scheduler->TouchNextToRun();
- if (nextThread != NULL) {
- if(nextThread->GetPriority()<this->GetPriority() ||
- (this->ext_time >= this->TIME_SLICE && nextThread->GetPriority()==this->GetPriority()))
- {
- this->ext_time = 0;
- kernel->scheduler->FindNextToRun();
- DEBUG(dbJohn, "\t[Test] Yielding thread=" << name << "(" << this->GetRemainingExecutionTicks() << "/"
- << this->GetPriority() <<"); Switch to thread=" << nextThread->name<< "(" << nextThread->GetRemainingExecutionTicks()
- <<"/" << nextThread->GetPriority()<<")...");
- kernel->scheduler->ReadyToRun(this);
- kernel->scheduler->Run(nextThread, FALSE);
- }
- }
- else
- {
- DEBUG(dbJohn, "\t[Test] Yielding thread=" << name <<"(" <<this->GetRemainingExecutionTicks() << "/"
- << this->GetPriority() <<")...");
- }
- (void) kernel->interrupt->SetLevel(oldLevel);
- }
- ~/code/threads/thread.cc
- ...
- void
- Thread::MyScheduling(char*ParameterFile)
- {
- DEBUG(dbJohn, "\t[Info] Entering Thread::MyScheduling");
- DEBUG(dbJohn, "\t[Info] Input test file=" << ParameterFile << "...");
- FILE *pFile;
- int timeSlice=1;
- int thdNum=0;
- pFile = fopen(ParameterFile, "r");
- if(pFile != NULL)
- {
- /*1) Parsing intput file*/
- fscanf(pFile, "%d", &timeSlice);
- fscanf(pFile, "%d", &thdNum);
- DEBUG(dbJohn, "\t[Info] Time slice=" << timeSlice << "...");
- DEBUG(dbJohn, "\t[Info] Thread number=" << thdNum << "...");
- Thread::TIME_SLICE = timeSlice;
- char ThreadName[thdNum][10];
- int ThreadPriority[thdNum];
- int ThreadRemainingExecutionTicks[thdNum];
- int i=0;
- for(i; i
- {
- fscanf(pFile, "%s %d %d", &ThreadName[i], &ThreadPriority[i], &ThreadRemainingExecutionTicks[i]);
- }
- /*2) Initialize Threads to run tasks.*/
- DEBUG(dbJohn, "\t[Info] Start testing...");
- Thread *t;
- for(i=0;i
- t = new Thread(ThreadName[i]);
- t->SetPriority(ThreadPriority[i]);
- t->SetRemainingExecutionTicks(ThreadRemainingExecutionTicks[i]);
- t->Fork((VoidFunctionPtr)MyScheduleFun, (char *)ThreadName[i]);
- }
- SchedulerRoundRobin *srr = new SchedulerRoundRobin(1);
- kernel->currentThread->Yield(); //* Give up CPU
- fclose(pFile);
- }
- else
- {
- DEBUG(dbJohn, "\t[Error] Fail to open file=" << ParameterFile << "!");
- }
- }
- ~/code/threads/main.cc
- ...
- int
- main(int argc, char **argv)
- {
- ...
- bool mySchedulingFlag = false; // New: flag of -S
- char *ParameterFile = ""; // New: Record test file name
- ...
- else if (strcmp(argv[i], "-S") == 0) {
- ASSERT(i + i < argc);
- //DEBUG(dbJohn, "\t[Info] RR Scheduling testing!" << "\n");
- ParameterFile = argv[i + 1];
- mySchedulingFlag = true;
- i++;
- }
- ...
- if (mySchedulingFlag) {
- kernel->currentThread->MyScheduling(ParameterFile);
- }
- ...
- }
考慮我們有測試輸入檔案內容如下:
- test.txt
接著執行下面命令:
Supplement:
* NTU Operating Systems (902 36700), 2013 Spring
* Project2 specification
沒有留言:
張貼留言