加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 調(diào)度的發(fā)展歷史
    •  
    • 實際運行時間
    • 虛擬運行時間
    •  
    • CFS 數(shù)據(jù)結(jié)構(gòu)
    •  
    • CFS 算法實現(xiàn)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

Linux 進程管理之CFS調(diào)度器

2021/05/13
224
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

調(diào)度的發(fā)展歷史

字段 版本
O(n) 調(diào)度器 linux0.11 - 2.4
O(1) 調(diào)度器 linux2.6
CFS調(diào)度器 linux2.6至今

O(n) 調(diào)度器是在內(nèi)核2.4以及更早期版本采用的算法,其調(diào)度算法非常簡單和直接,就緒隊列是個全局列表,從就緒隊列中查找下一個最佳任務,由于每次在尋找下一個任務時需要遍歷系統(tǒng)中所有的任務(全局列表),因此被稱為 O(n) 調(diào)度器(時間復雜度)。

內(nèi)核2.6采用了O(1) 調(diào)度器,讓每個CPU維護一個自己的就緒隊列,從而減少了鎖的競爭。就緒隊列由兩個優(yōu)先級數(shù)組組成,分別是active優(yōu)先級數(shù)組和expired優(yōu)先級數(shù)組。每個優(yōu)先級數(shù)組包含140個優(yōu)先級隊列,也就是每個優(yōu)先級對應一個隊列,其中前100個對應實時進程,后40個對應普通進程。如下圖所示:

這樣設(shè)計的好處,調(diào)度器選擇下一個被調(diào)度任務就變得高效和簡單多了,只需要在active優(yōu)先級數(shù)組中選擇優(yōu)先級高,并且隊列中有可運行的任務即可。這里使用位圖來定義該隊列中是否有可運行的任務,如果有,則位圖中相應的位就會被置1。這樣選擇下一個被調(diào)用任務的時間就變成了查詢位圖的操作。

  • 但上面的算法有個問題,一個高優(yōu)先級多線程的應用會比低優(yōu)先級單線程的應用獲得更多的資源,這就會導致一個調(diào)度周期內(nèi),低優(yōu)先級的應用可能一直無法響應,直到高優(yōu)先級應用結(jié)束。CFS調(diào)度器就是站在一視同仁的角度解決了這個問題,保證在一個調(diào)度周期內(nèi)每個任務都有執(zhí)行的機會,執(zhí)行時間的長短,取決于任務的權(quán)重。下面詳細看下CFS調(diào)度器是如何動態(tài)調(diào)整任務的運行時間,達到公平調(diào)度的。

 

實際運行時間

CFS是Completely Fair Scheduler簡稱,即完全公平調(diào)度器。CFS調(diào)度器和以往的調(diào)度器不同之處在于沒有時間片的概念,而是公平分配cpu使用的時間。例如:2個相同優(yōu)先級的進程在一個cpu上運行,那么每個進程都將會分配50%的cpu運行時間。這就是要實現(xiàn)的公平。

但現(xiàn)實中,必然是有的進程優(yōu)先級高,有的進程優(yōu)先級低。CFS調(diào)度器引入權(quán)重的概念,用權(quán)重代表進程的優(yōu)先級,各個進程按照權(quán)重的比例分配cpu的時間。比如:2個進程A和B。A的權(quán)重是1024,B的權(quán)重是2048。那么A獲得cpu的時間比例是1024/(1024+2048) = 33.3%。B進程獲得的cpu時間比例是2048/(1024+2048)=66.7%。

在引入權(quán)重之后,分配給進程的時間計算公式如下:

實際運行時間 = 調(diào)度周期 * 進程權(quán)重 / 所有進程權(quán)重之和

CFS調(diào)度器用nice值表示優(yōu)先級,取值范圍是[-20, 19],nice和權(quán)重是一一對應的關(guān)系。數(shù)值越小代表優(yōu)先級越大,同時也意味著權(quán)重值越大,nice值和權(quán)重之間的轉(zhuǎn)換關(guān)系:

const int sched_prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
}; 

數(shù)組值計算公式是:weight = 1024 / 1.25nice。

公式中的1.25取值依據(jù)是:進程每降低一個nice值,將多獲得10% cpu的時間。公式中以1024權(quán)重為基準值計算得來,1024權(quán)重對應nice值為0,其權(quán)重被稱為NICE_0_LOAD。默認情況下,大部分進程的權(quán)重基本都是NICE_0_LOAD。

虛擬運行時間

根據(jù)上面的理解,這里看個例子。假如一個CPU的調(diào)度周期是6ms,進程A和B的權(quán)重分別是1024和820(nice值分別是0和1),那么進程A獲得的運行時間是6x1024/(1024+820)=3.3ms,進程B獲得的執(zhí)行時間是6x820/(1024+820)=2.7ms。進程A的cpu使用比例是3.3/6x100%=55%,進程B的cpu使用比例是2.7/6x100%=45%。(符合上面說的“進程每降低一個nice值,將多獲得10% CPU的時間”)

