1. 程式人生 > >5.Linux核心設計與實現 P39---linux2.6 CFS排程演算法分析(轉)

5.Linux核心設計與實現 P39---linux2.6 CFS排程演算法分析(轉)

1.概述
      CFS(completely fair schedule)是最終被核心採納的排程器。它從RSDL/SD中吸取了完全公平的思想,不再跟蹤程序的睡眠時間,也不再企圖區分互動式程序。它將所有的程序都統一對待,這就是公平的含義。CFS的演算法和實現都相當簡單,眾多的測試表明其效能也非常優越。

      CFS 背後的主要想法是維護為任務提供處理器時間方面的平衡(公平性)。這意味著應給程序分配相當數量的處理器。分給某個任務的時間失去平衡時(意味著一個或多個任務相對於其他任務而言未被給予相當數量的時間),應給失去平衡的任務分配時間,讓其執行。 

      CFS拋棄了時間片,拋棄了複雜的演算法,從一個新的起點開始了排程器的新時代,最開始的2.6.23版本,CFS提供一個
虛擬的時鐘,所有程序複用這個虛擬時鐘的時間,CFS將時鐘的概念從底層體系相關的硬體中抽象出來,程序排程模組直接和這個虛擬的時鐘介面 而不必再為硬體時鐘操作而操心,如此一來,整個程序排程模組就完整了,從時鐘到排程演算法,到不同程序的不同策略,全部都由虛擬系統提供,也正是在這個新的核心,引入了排程類。因此新的排程器就是不同特性的程序在統一的虛擬時鐘下按照不同的策略被排程。

      按照作者Ingo Molnar的說法:"CFS百分之八十的工作可以用一句話概括:CFS在真實的硬體上模擬了完全理想的多工處理器"。在“完全理想的多工處理器 “下,每個程序都能同時獲得CPU的執行時間。當系統中有兩個程序時,CPU的計算時間被分成兩份,每個程序獲得50%。然而在實際的硬體上,當一個程序佔用CPU時,其它程序就必須等待。這就產生了不公平。


2.相關概念
排程實體(sched entiy):就是排程的物件,可以理解為程序。
虛擬執行時間(vruntime):即每個排程實體的執行時間。任務的虛擬執行時間越小, 意味著任務被允許訪問伺服器的時間越短 — 其對處理器的需求越高。
公平排程佇列(cfs_rq):採取公平排程的排程實體的執行佇列。

3.CFS的核心思想
      全公平排程器(CFS)的設計思想是:在一個真實的硬體上模型化一個理想的、精確的多工CPU。該理想CPU模型執行在100%的負荷、在精確 平等速度下並行執行每個任務,每個任務執行在1/n速度下,即理想CPU有n個任務執行,每個任務的速度為CPU整個負荷的1/n。
      由於真實硬體上,每次只能執行一個任務,這就得引入"虛擬執行時間"(virtual runtime)的概念,虛擬執行時間為一個任務在理想CPU模型上執行的下一個時間片(timeslice)。實際上,一個任務的虛擬執行時間為考慮到執行任務總數的實際執行時間。 


      CFS 背後的主要想法是維護為任務提供處理器時間方面的平衡(公平性)。CFS為了體現的公平表現在2個方面
(1)程序的執行時間相等
      CFS 在叫做虛擬執行時 的地方維持提供給某個任務的時間量。任務的虛擬執行時越小, 意味著任務被允許訪問伺服器的時間越短 — 其對處理器的需求越高。
            假設runqueue中有n個程序,當前程序運行了 10ms。在“完全理想的多工處理器”中,10ms應該平分給n個程序(不考慮各個程序的nice值),因此當前程序應得的時間是(10/n)ms,但 是它卻運行了10ms。所以CFS將懲罰當前程序,使其它程序能夠在下次排程時儘可能取代當前程序。最終實現所有程序的公平排程。

(2)睡眠的程序進行補償
      CFS 還包含睡眠公平概念以便確保那些目前沒有執行的任務(例如,等待 I/O)在其最終需要時獲得相當份額的處理器。 

      CFS排程器的執行時間是O(logN),而以前的排程器的執行時間是O(1),這是不是就是說CFS的效率比O(1)的更差呢?
      答案並不是那樣,我們知道 CFS排程器下的執行佇列是基於紅黑樹組織的,找出下一個程序就是截下左下角的節點,固定時間完成,所謂的O(logN)指的是插入時間,可是紅黑樹的統 計效能是不錯的,沒有多大概率真的用得了那麼多時間,因為紅節點和黑節點的特殊排列方式既保證了樹的一定程度的平衡,又不至於花太多的時間來維持這種平 衡,插入操作大多數情況下都可以很快的完成,特別是對於組織得相當好的資料。

