主要内容:
- 什麼是排程
- 排程實作原理
- Linux上排程實作的方法
- 排程相關的系統調用
1. 什麼是排程
現在的作業系統都是多任務的,為了能讓更多的任務能同時在系統上更好的運作,需要一個管理程式來管理計算機上同時運作的各個任務(也就是程序)。
這個管理程式就是排程程式,它的功能說起來很簡單:
- 決定哪些程序運作,哪些程序等待
- 決定每個程序運作多長時間
此外,為了獲得更好的使用者體驗,運作中的程序還可以立即被其他更緊急的程序打斷。
總之,排程是一個平衡的過程。一方面,它要保證各個運作的程序能夠最大限度的使用CPU(即盡量少的切換程序,程序切換過多,CPU的時間會浪費在切換上);另一方面,保證各個程序能公平的使用CPU(即防止一個程序長時間獨占CPU的情況)。
2. 排程實作原理
前面說過,排程功能就是決定哪個程序運作以及程序運作多長時間。
決定哪個程序運作以及運作多長時間都和程序的優先級有關。為了确定一個程序到底能持續運作多長時間,排程中還引入了時間片的概念。
2.1 關于程序的優先級
程序的優先級有2種度量方法,一種是nice值,一種是實時優先級。
nice值的範圍是-20~+19,值越大優先級越低,也就是說nice值為-20的程序優先級最大。
實時優先級的範圍是0~99,與nice值的定義相反,實時優先級是值越大優先級越高。
實時程序都是一些對響應時間要求比較高的程序,是以系統中有實時優先級高的程序處于運作隊列的話,它們會搶占一般的程序的運作時間。
程序的2種優先級會讓人不好了解,到底哪個優先級更優先?一個程序同時有2種優先級怎麼辦?
其實linux的核心早就有了解決辦法。
對于第一個問題,到底哪個優先級更優先?
答案是實時優先級高于nice值,在核心中,實時優先級的範圍是 0~MAX_RT_PRIO-1 MAX_RT_PRIO的定義參見 include/linux/sched.h
1611 #define MAX_USER_RT_PRIO 100
1612 #define MAX_RT_PRIO MAX_USER_RT_PRIO
nice值在核心中的範圍是 MAX_RT_PRIO~MAX_RT_PRIO+40 即 MAX_RT_PRIO~MAX_PRIO
1614 #define MAX_PRIO (MAX_RT_PRIO + 40)
第二個問題,一個程序同時有2種優先級怎麼辦?
答案很簡單,就是一個程序不可能有2個優先級。一個程序有了實時優先級就沒有Nice值,有了Nice值就沒有實時優先級。
我們可以通過以下指令檢視程序的實時優先級和Nice值:(其中RTPRIO是實時優先級,NI是Nice值)
$ ps -eo state,uid,pid,ppid,rtprio,ni,time,comm
S UID PID PPID RTPRIO NI TIME COMMAND
S 0 1 0 - 0 00:00:00 systemd
S 0 2 0 - 0 00:00:00 kthreadd
S 0 3 2 - 0 00:00:00 ksoftirqd/0
S 0 6 2 99 - 00:00:00 migration/0
S 0 7 2 99 - 00:00:00 watchdog/0
S 0 8 2 99 - 00:00:00 migration/1
S 0 10 2 - 0 00:00:00 ksoftirqd/1
S 0 12 2 99 - 00:00:00 watchdog/1
S 0 13 2 99 - 00:00:00 migration/2
S 0 15 2 - 0 00:00:00 ksoftirqd/2
S 0 16 2 99 - 00:00:00 watchdog/2
S 0 17 2 99 - 00:00:00 migration/3
S 0 19 2 - 0 00:00:00 ksoftirqd/3
S 0 20 2 99 - 00:00:00 watchdog/3
S 0 21 2 - -20 00:00:00 cpuset
S 0 22 2 - -20 00:00:00 khelper
2.2 關于時間片
有了優先級,可以決定誰先運作了。但是對于排程程式來說,并不是運作一次就結束了,還必須知道間隔多久進行下次排程。
于是就有了時間片的概念。時間片是一個數值,表示一個程序被搶占前能持續運作的時間。
也可以認為是程序在下次排程發生前運作的時間(除非程序主動放棄CPU,或者有實時程序來搶占CPU)。
時間片的大小設定并不簡單,設大了,系統響應變慢(排程周期長);設小了,程序頻繁切換帶來的處理器消耗。預設的時間片一般是10ms
2.3 排程實作原理(基于優先級和時間片)
下面舉個直覺的例子來說明:
假設系統中隻有3個程序ProcessA(NI=+10),ProcessB(NI=0),ProcessC(NI=-10),NI表示程序的nice值,時間片=10ms
1) 排程前,把程序優先級按一定的權重映射成時間片(這裡假設優先級高一級相當于多5msCPU時間)。
假設ProcessA配置設定了一個時間片10ms,那麼ProcessB的優先級比ProcessA高10(nice值越小優先級越高),ProcessB應該配置設定10*5+10=60ms,以此類推,ProcessC配置設定20*5+10=110ms
2) 開始排程時,優先排程配置設定CPU時間多的程序。由于ProcessA(10ms),ProcessB(60ms),ProcessC(110ms)。顯然先排程ProcessC
3) 10ms(一個時間片)後,再次排程時,ProcessA(10ms),ProcessB(60ms),ProcessC(100ms)。ProcessC剛運作了10ms,是以變成100ms。此時仍然先排程ProcessC
4) 再排程4次後(4個時間片),ProcessA(10ms),ProcessB(60ms),ProcessC(60ms)。此時ProcessB和ProcessC的CPU時間一樣,這時得看ProcessB和ProcessC誰在CPU運作隊列的前面,假設ProcessB在前面,則排程ProcessB
5) 10ms(一個時間片)後,ProcessA(10ms),ProcessB(50ms),ProcessC(60ms)。再次排程ProcessC
6) ProcessB和ProcessC交替運作,直至ProcessA(10ms),ProcessB(10ms),ProcessC(10ms)。
這時得看ProcessA,ProcessB,ProcessC誰在CPU運作隊列的前面就先排程誰。這裡假設排程ProcessA
7) 10ms(一個時間片)後,ProcessA(時間片用完後退出),ProcessB(10ms),ProcessC(10ms)。
8) 再過2個時間片,ProcessB和ProcessC也運作完退出。
這個例子很簡單,主要是為了說明排程的原理,實際的排程算法雖然不會這麼簡單,但是基本的實作原理也是類似的:
1)确定每個程序能占用多少CPU時間(這裡确定CPU時間的算法有很多,根據不同的需求會不一樣)
2)占用CPU時間多的先運作
3)運作完後,扣除運作程序的CPU時間,再回到 1)
3. Linux上排程實作的方法
Linux上的排程算法是不斷發展的,在2.6.23核心以後,采用了“完全公平排程算法”,簡稱CFS。
CFS算法在配置設定每個程序的CPU時間時,不是配置設定給它們一個絕對的CPU時間,而是根據程序的優先級配置設定給它們一個占用CPU時間的百分比。
比如ProcessA(NI=1),ProcessB(NI=3),ProcessC(NI=6),在CFS算法中,分别占用CPU的百分比為:ProcessA(10%),ProcessB(30%),ProcessC(60%)
因為總共是100%,ProcessB的優先級是ProcessA的3倍,ProcessC的優先級是ProcessA的6倍。
Linux上的CFS算法主要有以下步驟:(還是以ProcessA(10%),ProcessB(30%),ProcessC(60%)為例)
1)計算每個程序的vruntime(注1),通過update_curr()函數更新程序的vruntime。
2)選擇具有最小vruntime的程序投入運作。(注2)
3)程序運作完後,更新程序的vruntime,轉入步驟2) (注3)
注1. 這裡的vruntime是程序虛拟運作的時間的總和。vruntime定義在:kernel/sched_fair.c 檔案的 struct sched_entity 中。
注2. 這裡有點不好了解,根據vruntime來選擇要運作的程序,似乎和每個程序所占的CPU時間百分比沒有關系了。
1)比如先運作ProcessC,(vr是vruntime的縮寫),則10ms後:ProcessA(vr=0),ProcessB(vr=0),ProcessC(vr=10)
2)那麼下次排程隻能運作ProcessA或者ProcessB。(因為會選擇具有最小vruntime的程序)
長時間來看的話,ProcessA、ProcessB、ProcessC是公平的交替運作的,和優先級沒有關系。
而實際上vruntime并不是實際的運作時間,它是實際運作時間進行權重運算後的結果。
比如上面3個程序中ProcessA(10%)隻配置設定了CPU總的處理時間的10%,那麼ProcessA運作10ms的話,它的vruntime會增加100ms。
以此類推,ProcessB運作10ms的話,它的vruntime會增加(100/3)ms,ProcessC運作10ms的話,它的vruntime會增加(100/6)ms。
實際的運作時,由于ProcessC的vruntime增加的最慢,是以它會獲得最多的CPU處理時間。
上面的權重算法是我自己為了了解友善簡化的,Linux對vruntime的權重方法還得去看源碼^-^
注3.Linux為了能快速的找到具有最小vruntime,将所有的程序的存儲在一個紅黑樹中。這樣樹的最左邊的葉子節點就是具有最小vruntime的程序,新的程序加入或有舊的程序退出時都會更新這棵樹。
其實Linux上的排程器是以子產品方式提供的,每個排程器有不同的優先級,是以可以同時存在多種排程算法。
每個程序可以選擇自己的排程器,Linux排程時,首先按排程器的優先級選擇一個排程器,再選擇這個排程器下的程序。
4. 排程相關的系統調用
排程相關的系統調用主要有2類:
1) 與排程政策和程序優先級相關 (就是上面的提到的各種參數,優先級,時間片等等) - 下表中的前8個
2) 與處理器相關 - 下表中的最後3個
系統調用 | 描述 |
nice() | 設定程序的nice值 |
sched_setscheduler() | 設定程序的排程政策,即設定程序采取何種排程算法 |
sched_getscheduler() | 擷取程序的排程算法 |
sched_setparam() | 設定程序的實時優先級 |
sched_getparam() | 擷取程序的實時優先級 |
sched_get_priority_max() | 擷取實時優先級的最大值,由于使用者權限的問題,非root使用者并不能設定實時優先級為99 |
sched_get_priority_min() | 擷取實時優先級的最小值,理由與上面類似 |
sched_rr_get_interval() | 擷取程序的時間片 |
sched_setaffinity() | 設定程序的處理親和力,其實就是儲存在task_struct中的cpu_allowed這個掩碼标志。該掩碼的每一位對應一個系統中可用的處理器,預設所有位都被設定,即該程序可以再系統中所有處理器上執行。 使用者可以通過此函數設定不同的掩碼,使得程序隻能在系統中某一個或某幾個處理器上運作。 |
sched_getaffinity() | 擷取程序的處理親和力 |
sched_yield() | 暫時讓出處理器 |