一文帶你圖解Linux組調度(看完悟了)
在介紹 組調度 前,我們先來重溫下什么是 進程調度。
本文基于 Linux-2.6.26 版本
什么是進程調度
一般來說,在操作系統(tǒng)中會運行多個進程(幾個到幾千個不等),但一臺計算機的 CPU 資源是有限的,如 8 核的 CPU 只能同時運行 8 個進程。那么當進程數大于 CPU 核心數時,操作系統(tǒng)是如何同時運行這些進程的呢?
這里就涉及 進程調度 問題。
操作系統(tǒng)運行進程的時候,是按 時間片 來運行的。時間片 是指一段很短的時間段(如20毫秒),操作系統(tǒng)會為每個進程分配一些時間片。當進程的時間片用完后,操作系統(tǒng)將會把當前運行的進程切換出去,然后從進程隊列中選擇一個合適的進程運行,這就是所謂的 進程調度。如下圖所示:

什么是組調度
一般來說,操作系統(tǒng)調度的實體是 進程,也就是說按進程作為單位來調度。但如果按進程作為調度實體,就會出現以下情況:
Linux 是一個支持多用戶的操作系統(tǒng),如果 A 用戶運行了 10 個進程,而 B 用戶只運行了 2 個進程,那么就會出現 A 用戶使用的 CPU 時間是 B 用戶的 5 倍。如果 A 用戶和 B 用戶都是花同樣的錢來買的虛擬主機,那么對 B 用戶來說是非常不公平的。
為了解決這個問題,Linux 實現了 組調度 這個功能。那么什么是 組調度 呢?
組調度 的實質是:調度時候不再以進程作為調度實體,而是以 進程組 作為調度實體。比如上面的例子,可以把 A 用戶運行的進程劃分為 進程組A,而 B 用戶運行的進程劃分為 進程組B。
調度的時候,進程組A 和 進程組B 分配到相同的可運行 時間片,如 進程組A 和 進程組B 各分配到 100 毫秒的可運行時間片。由于 進程組A 有 10 個進程,所以每個進程分配到的可運行時間片為 10 毫秒。而 進程組B 只有 2 個進程,所以每個進程分配到的可運行時間片為 50 毫秒。
下圖是 組調度 的原理:

如上圖所示,當內核進行調度時,首先以 進程組 作為調度實體。當選擇出最優(yōu)的 進程組 后,再從 進程組 中選擇出最優(yōu)的進程進行運行,而被切換出來的進程將會放置回原來的 進程組。
由于 組調度 是建立在 cgroup 機制之上的,而 cgroup 又是基于 虛擬文件系統(tǒng),所以 進程組 是以樹結構存在的。也就是說,進程組 除了可以包含進程,還可以包含進程組。如下圖所示:

在 Linux 系統(tǒng)啟動時,會創(chuàng)建一個根進程組 init_task_group。然后,我們可以通過使用 cgroup 的 CPU 子系統(tǒng)創(chuàng)建新的進程組,如下命令:
Linux 在調度的時候,首先會根據 完全公平調度算法 從根進程組中篩選出一個最優(yōu)的進程或者進程組進行調度。
如果篩選出來的是進程,那么可以直接把當前運行的進程切換到篩選出來的進程運行即可。
如果篩選出來的是進程組,那么就繼續(xù)根據 完全公平調度算法 從進程組中篩選出一個最優(yōu)的進程或者進程組進行調度(重復進行第一步操作),如此類推。
【文章福利】小編推薦自己的Linux內核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!前100名進群領取,額外贈送一份價值699的內核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)?


接下來,我們將介紹 組調度 是如何實現的。
1. 進程組
在 Linux 內核中,使用 task_group 結構表示一個進程組。其定義如下:
下面介紹一下 task_group 結構各個字段的作用:
se:完全公平調度算法 是以 sched_entity 結構作為調度實體(也就是說運行隊列中的元素都是 sched_entity 結構),而 sched_entity 結構既能代表一個進程,也能代表一個進程組。這個字段主要作用是,將進程組放置到運行隊列中進行調度。由于進程組中的進程可能會在不同的 CPU 上運行,所以這里為每個 CPU 分配一個 sched_entity 結構。
cfs_rq:完全公平調度算法 的運行隊列。完全公平調度算法 在調度時是通過 cfs_rq 結構完成的,cfs_rq 結構使用一棵紅黑樹將需要調度的進程或者進程組組織起來,然后選擇最左端的節(jié)點作為要運行的進程或進程組,詳情可以參考文章:《Linux完全公平調度算法》。由于進程組可能在不同的 CPU 上調度,所以進程組也為每個 CPU 分配一個運行隊列。
shares:進程組的權重,用于計算當前進程組的可運行時間片。
parent、siblings、children:用于將系統(tǒng)中所有的進程組組成一棵親屬關系樹。
task_group、sched_entity 和 cfs_rq 這三個結構的關系如下圖所示:

從上圖可以看出,每個進程組都為每個 CPU 分配一個可運行隊列,可運行隊列中保存著可運行的進程和進程組。Linux 調度的時候,就是從上而下(從根進程組開始)地篩選出最優(yōu)的進程進行運行。
2. 調度過程
當 Linux 需要進行進程調度時,會調用 schedule() 函數來完成,其實現如下(經精簡后):
schedule() 函數會調用 pick_next_task() 函數來篩選最優(yōu)的可運行進程,我們來看看 pick_next_task() 函數的實現過程:
從 pick_next_task() 函數的實現來看,其最終會調用 完全公平調度算法 的 pick_next_task() 方法來完成篩選工作,我們來看看這個方法的實現:
我們來分析下 pick_next_task_fair() 函數到流程:
從根進程組中篩選出最優(yōu)的可運行實體(進程或進程組)。
如果篩選出來的實體是進程,那么直接返回這個進程。
如果篩選出來的實體是進程組,那么將會繼續(xù)對這個進程組中的可運行隊列進行篩選,直至篩選出一個可運行的進程。
怎么區(qū)分 sched_entity 實體是進程或者進程組?sched_entity 結構中有個 my_q 的字段,當這個字段設置為 NULL 時,說明這個實體是一個進程。如果這個字段指向一個可運行隊列時,說明這個實體是一個進程組。
