Linux 是如何調度進程的?

通過上文《Linux進程在內核眼中是什麼樣子的?》,可以理解內核關於進程線程的所有管理都是通過一個結構體 —— task_struct。《Linux 進程線程是如何創建的?》也讓我們知道了用戶態下進程線程是如何創建的,不同的創建方式又有哪些優劣。本文就看下內核態是如何對 task 進行調度的。

調度的發展歷史

O(n)算法雖然歷史有點悠久,但很有必要研究,是後續O(1)等算法理解的基礎。由於O(n)不是本文重點,建議先去網上了解相關知識點。

O(1) 調度器:

O(1) 調度器中引入了per-CPU runqueue的概念。系統中所有的可運行狀態的進程首先經過負載均衡模塊掛入各個CPU的runqueue,每隔 200ms,處理器都會檢查 CPU 的負載是否不均衡,如果不均衡,處理器就會在 CPU 之間進行一次任務均衡操作。然後由主調度器和tick調度器驅動該CPU上的調度行爲。每一個優先級的進程被掛入不同鏈表中。

上圖說明了 task 與負載均衡和 runqueue 以及對應調度器之間的關係。每個 runqueue 裏又會分爲active和expired隊列,每個隊列中掛載着140個優先級不同的 task 。關於調度器在 runqueue 裏的算法實現我們看下面一張圖:

來自網絡

可以看出2.6 kernel 裏有 140 種優先級,所以我們就用長度爲 140 的 array 去記錄優先級。每個優先級下面用一個 FIFO queue 管理這個優先級下的 process。

那麼,我們怎麼找到當前最高優先級下面的可執行的 process 呢?如果從 0 開始一直遍歷下去,算法雖然不是 O(N),但是是跟優先級多少相關的 O(M),也不能算作 O(1)。在 2.6 scheduler 裏,採用 bitarray。它爲每種優先級分配一個 bit,如果這個優先級隊列下面有 process,那麼就對相應的 bit 染色,置爲 1,否則置爲 0。問題就簡化成尋找一個 bitarray 裏面最高位是 1 的 bit(left-most bit),這基本上是一條 CPU 指令的事(fls)

大致的思路齊備,我們來整理下步驟:

  1. 在 active bitarray 裏,尋找 left-most bit 的位置 x。

  2. 在 active priority array(APA)中,找到對應隊列 APA[x]。

  3. 從 APA[x] 中 dequeue 一個 process,dequeue 後,如果 APA[x] 的 queue 爲空,那麼將 active bitarray 裏第 x bit置爲 0。

  4. 對於當前執行完的 process,重新計算其 priority,然後 enqueue 到 expired priority array(EPA)相應的隊裏 EPA[priority]。

  5. 如果 priority 在 expired bitarray 裏對應的 bit 爲 0,將其置 1。

  6. 如果 active bitarray 全爲零,將 active bitarray 和 expired bitarray 交換一下。

CFS 調度器:

虛擬時間:

比如,調度週期是12ms,2個相同優先級的進程A和B,那麼每個進程的運行時間各爲6ms。倘若進程A,B的優先級nice分別爲0和1,那麼權重分別是1024和820。它們的關係如下:

權重 = 1024 / 1.25nice(次方)

那麼進程A獲取的運行時間是12x1024/(1024+820)=6.66ms,進程B獲取的運行時間是12x820/(1024+820)=5.34ms。進程A的cpu使用比例是6.66/10=66.6%,進程B的cpu使用比例是5.34/10=53.4%。這裏我們看到2個進程的執行時間分別是6.66ms和5.34ms,是不一樣的。但是CFS是想讓每個進程完全公平調度,這裏就引入一個概念——虛擬時間,CFS也是通過虛擬時間相等來保證調度公平的。虛擬時間vriture_runtime和實際時間wall time轉換公式如下:

虛擬時間 = 實際時間 * (1024/進程權重) = (調度週期 * 進程權重 / 所有進程總權重) * (1024 / 進程權重) = 調度週期 * 1024 / 所有進程總權重

可以看出雖然進程的權重不同,但是它們的 vruntime增長速度應該是一樣的 ,與權重無關。進程A的虛擬時間=6.66 *(1024/1024)= 6.66ms,進程B的虛擬時間=5.34 *(1024/820)= 6.66ms。這裏我們看出雖然進程的優先級不同,但最終的虛擬時間一樣。

總結:誰的vruntime值較小就說明它當前佔用cpu的時間較短,受到了“不公平”對待,因此下一個運行進程就是它。這樣既能公平選擇進程,又能保證高優先級進程獲得較多的運行時間。這就是CFS的主要思想了。

就緒隊列(runqueue):

CFS維護了一個按照虛擬時間排序的紅黑樹:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章