對Nachos執行緒排程的探討和改進
nachos執行緒排程例項剖析
為了有效的管理CPU資源在處理Nachos執行緒排程的修改問題上,首先應該著重考慮這樣幾個問題:何時進行執行緒排程?遵循何種規則完成排程?以及排程過程中需要完成哪些工作?同時要兼顧執行緒排程的考量標準,即響應時間、週轉時間、CPU吞吐量等。
排程策略的選擇取決於系統的型別,Nachos作為一個分時系統,若將其改為搶佔式排程,其排程策略的主要目標應該達到如下內容:
(1)加快系統和使用者之間的互動速度;
(2)保證系統的執行平穩和一定的系統效率。
這兩個目標之間是有衝突的,加快響應速度必然要求在短的時間內執行多道使用者程式,而頻繁的執行緒切換會增加系統開銷,降低系統效率。
考慮到以上排程策略的主要目標,併兼顧各方面的問題,同時參考Linux以及Unix的系統程序排程策略,在Nachos的專案實踐過程中,筆者具體採用“時間片輪轉+動態優先順序排程”的演算法策略。但是在具體的實現中還需要衡量如下幾個問題:
(1)選擇合適的時間片大小。時間片大小的選擇對系統性能非常重要,也直接影響到執行緒動態優先順序的計算。在原本的Nachos作業系統中,已經設定了一個計時數值常量Timerticks,因而我們可以應用該常量作為時間片大小的量度。然而又考慮到實際執行的執行緒可能需要更多的處理機時間,而更加連續的執行,我們將時間片大小由原本的100個時鐘滴答調整為200,客觀上使執行緒能在每個時間片內獲得更有效的處理。
(2)避免頻繁的執行緒切換。一般的策略是保證執行執行緒佔用處理機後在一段時間內不被強迫排程失去處理機。基於這種理念,筆者在修改Nachos執行緒排程內容的時候,為系統設定一個記錄上次排程時間的變數lastSwitchTick,同時設定了一個數值常量MinSwitchPace用來標示兩次搶佔排程的最小時間間隔,排程時根據上次排程時間和當前時間的差決定是否排程,如果這個差值小於兩次搶佔排程的最小時間間隔,則不發生執行緒的排程,即在下一個時間片仍然讓當前執行緒佔用處理機。這樣就在客觀上保證了系統執行的穩定並節省了系統執行所需消耗的資源。然而由於系統時間統計上的不精確性,其效果有待進一步的測試與驗證。
(3)選擇合適的優先順序計算策略。在對Nachos作業系統的執行緒排程修改過程中,筆者所採用的是如下的優先順序計算策略:
a) 按執行緒的型別確定其初始優先順序:一般系統執行緒的優先順序高於使用者執行緒;執行緒在核心態下執行時的優先順序高於在使用者態下執行的優先順序;由阻塞狀態喚醒的執行緒優先順序高於剛建立執行緒優先順序;實時性要求高的執行緒的優先權高於實時性要求低的執行緒的優先權;
b) 按執行緒提交的時間次序確定:一般先提交的執行緒優先順序較高(FCFS原則);
c) 按作業要求的資源型別和數量確定:一般要求資源較少的執行緒有較高的優先順序,這樣有助於提高系統的吞吐量;
以上是對於Nachos作業系統靜態優先順序的分配原則,然而由於在此筆者選擇的是動態優先順序的排程策略,所以執行緒優先權線上程建立後不是固定不變的,而是根據系統的實際執行狀態而不斷變化的。一般動態優先權排程演算法的優先權計算遵循如下幾個原則:
e) 連續佔用處理機時間長的執行緒,優先權相應降低。線上程切換排程時,這種執行緒被排程上處理機的機會少;
i) 在較長時間未使用處理機或者雖然頻繁使用處理機,j) 但每次使用時間很短的執行緒,k) 在動態調整的過程中,l) 其執行緒優先權被相應的提高。線上程切m) 換排程時,n) 這種執行緒被排程佔用處理機的機會增加;
對於動態優先權排程,它能夠及時根據執行緒的不同狀態及執行情況公平合理的對系統進行優化,然而它的一個缺點是每次調整系統開銷比較大,這一方面取決於動態優先數計算的複雜程度,另一方面也與計算的頻繁度有關。因而在計算優先權時機的選擇上,一般至少應該保證重新計算當前執行執行緒的優先權的時間間隔不低於一個固定而合理的數值,並且優先調整一些有可能排程上處理機的執行緒的優先權,而不是將系統中所有執行緒的優先權都重新計算。
綜合以上的因素,在設計執行緒排程的具體過程中,筆者實現瞭如下內容:
時間片輪轉的實現
設定執行緒佔用處理機的時間片大小為200。實現“時間片輪轉”的執行緒排程機制,線上程資料結構中增加“已使用時間片計數TimerAlready”這樣一個變數。同時在“執行緒建立”、“時鐘中斷”、“執行緒切換”等相關細節中,增加對這個資料成員的維護性程式碼。
在排程類中增加lastSwitchTick資料成員用來記錄上次執行緒切換的時間,並在其他相關函式中增加對該資料成員的維護。
在scheduler檔案中增加對於常量數值“兩次搶佔排程的最小時間間隔MinSwitchPace”的定義,在mipssim.cc中的Run()函式中增加控制搶佔排程最短時間間隔的程式碼,即如果總執行時間到達時間片的限制並且距上次排程的時間間隔不小於兩次搶佔排程的最小時間間隔,則將設定搶佔優先順序的標誌位置位用來設定時間片輪轉的限制。這樣就可以保證執行緒執行的巨集觀連續性,而不至於讓處理機頻繁進行排程而浪費系統資源。
優先順序排程的實現
在Thread類中增加執行緒優先順序資料成員priority,並增加對優先順序數值常量的定義。執行緒建立時的優先順序CreatePriority設定為50,睡眠喚醒後執行緒的優先順序BlockedPriority設定為80。同時在Thread類的建構函式裡增加對priority資料成員的初始化操作。
在List類中增加UpdateKey()函式。用來更新連結串列中各個元素的key值(該函式主要用來調整就執行緒列表中各個執行緒的優先順序)。
在Thread類中增加FlushPorioty()函式,用來調整在時間片用完時所有就緒執行緒以及當前執行緒的優先順序。所有就緒執行緒的優先順序進行調整,計算公式為:Priority=Priority-AdaptPace;
當前執行緒的優先順序調整公式為:Priority = Priority - (當前系統時間 - lastSwitchTick ) /20
改寫Scheduler類中的ReadyToRun()函式,將原來函式中的Append()函式改為SortedInsert()函式,以便使Thread類中的Fork()函式能夠將新執行緒初始化之後按照優先順序插入就緒執行緒佇列中(原來的Append()的功能是直接將執行緒放入就緒對列的尾部,這樣不利於就緒佇列按照優先順序進行排序)。
改寫Thread類中的Yield()函式,使之按照優先順序進行排程。先將當前執行緒按照優先順序插入就緒佇列中,再從就緒佇列中取出優先順序最高的執行緒將其作為下一個執行的執行緒。
優先順序和時間片的綜合考慮
在interrupt類的OneTick()函式中新增更新已用時間片計數的程式碼,使系統執行使用者執行緒的時候,TimerAlready便累計當前執行緒所用的時鐘滴答總數。同時在要進行執行緒排程的條件分支中,增加程式碼用於實現如下操作:調整所有執行緒的優先順序、將已用時間片計數清零、更新“上次執行緒切換時間”這個資料成員。
受阻塞執行緒的排程
在Thread檔案中增加列舉型別SleepReasons用來定義各種阻塞的原因。同時在Thread類中增加執行緒資料成員sleepReason來記錄執行緒睡眠的原因,並增加函式成員SetSleepReason()來設定執行緒的睡眠原因。同時在Thread類中的建構函式中增加對sleepReason資料成員的初始化(初始設定為沒有睡眠),並新增SetSleepReason()函式的具體實現。在排程類中宣告一個阻塞佇列sleepList,用來記錄所有處於阻塞狀態的執行緒。在排程的部分加入阻塞排程,即將當前已經標記為阻塞狀態的執行緒新增到阻塞佇列中。在scheduler的類定義中加入ReadyToSleep()函式,用以實現將當前執行的執行緒設定成阻塞,並插入阻塞佇列的功能。
在scheduler類中的Run()函式中增加相應程式碼,使受阻塞執行緒(或者已執行完執行緒)被切換出CPU時,系統對其優先順序的調整,同時將其已用時間片計數清零,並更新“上次執行緒切換時間”這個資料成員。
為Condition類中的Signal()函式新增具體的函式體,使該函式實現喚醒一個等待該條件變數的執行緒的功能。
總結
以上是對執行緒狀態的簡要分析以及對Nachos作業系統排程演算法的簡單改進。如果系統中執行緒執行的時間有很大的差別,可以採用多個時間片就緒佇列來代替現有系統中的執行緒就緒佇列。為提高吞吐率,系統總是對需要時間片較小的執行緒優先進行處理,而長執行緒一旦在處理機上執行,就會佔據較長時間,避免了多次執行緒切換而必須的現場保護的系統開銷。同樣的,執行緒優先順序的計算也可以進一步改進。