1. 程式人生 > >執行緒排程實驗報告_Nachos Lab2

執行緒排程實驗報告_Nachos Lab2

         本次Lab針對的內容瞭解執行緒的排程機制,每個時刻每個CPU上只能有一個執行緒執行,為了提高系統的吞吐量和工作效率,必須合理的安排每個執行緒上CPU的順序和時間。在實用中,多數為幾種排程策略結合使用的。包括是否搶佔、動態優先順序還是靜態優先順序、是否分時等等。

         排程策略的實施通常需要在PCB中增加相應的修改,需要在其中增加相應的資料結構記錄執行的狀態和統計資訊。

【用簡潔的語言描述本次lab的主要內容;闡述本次lab中涉及到的重要的概念,技術,原理等,以及其他你認為的最重要的知識點。這一部分主要是看大家對lab的總體的理解。

要求:簡潔,不需要面面俱到,把重要的知識點闡述清楚即可。】

Exercise1

Exercise2

Exercise3

完成情況

Y

Y

Y

         Exercise1

瞭解Linux或Windows中採用的程序/執行緒排程演算法。

我調研的是Linux系統中的程序/執行緒排程演算法。Linux的核心有三種排程策略:

1.   SCHED_OTHER分時排程策略。

2.   SCHED_FIFO實時排程策略,先到先服務。一旦佔用CPU則一直執行直到有更高優先順序任務達到或自己放棄。

3.   SCHED_RR實時排程策略,時間片輪轉。當程序的時間片用完,系統將重新分配時間片,並置於就緒佇列。放在隊尾保證了所有具有相同優先順序的RR任務的排程公平。

Linux核心將程序分成兩個級別:普通程序和實時程序。實時程序的優先順序都高於普通進,除此之外,它們的排程策略也有所不同。

如果一個程序有實時需求(它是一個實時程序),則只要它是可執行狀態的,核心就一直讓它執行,以儘可能地滿足它對CPU的需求,直到它完成所需要做的事情,然後睡眠或退出(變成非可執行狀態)。而如果有多個實時程序都處於可執行狀態,則核心會先滿足優先順序最高的實時程序對CPU的需要,直到它變為非課執行狀態。於是,只要有高優先順序的實時程序一直處於可執行狀態,低優先順序的實時程序就一直不能得到CPU;只要一直有實時程序處於可執行狀態,普通程序就一直不能得到CPU。後來核心進行了一些改動,限定了以sched_rt_period_us為週期的時間內,實時程序最多隻能執行sched_rt_runtime_us這樣給普通程序留下了一點點能夠得到執行的機會。

在Linux 2.6版本中,核心排程曾有幾度變遷,其基本思想是:1)提高實時程序排程相應比;2)普通程序排程體現“完全公平這個思想”。為實時程序(SCHED_FIFO/SCHED_RR)提供O(1)時間複雜度的排程演算法,同時,為了兼顧“完全公平”這一設計思路,設計了CFS(Complete Fair Schedule)排程器,為普通程序提供滿足公平性為原則的O(lgn)時間複雜度的排程演算法.

         Exercise2

閱讀下列原始碼,理解Nachos現有的執行緒排程演算法。

code/threads/scheduler.h和code/threads/scheduler.cc

code/threads/switch.s

code/machine/timer.h和code/machine/timer.cc

scheduler.h和scheduler.cc

scheduler.h定義了執行緒排程用到的一些資料結構,主要包括了一個就緒佇列的列表,還有一些關於操作就緒佇列的方法,比如找到一個可以執行的執行緒,執行一個執行緒,將一個執行緒設定為Ready並加入就緒佇列的末尾,列印就緒佇列的內容等。

scheduler.cc中實現了這些函式,值得一提的是Run()函式。它的執行步驟如下:

1.      儲存當前執行緒為舊執行緒;

2.      如果是使用者程式,則儲存當前CPU暫存器的狀態;

3.      檢查當前執行緒是否有存在棧溢位;

4.      將nextThread的狀態設定為執行態,並作為currentThread執行執行緒;

5.      切換到nextThread執行緒執行;