很明顯,2個進程的實際執(zhí)行時間是不相等的,但是CFS想保證每個進程運行時間相等。因此CFS引入了虛擬時間的概念,也就是說上面的2.7ms和3.3ms經(jīng)過一個公式的轉(zhuǎn)換可以得到一樣的值,這個轉(zhuǎn)換后的值稱作虛擬時間。這樣的話,CFS只需要保證每個進程運行的虛擬時間是相等的即可。虛擬時間vriture_runtime和實際時間(wall time)轉(zhuǎn)換公式如下:

虛擬運行時間 = 實際運行時間 * NICE_0_LOAD / 進程權(quán)重 = (調(diào)度周期 * 進程權(quán)重 / 所有進程權(quán)重之和) * NICE_0_LOAD / 進程權(quán)重 = 調(diào)度周期 * 1024 / 所有進程總權(quán)重

從公式可以看出,在一個調(diào)度周期里,所有進程的虛擬運行時間是相同的。所以在進程調(diào)度時,只需要找到虛擬運行時間最小的進程調(diào)度運行即可。

為了能夠快速找到虛擬運行時間最小的進程,Linux 內(nèi)核使用紅黑樹來保存可運行的進程。CFS跟蹤調(diào)度實體sched_entity的虛擬運行時間vruntime,將sched_entity通過enqueue_entity()和dequeue_entity()來進行紅黑樹的出隊入隊,vruntime少的調(diào)度實體sched_entity排列到紅黑樹的左邊。

如上圖所示,紅黑樹的左節(jié)點比父節(jié)點小,而右節(jié)點比父節(jié)點大。所以查找最小節(jié)點時,只需要獲取紅黑樹的最左節(jié)點即可。

相關(guān)步驟如下:

  1. 每個sched_latency周期內(nèi),根據(jù)各個任務的權(quán)重值,可以計算出運行時間runtime;運行時間runtime可以轉(zhuǎn)換成虛擬運行時間vruntime;根據(jù)虛擬運行時間的大小,插入到CFS紅黑樹中,虛擬運行時間少的調(diào)度實體放置到左邊;在下一次任務調(diào)度的時候,選擇虛擬運行時間少的調(diào)度實體來運行(pick_next_task從就緒隊列中選擇最適合運行的調(diào)度實體,即虛擬時間最小的調(diào)度實體);

 

CFS 數(shù)據(jù)結(jié)構(gòu)

  • task_struct: 任務描述符,包含很多進程相關(guān)的信息,例如,優(yōu)先級、進程狀態(tài)以及調(diào)度實體等。
struct task_struct {
    ...
    struct sched_entity se;
    ...
}
  • cfs_rq:跟蹤就緒隊列信息以及管理就緒態(tài)調(diào)度實體,并維護一棵按照虛擬時間排序的紅黑樹。tasks_timeline->rb_root是紅黑樹的根,tasks_timeline->rb_leftmost指向紅黑樹中最左邊的調(diào)度實體,即虛擬時間最小的調(diào)度實體。
struct cfs_rq {
  ...
  struct rb_root_cached tasks_timeline
  ...
};
  • sched_entity:可被內(nèi)核調(diào)度的實體。每個就緒態(tài)的調(diào)度實體sched_entity包含插入紅黑樹中使用的節(jié)點rb_node,同時vruntime成員記錄已經(jīng)運行的虛擬時間。
struct sched_entity {
  ...
  struct rb_node    run_node;      
  ...
  u64          vruntime;              
  ...
};

這些數(shù)據(jù)結(jié)構(gòu)的關(guān)系如下圖所示:

 

CFS 算法實現(xiàn)

  1. 時鐘中斷 scheduler_tick 更新虛擬運行時間,檢查是否需要搶占。

  • 更新運行時的各類統(tǒng)計信息,比如vruntime, 運行時間、負載值、權(quán)重值等。檢查是否需要搶占,主要是比較運行時間是否耗盡,以及vruntime的差值是否大于運行時間等。
  1. 任務出隊入隊

當任務進入可運行狀態(tài)時,用 enqueue_task_fair 將調(diào)度實體放入到紅黑樹中,完成入隊操作;當任務退出可運行狀態(tài)時,用 dequeue_task_fair 將調(diào)度實體從紅黑樹中移除,完成出隊操作;隊操作。

調(diào)用 __enqueue_entity 函數(shù)后,就可以把進程調(diào)度實體插入到運行隊列的紅黑樹中。同時會把紅黑樹最左端的節(jié)點緩存到運行隊列的 rb_leftmost 字段中,用于快速獲取下一個可運行的進程。

  1. 從 cfs_rq 中獲取下一個可運行的任務

每當進程任務切換的時候,也就是schedule函數(shù)執(zhí)行時,調(diào)度器都需要選擇下一個將要執(zhí)行的任務。在CFS調(diào)度器中,是通過 pick_next_task_fair 函數(shù)完成的,其本質(zhì)是從就緒隊列中選擇最適合運行的調(diào)度實體(虛擬時間最小的調(diào)度實體)。

 

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

針對嵌入式人工智能,物聯(lián)網(wǎng)等專業(yè)技術(shù)分享和交流平臺,內(nèi)容涉及arm,linux,android等各方面。