4.CFS的實現
4.1 2.6.23 VS 2.6.25
      在2.6.23核心中,剛剛實現的CFS排程器顯得很淳樸,每次的時鐘滴答中都會將當前程序先出隊,推進其虛擬時鐘和系統虛擬時鐘後再入隊,然後判斷紅黑 樹的左下角的程序是否還是當前程序而抉擇是否要排程,這種排程器的key的計算是用當前的虛擬時鐘減去待計算程序的等待時間,如果該計算程序在執行,那麼其等待時間就是負值,這樣,等待越長的程序key越小,從而越容易被選中投入執行;
      在2.6.25核心以後實現了一種更為簡單的方式,就是設定一個執行佇列的虛擬時鐘,它單調增長並且跟蹤該佇列的最小虛擬時鐘的程序,key值由程序的vruntime和佇列的虛擬時鐘的差值計算,這種方式就是真正的追趕, 比2.6.23實現的簡單,但是很巧妙,不必在每次時鐘滴答中都將當前程序出隊,入隊,而是根據當前程序實際執行的時間和理想應該執行的時間判斷是否應該排程。

4.2紅黑樹
      與之前的 Linux 排程器不同,它沒有將任務維護在執行佇列中,CFS 維護了一個以時間為順序的紅黑樹(參見下圖)。 紅黑樹 是一個樹,具有很多有趣、有用的屬性。首先,它是自平衡的,這意味著樹上沒有路徑比任何其他路徑長兩倍以上。 第二,樹上的執行按 O(log n) 時間發生(其中 n 是樹中節點的數量)。這意味著您可以快速高效地插入或刪除任務。 


      任務儲存在以時間為順序的紅黑樹中(由 sched_entity 物件表示),對處理器需求最多的任務 (最低虛擬執行時)儲存在樹的左側,處理器需求最少的任務(最高虛擬執行時)儲存在樹的右側。 為了公平,排程器先選取紅黑樹最左端的節點排程為下一個以便保持公平性。任務通過將其執行時間新增到虛擬執行時, 說明其佔用 CPU 的時間,然後如果可執行,再插回到樹中。這樣,樹左側的任務就被給予時間運行了,樹的內容從右側遷移到左側以保持公平。 因此,每個可執行的任務都會追趕其他任務以維持整個可執行任務集合的執行平衡。 

4.3 CFS內部原理
      Linux 內的所有任務都由稱為 task_struct 的任務結構表示。該結構完整地描述了任務幷包括了任務的當前狀態、其堆疊、程序標識、優先順序(靜態和動態)等等。您可以在 ./linux/include/linux/sched.h 中找到這些內容以及相關結構。 但是因為不是所有任務都是可執行的,您在 task_struct 中不會發現任何與 CFS 相關的欄位。 相反,會建立一個名為 sched_entity 的新結構來跟蹤排程資訊(參見下圖)。



      樹的根通過 rb_root 元素通過 cfs_rq 結構(在 ./kernel/sched.c 中)引用。紅黑樹的葉子不包含資訊,但是內部節點代表一個或多個可執行的任務。紅黑樹的每個節點都由 rb_node 表示,它只包含子引用和父物件的顏色。 rb_node 包含在 sched_entity 結構中,該結構包含 rb_node 引用、負載權重以及各種統計資料。最重要的是, sched_entity 包含 vruntime(64 位欄位),它表示任務執行的時間量,並作為紅黑樹的索引。 最後,task_struct 位於頂端,它完整地描述任務幷包含 sched_entity 結構。 

      CFS 排程函式非常簡單。 在 ./kernel/sched.c 中的 schedule() 函式中,它會先搶佔當前執行任務(除非它通過 yield() 程式碼先搶佔自己)。注意 CFS 沒有真正的時間切片概念用於搶佔,因為搶佔時間是可變的。 當前執行任務(現在被搶佔的任務)通過對 put_prev_task 呼叫(通過排程類)返回到紅黑樹。 當 schedule 函式開始確定下一個要排程的任務時,它會呼叫 pick_next_task 函式。此函式也是通用的(在 ./kernel/sched.c 中),但它會通過排程器類呼叫 CFS 排程器。 CFS 中的 pick_next_task 函式可以在 ./kernel/sched_fair.c(稱為 pick_next_task_fair())中找到。 此函式只是從紅黑樹中獲取最左端的任務並返回相關 sched_entity。通過此引用,一個簡單的 task_of() 呼叫確定返回的 task_struct 引用。通用排程器最後為此任務提供處理器。

4.4 CFS的優先順序
      CFS 不直接使用優先順序而是將其用作允許任務執行的時間的衰減係數。 低優先順序任務具有更高的衰減係數,而高優先順序任務具有較低的衰減係數。 這意味著與高優先順序任務相比,低優先順序任務允許任務執行的時間消耗得更快。 這是一個絕妙的解決方案,可以避免維護按優先順序排程的執行佇列。