6.      如果有threadToBeDestroyed執行緒,則銷燬它,並釋放它所佔用的空間;

7.      如果是使用者程式,恢復當前CPU暫存器的狀態。

從scheduler可以看出來,現有的Nachos的排程只是簡單的從readylist中取出第一個已經就緒的執行緒。

switch.s

這裡定義了執行緒交換的有關內容,執行緒的上下文切換部分涉及到暫存器的分配,因此這部分內容直接依賴於機器。Nachos目前支援四種類型的機器架構。檔案中定義了對這四種機器的兩個操作,分別是ThreadRoot()和SWITCH()。

Nachos中,除了main執行緒外,所有其他的執行緒都是從ThreadRoot入口執行的,它的語法是ThreadRoot(int InitialPC,int InitialArg,int WhenDonePC,intStartupPC),其中InitialPC指明新生成執行緒的入口函式地址,InitialArg是該入口函式的引數,StartupPC是在執行該執行緒時需要作的一些初始化工作;而WhenDonePC是該執行緒執行結束時需要作的一些後續工作。在Nachos原始碼中,沒有任何一個函式和方法顯式地呼叫ThreadRoot函式,ThreadRoot函式只有在被切換時才被呼叫到。這幾個引數是在生成執行緒分配棧空間時被初始化。

Switch函式則相對簡單,首先儲存了原執行執行緒的狀態,其次恢復新執行執行緒的狀態,最後在新執行的執行緒的棧空間上執行新執行緒。

timer.h和timer.cc

這裡定義了一個模擬硬體時間的資料結構。硬體計時器每隔一定的時間產生一個CPU中斷,我們可以利用這個中斷實現分時。

Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom)初始化這個模擬時鐘。引數timerHandler表示時鐘中斷處理函式,callArg是timerHandler函式的引數,doRandom表示是否允許中斷隨機發生,而不是預先制定好。

變數初始化完成後,該函式把一個時鐘中斷插入等待處理的中斷佇列,當時鍾中斷時刻到來時,呼叫TimerHandler函式,呼叫它的TimerExpired方法,該方法將新的時鐘中斷插入等待處理中斷佇列中,然後再呼叫真正的時鐘中斷處理函式。

TimerExpired()該函式將新的時鐘中斷插入等待處理中斷佇列中,然後再呼叫真正的時鐘中斷處理函式,這樣Nachos就可以定時收到時鐘中斷。

TimerOfNextInterrupt()該函式返回距離下一次中斷髮生所需要的時鐘週期數。

         Exercise3

         擴充套件執行緒排程演算法,實現基於優先順序的搶佔式排程演算法。

設計思路:

要實現優先順序,首先需要線上程的PCB結構中增加一個priority變數,以表示優先順序,之後要定義優先順序數和優先順序大小的關係。因為看到list.cc中有一個SortedInsert(void*item,int sortKey)方法,該方法根據sortKey的從小到大向list插入元素。於是我定義優先數為0的執行緒優先順序最高,優先數8的優先順序最低。每次向readyList插入執行緒的時候,呼叫SortedInsert()函式,將優先順序高的插入隊首。

因為我設定的是靜態優先順序演算法,當有新執行緒加入時,可能新加入的執行緒可能會比原來執行緒的優先順序高,所以這裡需要讓當前執行緒退出CPU,重新選擇一次優先順序最高的執行緒。(而不在是在Threadtest測試程式碼中執行緒自己主動退出)。

還有一些修改在下面部分展示。

對程式碼進行的修改:

Thread.h和Thread.cc中增加int型別的priority變數,範圍為0到8。0的優先順序最高,8的最低。並定義getPriority()方法提供對該私有變數的訪問。


並且對Yield()方法進行修改,使得現將當前執行的執行緒加入就緒佇列,然後在選擇新的執行緒,防止出現當前執行執行緒依舊是優先順序最高的執行緒而沒有排程到這個執行緒的情況。


在scheduler.cc的ReadyToRun()方法中,不再是每次都將執行緒加入就緒佇列的最末尾,而是根據執行緒的優先順序,插入就緒佇列中合適的位置。

有兩種情況會呼叫到ReadyToRun():

1.      新執行緒加入

