程式排程程式是多工作業系統的基礎,它是確保程式能有效工作的一個核心子系統,負責決定哪個程式投入執行、何時執行以及執行多長時間。只有通過程式排程程式的合理排程,系統資源才能夠最大限度地發揮作用,多程式才會有併發執行的效果。在一組處於可執行狀態的程式中選擇一個來執行,是排程程式所需完成的基本工作。
在前期的 Linux 版本中,Linux 的排程程式都相當簡陋,設計近乎原始,雖然容易理解,但是在可執行程式多或者多處理器的環境下都難以勝任。在 Linux2.5 系列核心中,開始採用一種叫做 O(1) 排程程式的新排程程式,解決了先前版本 Linux 排程程式的許多不足。在 Linux2.6 版本核心開發過程中,又引入了新的程式排程演算法,其中最為出名的是 RSDL 演算法,該演算法引入了公平排程的概念,隨後演化為 CFS:完全公平排程演算法。
Linux 中,與程式排程相關的資訊存放在程式描述符 task_struct 中:
struct task_struct { .... int prio, static_prio, normal_prio; // 程式優先順序 unsigned int rt_priority; const struct sched_class *sched_class; // 排程器類 struct sched_entity se; // 排程實體 struct sched_rt_entity rt; // 實時程式的排程實體 unsigned int policy; // 排程策略 cpumask_t cpus_allowed; // 用於控制程式可以在哪個處理器上執行 .... }
一、多工作業系統
多工作業系統就是能夠同時併發地互動執行多個程式的作業系統。可以分為兩類:非搶佔式多工系統和搶佔式多工系統。
1.1、非搶佔式多工系統:
在非搶佔式多工系統下,除非程式自己主動停止執行,否則它會一直執行下去。程式主動掛起自己的操作稱為讓步。理想情況下,程式通常會做出讓步,以便讓每個可執行程式享有足夠的處理器時間。這種機制有很多缺點:排程程式無法對每個程式該執行多長時間做出統一規定,所以程式獨佔的處理器時間可能會很長;更糟的是,如果一個程式一直不作出讓步,會使系統崩潰。現在的大多數系統都採用搶佔式多工模式,除了 MAC 0S 9(及其前身)以及 Windows 3.1(及其前身)。
1.2、搶佔式多工系統:
搶佔式多工系統下,由排程程式來決定什麼時候停止一個程式的執行,以便其他程式能夠得到執行機會,這個強制的掛起動作稱為搶佔。被搶佔的程式仍然是處於 TASK_RUNNING 狀態的,只是暫時沒有被CPU執行。程式的搶佔發生在程式處於使用者態執行階段,在核心態執行時是不能被搶佔的。 Linux 是搶佔式多工系統。
二、排程策略
排程策略決定排程程式在何時讓什麼程式執行,排程策略要根據多方因素擬定,以便讓系統資源得到最大限度的利用。
2.1、I/O 消耗型(互動式程式)和處理器消耗型程式(批處理程式)
程式大致可以被分為 I/O 消耗型和處理器消耗型(還可以既是 I/O 消耗型也是處理器消耗型):
I/O消耗型程式的大部分時間用來提交 I/O 請求或是等待 I/O 請求。這樣的程式經常處於可執行狀態,但通常執行時間都很短,因為它在等待更多的 I/O 請求時最後總會阻塞(這裡的 I/O 是指任何型別的可阻塞資源,如鍵盤輸入、網路I/O)。
處理器消耗型程式則是把大部分時間用在執行程式碼上,除非被搶佔,否則它們通常都一直不停地執行,因為它們沒有太多的 I/O 需求。對於這類程式,排程策略往往是降低它們的排程頻率,而延長其執行時間。一些需要進行大量計算工作的程式(如 MATLAB)就是處理器消耗型程式。
排程策略通常要在兩個矛盾的目標中間尋找平衡:程式響應迅速和最大系統利用率。為此,排程程式通常採用一套非常複雜的演算法來決定最值得執行的程式投入執行,但它往往並不保證低優先順序程式會被公平對待。Linux 為了保證互動式應用和桌面系統的效能,對程式的響應做了優化,更傾向於優先呼叫 I/O 消耗型。
2.2、程式優先順序
排程演算法中最基本的一類就是基於優先順序的排程。通常的做法是(Linux並未完全採用),優先順序高的程式先執行,低的後執行,相同優先順序的程式按輪轉方式進行排程。排程程式總是選擇時間片未用盡而優先順序最高的程式先執行。使用者和系統都可以通過設定程式的優先順序來影響系統的排程。
Linux 採用了兩種不同的優先順序範圍:
1)nice值:
nice值是所有 UNIX 系統中的標準化概念,但是不同的 UNIX 系統由於排程演算法不同,nice 值的運用方式也有所差異。 nice 值的範圍是從 -20 到 +19 ,預設值為 0;越大的 nice 值意味著更低的優先順序,相比高 nice 值(低優先順序)的程式,低 nice 值(高優先順序)的程式可以獲得更多的處理器時間。
在 Linux 系統中,nice 值代表時間片的比例。使用以下指令可以檢視程式的 nice 值:
ps -eo uid,pid,nice
其中,nice 選項就是檢視程式的 nice 值,在輸出結果中,NI 那一列即為程式的 nice 值。
2)實時優先順序:
實時優先順序的值是可以配置的,預設情況下它的變化範圍是從 0 到 99(包括0 和 99)。與 nice 值相反,實時優先順序值越高意味著程式優先順序越高,任何實時程式的優先順序都高於普通的程式(即實時優先順序和 nice 優先順序處於兩個互不相交的範疇)。可以使用如下指令檢視程式的實時優先順序:
ps -eo uid,pid,rtprio
其中,rtprio 選項就是程式的實時優先順序,在輸出結果中, RTPRIO那一列就是。如果有程式對應列顯示為“-”,則說明該程式不是實時程式。
2.3、時間片 與 處理器使用比
在 UNIX 系統中,程式在被搶佔之前能夠執行的時間是預先設定好的,被稱為時間片。時間片是一個數值,它表明程式在被搶佔前所能持續執行的時間。排程策略必須規定一個預設的時間片,既不能太長,也不能太短:時間片太長,則系統互動性較差;時間片太短,則會增大程式切換帶來的處理器耗時。一般而言,系統的預設時間片很短,比如10ms。
但是,Linux 的 CFS 排程器並沒有直接分配時間片到程式,Linux 使用的是處理器使用比的方式,將處理器使用比劃分給程式,這樣一來,程式所獲得的處理器時間其實是和系統負載密切相關的。
處理器使用比受 nice 值影響,具有較高 nice 值的程式將獲得較低的處理器使用比;具有較低 nice 值的程式將獲得較高的處理器使用比。而 CFS 排程器,則會根據程式的處理器使用比來決定搶佔的時機:如果消耗的使用比比當前程式小,則新程式立刻投入執行,搶佔當前程式;否則,將推遲其執行。通過擯棄時間片而是分配給程式一個處理器使用比的方式,CFS 確保了程式排程中能有恆定的公平性,而將切換頻率置於不斷的變動中。
三、Linux 排程演算法
3.1、排程器類
Linux 以模組的方式提供排程器,這樣做的目的是允許不同型別的程式可以有針對性地選擇排程演算法。這種模組化結構被稱為排程器類,它允許多種不同的可動態新增的排程演算法並存,排程屬於自己範疇的程式。每個排程器都有一個優先順序,基礎的排程器程式碼定義在 kernel/sched.c 檔案中,它會按照優先順序順序遍歷排程類,擁有一個可執行程式的最高優先順序的排程器類勝出,去選擇下面要執行的那一個程式。
在 linux 2.6.34 版本核心裡,有三種排程器類:idle_sched_class、rt_sched_class 和 fail_sched_class,在最新的 linux 4.6版本里,已經增加到了五種,另外兩種是 stop_sched_class 和 dl_sched_class:
排程器類 | 描述 | 對應排程策略 |
stop_sched_class | 優先順序最高的執行緒,會中斷所有其他執行緒,且不會被其他任務打斷 | 無,不需要排程普通程式 |
dl_sched_class | 採用 EDF 最早截止時間優先演算法排程實時程式 | SCHED_DEADLINE |
rt_sched_class | 採用提供 Roound_Robin 演算法或者 FIFO 演算法排程實時程式,具體排程策略由程式的 task_struct -> policy 指定 |
SCHED_FIFO, SCHED_RR |
fair_sched_class | 採用 CFS 演算法排程普通的非實時程式 |
SCHED_NORMAL, SCHED_BATCH |
idle_sched_class | 每個 CPU 的第一個pid=0執行緒:swapper,是一個靜態執行緒;一般執行在開機過程和 CPU 異常的時候做 dump | SCHED_IDLE |
可以看到,五種排程器類共有六種排程策略(即排程演算法),用於對不同型別的程式進行排程,或者支援某些特殊的功能。每個排程器都有一個優先順序,這五個排程器的優先順序順序為:
stop_sched_class > dl_sched_class > rt_sched_class > fair_sched_class > idle_sched_class
另外,關於所對應的排程策略,在核心版本 2.6.34 中有如下定義:
/* * Scheduling policies */ #define SCHED_NORMAL 0 #define SCHED_FIFO 1 #define SCHED_RR 2 #define SCHED_BATCH 3 /* SCHED_ISO: reserved but not implemented yet */ #define SCHED_IDLE 5 /* Can be ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */ #define SCHED_RESET_ON_FORK 0x40000000
SCHED_DEADLINE 在最新版本中有定義,暫不做敘述。上面的五種排程策略中, SCHED_NORMAL 和 SCHED_BATCH 是針對普通程式(非實時程式)的排程策略,都是通過 CFS 排程器來實現的。更進一步劃分的話,SCHED_NORMAL 是針對互動式程式的排程策略;SCHED_BATCH 是針對批處理程式的排程策略。SCHED_FIFO 和 SCHED_RR 是針對實時程式的排程策略,稍後會再做細說。 SCHED_IDLE 的優先順序最低,在系統空閒時才執行這類程式。
鑑於筆者所看的核心版本以及能力問題,暫時就先簡單討論一下實時排程器和完全公平排程器這兩種。實際上,大部分程式也都是屬於實時排程器和完全公平排程器的。
3.2、完全公平排程
完全公平排程(CFS)是一個針對普通程式的排程類,在 Linux 中被稱為 SCHED_NORMAL(也被稱作 SCHED_OTHER),CFS 演算法實現定義在檔案 kernel/sched_fair.c 中。
CFS 的出發點基於這樣一個理念:程式排程的效果應該能讓系統感覺像是具備一個理想中的完美多工處理器。在完美多工處理器下,每個程式將能獲得 1/n 的處理器時間(n 指的是可執行程式的數量)。同時,我們可以排程給它們無限小的時間週期,所以,在任何可測量週期內,我們給予 n 個程式中每個程式同樣多的執行時間。但是,這種模型是不現實的,首先,我們無法在一個處理器上真的同時執行多個程式,其次,每個程式的執行時間太小的話,將會增大程式切換帶來的額外消耗,這是不高效的。
CFS 的做法是允許每個程式執行一段時間、迴圈輪轉、選擇執行最少的程式作為下一個執行程式,而不是採用分配給每個程式時間片的做法;CFS 在所有可執行程式總數基礎上計算出一個程式應該執行多久,而不是依靠 nice 值來計算時間片。每個程式都按其權重在全部可執行程式中所佔比例的“時間片”來執行,為了計算準確的時間片,CFS 為完美多工中的無限小排程週期的近似值設立了一個目標,稱為“目標延遲”,目標延遲越小,則系統的互動性越好,同時也更接近完美的多工;與之相對的,是更高的切換代價和更差的系統總吞吐能力。同時,為了避免可執行程式過多而導致每個程式所獲得的處理器使用比太小帶來的鉅額切換消耗,CFS 引入了最小粒度的概念,用來表示每個程式所能獲得的時間片的底線,預設情況下,最小粒度為 1ms。
在 CFS 策略下,任何程式所獲得的處理器時間是由它自己和其他所有可執行程式 nice 值的相對差值決定的,nice 值對時間片的作用不再是算數加權,而是幾何加權。所對應的也不再是一個絕對的時間片,而是處理器的使用比。CFS 通過保證分配每個程式的處理器使用比的公平性,從而達到公平排程。當然,CFS 也不是完美的公平,而是近乎完美的多工,但是確實在一定程度上提升了程式排程公平性。
3.3、實時排程
實時排程是針對實時程式的排程類,Linux 提供了兩種實時排程策略:SCHED_FIFO 和 SCHED_RR。
SCHED_FIFO 實現了一種簡單的、先入先出的演算法:它不使用時間片。處於可執行狀態的 SCHED_FIFO 級的程式回比任何 SCHED_NORMAL 級的程式都先得到排程。一旦一個 SCHED_FIFO 程式處於可執行狀態,它就會一直執行,知道它自己受阻塞或顯式的釋放處理器為止。它不基於時間片,可以一直執行下去。只有更高優先順序的 SCHED_FIFO 或者 SCHED_RR 任務才能搶佔 SCHED_FIFO 任務。如果有兩個或更多同優先順序的 SCHED_FIFO 級程式,它們會輪流執行,但是依然只有在它們願意讓出處理器時才會退出。只要有 SCHED_FIFO 級程式在執行,其他級別較低的程式就只有等待它變為不可執行狀態後才有機會執行。
SCHED_RR 與 SCHED_FIFO 大體相同,只是 SCHED_RR 級的程式在耗盡事先分配給它的時間後就不在繼續執行了。也就是說,SCHED_RR 是帶有時間片的 SCHED_FIFO —— 這是一種實時輪流排程演算法。當 SCHED_RR 任務耗盡它的時間片時,處於同一優先順序的其他實時程式被輪流排程。時間片只用來重新排程同一優先順序的程式。對於 SCHED_FIFO 程式,高優先順序總是立即搶佔低優先順序,但是低優先順序程式決不能搶佔 SCHED_RR 任務,即使它的時間片耗盡。
核心版本:2.6.34
參考:https://blog.csdn.net/gatieme/article/details/51702662
參考書籍:Linux核心設計與實現(第3版)