現代作業系統設計的目的在於管理底層硬體資源並使整個計算機系統執行效能達到最優。我們可以發現實現這一目的的方法可以歸結為兩點:cpu虛擬化和記憶體虛擬化。現代作業系統一般都能提供多工執行環境,每個程序都可以擁有自己的虛擬cpu,程序在執行過程中感覺自己是獨佔cpu。
類似地,記憶體虛擬化是通過讓每個程序擁有虛擬位址空間,然後對映到物理記憶體。這樣程序也不知道自己的虛擬位址其實並非物理記憶體中的相應位置,而是存在乙個由虛擬位址向實體地址的對映。
cpu虛擬化是通過多個程序共享cpu實現的,具體來說,就是在乙個固定週期內,每個程序獲得一定比例的cpu時間。用來選擇可以執行的程序的演算法稱為排程器,選擇下乙個執行的程序的過程稱之為程序排程。
程序排程器可以說是現代作業系統中最重要的元件了。實現乙個排程演算法主要有兩個挑戰:一是對高優先順序的程序要特殊照顧,分配更多的cpu資源,二是要保證低優先順序的程序不會被餓死(遲遲得不到cpu資源)。此外排程器設計也需要十分精巧,可以保證執行排程器不會有太大開銷進而影響整個系統吞吐量。
對一些互動性要求很高的程序,比如乙個文字編輯程序,排程器就應該給每個程序很少的cpu時間,這樣可以快速地切換程序,從而提高程序對io的響應速度,達到更加良好的互動體驗。而對一些非互動性的程序,情況則剛好相反。程序間的切換操作開銷較大,因此給這種程序分配長cpu時間可以減少上下文切換,從而提高系統效能和吞吐量。顯然以上兩種情況存在衝突,需要程序排程器做乙個tradeoff。
linux中程序排程器已經經過很多次改進了,目前核心排程器是在cfs(completely fair scheduler),從2.6.23開始被作為預設排程器。用作者ingo molnar的話講,cfs在真實的硬體上模擬了完全理想的多工處理器。也就是說cfs試圖**cpu。理想、精確的多工cpu是乙個可以同時並行執行多個程序的硬體cpu,給每個程序分配等量的處理器功率(並非時間)。如果只有乙個程序執行,那麼它將獲得100%的處理器功率,兩個程序就是50%,依次平均分配。這樣就可以實現所有程序公平執行。
顯然這樣的理想cpu是不存在的,cfs排程器試圖通過軟體方式去模擬出這樣的cpu來。在真實處理器上,同一時間只能有乙個程序被排程執行,因此其他程序必須等待。因此當前執行的程序將會獲得100%的cpu功率,其他等待的程序無法獲得cpu功率,這樣的分配顯然是違背之前的初衷的,沒有公平可言。
cfs排程器就是為了減少系統中的這種不公平,cfs跟蹤記錄cpu的公平分配份額,用來分配給系統中的每個程序。因此cfs在一小部分cpu時鐘速度下執行乙個公平時鐘。公平時鐘增加速度通過用實際時鐘時間除以等待程序個數計算。結果就是分配給每個程序的cpu時間。
程序等待cpu的時候,排程器會跟蹤記錄它將會使用理想的處理器時間。這個等待時間用每個程序等待執行時間來表示,可以用來對程序進行排序,決定其在被搶占之前可以被分配的cpu時間。等待時間最長的程序會被首先選擇排程到cpu上執行,當這個程序執行時,它的等待時間會相應減少,其他程序等待時間自然就會增加了。這樣馬上就會有另外乙個等待時間最長的程序出現,當前執行的程序就會被搶占。基於這一原理,cfs排程器試圖公平對待所有程序並使每個程序等待時間為0,這樣每個程序就可以擁有等量的cpu資源,達到完全公平的一種理想狀態。
正如前文所說,cfs排程器試圖保證完全公平排程,cfs將cpu時間按照執行佇列裡所有的se(sched_entity)的權重分配。se的排程順序由每個task使用cpu的虛擬時間(vruntime)決定,vruntime越少,對應在佇列中的位置就越靠前,就會被盡快排程執行。cfs採用紅黑樹這一資料結構來維護task的佇列,紅黑樹的時間複雜度可以達到o(log n)。
tick中斷更新排程資訊,調整當前程序在紅黑樹中的位置,調整完成後如果當前程序不是最左邊的葉子節點(vruntime最小),就標記為需要排程。中斷返回時就會呼叫schedule()來完成程序切換。否則當前程序繼續執行。
紅黑樹是核心中一種極其重要的資料結構,cfs用來維護程序佇列,cfs中紅黑樹節點就是se,鍵值就是vruntime,vruntime由程序已經使用cpu時間、nice值和當前cpu負載共同計算而來。關於計算細節下面介紹。
程序排程實際上不是以程序為實際操作物件的,程序結構體定義在」linux/sched.h」中
struct task_struct while (cfs_rq);
p = task_of(se);
hrtick_start_fair(rq, p);
return p;
這裡呼叫了pick_next_entity來獲取下乙個程序排程實體,進一步看其呼叫的__pick_next_entity函式
static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)
struct rb_node *left = cfs_rq->rb_leftmost;
if (!left)
return null;
return rb_entry(left, struct sched_entity, run_node);
終於看到勝利的曙光了,這裡就是選擇紅黑樹最左邊的子節點了。完成選擇下乙個排程執行程序後,接下來就是上下文切換,程序排程執行了。關於上下文切換這裡不作詳細討論。
程序排程器作為多工作業系統中最重要的一部分,在linux中進行了很多次革新,目前cfs是作為核心預設的程序排程器。排程過程的實際**實現遠遠不止前文所描述的那般簡單,需要對**進行更加深入的跟蹤學習,這裡僅僅從原理和基本實現過程進行簡單介紹。
[1]robert love,《linux kernel development》,3rd edition
[2]completely fair scheduler
Linux程序排程之CFS
在linux2.6核心中,開發人員引入了一種新的排程策略,旨在解決2.5及之前的排程器在處理使用者互動式程式時延遲大的不足。這種排程器就是completely fair scheduler 簡稱cfs 排程器的任務就是從當前系統中的就緒任務中選擇合適的任務執行。排程器需要確定在什麼時候排程什麼任務,...
CFS排程器(一)
關於排程類和優先順序的概念,前面的文章 排程器概述 中已經做了介紹了,本文不在重述。本文主要關注的就是cfs排程器,或者叫做fair sched class排程類。這種排程器是被sched normal sched batch這兩種排程策略使用的。本文基於linux4.0 提到排程器涉及到兩個問題 ...
CFS排程分析 核心版本 2 6 34
cfs不再有時間片的概念,他維護的是每個程序執行的時間記賬 使用排程器實體結構來追蹤程序執行記賬 無數統計變數 但是演算法核心就是圍繞vruntime設計。排程器實體,作為程序的乙個名為se的成員變數。cfs使用vruntime變數來記錄乙個程式到底執行了多長時間以及他要應該執行多久。中,up da...