2.      currentThread執行緒退出時,呼叫了Yield()方法。

第一種情況下需要當前執行緒停止執行,重新在readyList中找優先順序最高的執行緒。第二種情況下不能再呼叫Yield()方法,否則會出現兩個方法相互迴圈呼叫的情況。所以增加以一些判斷。


修改ThreadTest.cc

將SimpleThread()中的currentThread->Yield()註釋掉,不再讓當前執行緒主動讓出CPU。並修改輸出,使其輸出執行緒的優先順序。

編寫測試函式,生成三個執行緒,分別呼叫SimpleThread方法,觀察執行的先後順序,因為執行緒的優先順序從大到小如下:執行緒2、執行緒3、執行緒1.。所以執行的順序應為2->3->1。觀察結果。


為了進一步驗證搶佔的有效性,我又修改了ThreadTest.cc,使確保線上程1執行中執行緒2才生成,觀察是否搶佔了執行緒1

在PriorityScheduler()中,只生成了一個執行緒,呼叫了p1()。在p1()中有五次迴圈輸出,其中第一次的時候生成一個優先順序更高的thread2。並排程執行p2()函式。


p2()同樣是五次迴圈,第一次的時候又生成一個thread3執行緒,thread3執行緒的優先順序在兩個執行緒的中間。thread3執行SimpleThread()方法,簡單的迴圈輸出五次,其中SimpleThread()方法把currentThread->Yield()給註釋掉,不再讓執行緒自己主動讓出CPU。


執行結果如圖:


可見,Thread2、Thread3成功的搶佔了Thread1得以執行。

再一次修改Thread3的執行緒優先順序,使得Thread3>Thread2>Thread1.執行結果如下:


可見,根據優先順序的搶佔依然成功。

         【對於閱讀程式碼類的exercise,請對其中你認為重要的部分(比如某檔案,或某個類、或某個變數、或某個函式……)做出說明。

         對於要程式設計實現的exercise,請對你增加或修改的內容作出說明。如果增加或修改了某個類,請寫出這個類寫在那個檔案中,它的功能是什麼,你自己新增或修改的成員變數以及成員函式有哪些,它們的功能是什麼;特別的,對於複雜的函式,請說明你的實現方法。不需要貼具體的實現程式碼。

要求:表述清楚即可, 可採用圖表來輔助說明,不需要貼程式碼】

困難1

對作業系統中斷處理過程的不熟悉,用中斷完成該實驗的話會更理想,但是由於對中斷本身的及Nachos實現中斷的程式碼不熟悉。沒有在實驗中體現出來中斷。這個還需要繼續看資料和程式碼。

【描述你在實習過程中遇到的困難,是與實習直接相關的技術方面的難題。突出說明你是如何攻克這些難關的。

要求:只需寫一下有較深體會的困難。如果覺得整個過程都比較簡單的話此部分可不用寫。】

相信通過作業系統課的實習,我寫C++程式碼的能力會有顯著的提升,以前都是寫Java的web,對C++忘的差不多了,通過這次的實驗,可以撿回來不少。

另一個就是對執行緒的排程瞭解更加的透徹,相信可以通過以後的實驗,對作業系統的各種機制有更深入的瞭解。

         【自己的收穫,任何關於實習的感想,可以是技術方面的或非技術方面的,可隨意發揮。

要求:內容不限,體裁不限,字數不限,多多益善,有感而發。】

目前來講,我覺得一切還都挺好的,老師講的很不錯,進度也可以,沒有什麼建議。

         【請寫下你認為課程需要改進的地方,任何方面,比如進度安排、難易程度、課堂講解、考核方式、題目設定……甚至如果你認為原始碼哪裡寫得不好也歡迎提出。

         各位同學反饋的資訊對課程建設會有極大幫助。】

[1]      kouu’s home linux程序排程淺析

[2]      佚名 Nachos中文教程

【我們希望大家不要使用複製貼上來拼湊你的報告。詳細地列出你在完成lab的過程中引用的書籍,網站,講義,包括你諮詢過的大牛們。

要求:誠實,格式儘量按照論文的要求,請參考“論文參考文獻格式.doc”】