接上文:【亞嵌】Linux進程調度算法分析(一)
2.1 基於實時進程調度
Linux2.4內核維護雙向循環隊列runqueue,一旦調度時機觸發,內核重新計算當前隊列中所有進程運行權值,並從中挑選出權值最高的進程作爲當前進程投入運行。其弊端是顯而易見的:
1)調度時機觸發,重新計算runqueue中每個進程運行權值,複雜度爲O(n), 且調度性能與內核負載相關。
2)runqueue同時管理着實時進程與非實時進程(普通進程),內核通過進程屬性,如實時或非實時、實時進程優先級、用戶進程或內核線程相關因素來計算運行權值count,靈活性低,且不便於理解和維護。
從Linux2.6早期版本開始,內核進程對實時進程調度重新設計了O(1)調度器——SD/RSDL,RSDL調度器是在SD調度器基礎上的改進。Linux2.6.26內核在早期2.6內核基礎上簡化了RSDL調度器,把就緒進程隊列和過期進程隊列合併爲就緒隊列。下面結合內核代碼,給與實時進程O(1)調度器的實現(限於篇幅,本文給出核心數據結構關鍵成員的註釋)。
1)就緒進程隊列struct rq
struct rq {
/* ...... */
/* runqueue lock: */
spinlock_t lock;
/* 就緒隊列中進程個數 */
unsigned long nr_running;
/* ...... */
/* 普通進程就緒隊列 */
struct cfs_rq cfs;
/* 實時進程就緒隊列 */
struct rt_rq rt;
/* ...... */
/* 就緒隊列工作時間 */
u64 clock;
/* ...... */
/* used by load_balance */
struct task_struct *migration_thread;
struct list_head migration_queue;
/* ...... */
}
內核爲系統中每個CPU維護獨立的struct rq數據結構,在SMP環境下,CPU之間互不影響。實時進程調度的核心數據結構是struct rt_rq,定義如下:
2)實時進程就緒隊列struct rt_rq
struct rt_rq {
/* 實時進程優先級隊列 */
struct rt_prio_array active;
/* 實時進程個數 */
unsigned long rt_nr_running;
/* ... */
/* 實時進程隊列工作時間 */
u64 rt_time;
/* ... */
};
rt_rq中關鍵的數據結構在於prio_array_active,定義如下:
3)優先級隊列struct rt_prio_array
struct rt_prio_array {
/* 優先級位圖 */
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1);
/* 優先級隊列 */
struct list_head queue[MAX_RT_PRIO];
};
4)進程運行信息結構sched_info
struct sched_info {
/* cumulative counters */
unsigned long pcount; /* # of times run on this cpu */
unsigned long long cpu_time, /* time spent on the cpu */
run_delay; /* time spent waiting on a runqueue */
/* timestamps */
unsigned long long last_arrival,/* when we last ran on a cpu */
last_queued; /* when we were last queued to run */
};
sched_info維護進程運行時的實時信息,代碼作者的註釋已比較詳細,該結構數據在schedule進程切換髮生時被更新。
struct rt_prio_array成員bitmap是進程優先級隊列位圖,其大小是MAX_RT_PRIO + 1,如果某優先級就緒進程隊列不空,那麼bitmap相應的位置1,否則爲0。queue爲進程優先級隊列數組,每個進程優先級隊列用雙端循環鏈表來描述。內核尋找優先級最高的任務需要兩個簡單的BSFS彙編指令,查詢優先級隊列位圖,然後從優先級隊列數組中取出對應的優先級隊列的對頭所指向的進程,即爲下一個投入運行的進程。當進程用完了自己的時間片後,被加入active數組優先級隊列的末尾,調度任務從當前實時任務優先級隊列中取出隊首任務投入運行。實時進程調度核心數據結構之間的關係如圖2:
圖2
2.2 基於普通進程調度
Linux2.6.23內核進程調度支持CFS調度器,它從RSDL/SD中吸取了完全公平的思想,不再跟蹤進程的睡眠時間,也不再企圖區分交互式進程。它將所有的進程(普通進程)都統一對待,這就是公平的含義。CFS調度器使用紅黑樹管理就緒進程,所有狀態爲TASK_RUNNING的進程都被插入紅黑樹。在每個調度點,CFS調度器都會選擇紅黑樹的最左邊的葉子節點作爲下一個將獲得CPU的進程。由於紅黑樹是平衡樹,因此採用CFS調度器調度時間複雜度是O(lgn)。在CFS中,tick中斷首先更新調度信息。然後調整當前進程在紅黑樹中的位置。調整完成後如果發現當前進程不再是最左邊的葉子,就標記need_resched標誌,中斷返回時就會調用scheduler()完成進程切換。否則當前進程繼續佔用CPU。從這裏可以看到CFS調度器帶來的兩點變換:1)拋棄了傳統的時間片概念,進程運行權值的計算分散到tick中斷髮生時。tick中斷只需更新紅黑樹,以前的所有調度器都在tick中斷中遞減時間片,當時間片或者配額被用完時才觸發優先級調整並重新調度(參見函數update_curr()調用時機)。2)CFS爲內核搶佔調度提供完美支持。
理解CFS的關鍵就是了解紅黑樹鍵值的計算方法。該鍵值由三個因子計算而得:一是進程已經佔用的CPU時間;二是當前進程的nice值;三是當前的cpu負載。CFS調度器維護CPU級變量min_vruntime;同時,每個進程維護進程級變量vruntime。其中,min_vruntime = max (min_vruntime, vruntime),即調度前min_vruntime的數值和備選進程運行時間權值的大者。進程插入紅黑樹的鍵值爲vruntime - min_vruntime。它們的差值代表了一個進程的公平程度。該值越大,代表當前進程相對於其它進程越不公平。因此該值越大,鍵值越大,從而使得當前進程向紅黑樹的右側移動,越晚被選中。
以上,介紹了Linux2.6.26內核兩種主流調度器,2.6.26內核還爲idle進程提供了專門的調度器(idle_sched_class)。需要指出的是,進程調度首先選擇實時進程調度器,即進程總是以保證實時進程最高運行權限,如果系統中沒有實時進程,那麼纔會選擇CFS調度器進行進程調度。
Additions notes:
1)2.6.26內核對實時進程隊列運行時間進行了限制,如果某一實時進程隊列運行時間超過最大限制,內核將限制此實時進程隊列繼續佔用CPU(詳見update_curr_rt函數)。
2)2.6.26內核進程運行時間權值的分散計算點:
a.定時器中斷調用調度器計算進程調度權值
b.schedule中進程調度點
2.6內核對SMP支持的研究有待研究!
3. Linux進程調度實現代碼分析
Linux2.6.26進程調度部分核心數據結構已在上文介紹,?