1. 程式人生 > 其它 >《Linux核心設計與實現》 讀書筆記(4)--程序的排程

《Linux核心設計與實現》 讀書筆記(4)--程序的排程

《Linux核心設計與實現》 讀書筆記(4)--程序的排程

主要內容:

  • 什麼是排程
  • 排程實現原理
  • 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應該分配105+10=60ms,以此類推,ProcessC分配205+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() 暫時讓出處理器