發表於 2016-03-17 | 分類於 Linux內核 |
文章系原創,如需轉載請註明:轉載自”[Blog of UnicornX” (http://unicornx.github.io/)
最新更新於:2016-03-17
任務調度是操作系統內核的核心,我目前對它的認識也只能說是寥寥,但是聊勝於無,知道一點對理解內核的多任務處理機制,以及編寫內核驅動還是有好處的。
將我目前理解的東西總結在這裏,以後有新的認識再逐漸更新吧。
主要參考文章:
- ldd3
- lkd3
- ulk3
- 內核代碼v2.6.32.2
- 現在的 Linux 內核和 Linux 2.6 的內核有多大區別?
- Linux 調度器發展簡述 相對來說比較完整第從歷史的角度介紹了Linux調度器的發展,從最初2.4版本,經歷2.6的O(1)改進,再到完全公平思想的提出,最後發展爲從2.6.23開始正式被內核接納的CFS調度算法。
- linux2.6.24內核,調度器章節的筆記 基於內核2.6.24。作者撰寫技術博客的專業精神值得學習。也是我主要參考的文章,附註,幾張圖畫的好。
其他參考文章
- 【思維重現】進程調度
- Linux實時性分析
- 中斷解析
- Linux進程調度知識 這個應該是針對的2.6內核採用CFS之前的調度算法。
- Linux內核之CFS調度和組調度,對代碼的分析比較到位
- linux調度器(一)——概述
***
基於2.6.32 - 分析linux內核的idle的知識
- linux調度器之主調度器框架
一些別人寫的課件
進程調度概述
- 目標:最大限度地利用處理器時間
- 基本任務:選擇下一個要運行的進程
- 核心問題:在可運行態進程之間分配有限的處理器時間,保證各進程公平,有效地使用處理器時間資源。
- 調度的基本要求和難點:
- 不同類型的進程有不同的調度需求。進程主要分爲兩種類型:”IO操作頻繁型”和”計算密集型”
- 響應時間 vs 系統開銷:一方面對交互式應用要有良好的響應速度;一方面要減少調度器自身的開銷,以增加花在執行程序上的時間。
任務,進程和線程的概念
任務的概念
在邏輯上表達一個順序執行流,在操作系統中用於表達內核中可以獨立調度的一個實體。
進程和線程
歷史上先有進程的概念,然後纔有線程的概念。它們都是歷史上任務概念的具體實現。
現在Linux內核中一個任務對應的是一個線程,所以目前最新的內核中一個線程纔對應着內核中可以獨立調度的一個實體。進程的概念演變爲線程組。
Linux內核中任務的組織方式
多個共享資源的線程構成線程組,線程組即進程。
進程之間資源不共享,地址空間獨立。
進程之間有父子關係,線程之間無父子關係。父子進程之間組織爲樹狀結構,多個子進程以鏈表的形式保存在父進程的task_struct
結構體中。每個進程維護一個指向父進程的指針。
內核線程是完全運行在內核地址空間的任務,和用戶空間沒有關係。可以運行ps -eLf
命令列出來的線程,如果CMD
那一列的值被[]
括起來了,就是內核線程。
進程狀態
SCHED_NORMAL
在POSIX中稱爲SCHED_OTHER
,所以查man手冊時都叫SCHED_OTHER
,但查看linux的頭文件中都叫SCHED_NORMAL
,兩個實際是一回事。
圍繞任務的狀態遷移,有另外一個組織方式。
在CFD調度算法中內核把所有TASK_RUNNING
的任務組織爲一棵紅黑樹rbtree。
內核中還存在多個隊列,用來組織處於TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
的任務
任務類型和調度策略
內核把它所要處理的所有任務分爲兩種類型,普通任務和實時任務
三類調度
static const struct sched_class fair_sched_class
static const struct sched_class idle_sched_class
static const struct sched_class rt_sched_class
struct sched_class
策略
Linux支持三種進程調度策略,分別是SCHED_FIFO 、 SCHED_RR和SCHED_NORMAL。Linux支持兩種類型的進程,實時進程和普通進程。實時進程可以採用SCHED_FIFO 和SCHED_RR調度策略;普通進程採用SCHED_NORMAL調度策略。
`man sched_setscheduler`
TASK_RUNNING
任務的組織和管理
defines | filepath |
---|---|
runqueues | kernel/sched.c |
struct rq | kernel/sched.c |
對於調度器來說,只有任務進入TASK_RUNNING
態才需要執行調度。TASK_RUNNING
態的任務又分爲兩種,一種是尚未擁有CPU的就緒態,另外一種是擁有CPU的實際運行態。對一個CPU來說,實際正在其上運行的任務只有一個,記爲CURRENT。
在多核系統中,每個CPU(此處指一個核心)對應一個全局變量per_cpu_runqueues
,其數據結構爲struct rq
,該變量爲調度的最頂層的數據結構。每個CPU對應一個rq結構體,所有rq結構體存放在runqueues數組中,有幾個CPU數組長度就爲幾。
在這個數據結構中將系統中所有處於TASK_RUNNING
狀態的任務組織在一起。
static DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
內核將所有就緒的進程分爲兩大類,普通進程和實時進程。這個概念在代碼上體現爲:在struct rq
結構中包含了兩個重要的成員:cfs和rt,其數據結構爲分別爲struct cfs_rq
和struct rt_rq
。分別表示兩個就緒隊列:cfs就緒隊列用於組織就緒的普通進程(這個隊列上的進程用完全公平調度器進行調度);rt就緒隊列用於用於組織就緒的實時進程(該隊列上的進程用實時調度器調度)。
struct rq {
...
struct cfs_rq cfs;
struct rt_rq rt;
...
struct task_struct *curr, *idle;
...
};
curr指向此CPU上的CURRENT進程,通常用cpu_curr(cpu)
宏來訪問。
其他狀態的任務的組織管理
調度器原理概述
觸發內核施行任務調度的觸發點:
-
顯式調用:
想到這種時機最爲正常。一個進程顯式調用調度函數schedule()
,意味着它已經完成自己所需要做的事情,主動放棄對處理器的使用。這個“時間點”自然就是一個調度時機。 -
系統調用
有一些系統調用會產生調度時間發生,這些與調度相關的系統調度其實也很好去理解,凡是對task_struct
中與調度相關的參數進行操作(例如改變優先級)的時候,這就意味着有調度的期望,所以這些調度函數返回(執行完操作)的時候就是一個必要的調度時機。下圖列出了一些與調度相關的函數API,像nice()
,sched_getscheduler()
,sched_set_param()
這樣的函數在返回的時候就會進行重新調度(注:這些函數會引發調用不意味着絕對會被調度)。 -
中斷和時鐘中斷:
中斷——產生一箇中斷就很可能意味着發生了一件很緊急的事情,它很可能意味着出現了一個更高優先級的進程迫切需要執行,所以在中斷返回的時候就需要考慮是否有進程需要進行調度。對於時鐘中斷,之所以把它單獨提出講,是因爲它太重要了,在時鐘中斷的中斷處理程序中存在一個週期性調度時機。
任務調度函數
任務調度與兩個調度函數有關:scheduler_tick()
和schedule()
,這兩者分別被稱作週期性調度器(或週期性調度函數)和主調度器(或主調度函數)。兩者合在一起被稱作通用調度器(或者核心調度器)
主調度器
在我們通常的概念中:調度器就負責將CPU使用權限從一個進程切換到另一個進程。完成這個工作的這其實就是Linux內核中所謂的主調度器。
在前面說到的觸發內核施行任務調度的觸發點
中”顯式調用”,”系統調用”和”中斷返回”時就會調用主調度器函數schedule()
。
上圖中,三種不同顏色的長條分別表示CPU分配給進程A、B、C的一小段執行時間,執行順序是:A,B,C。豎直的虛線表示當前時間,也就是說;A已經在CPU上執行完CPU分配給它的時間,馬上輪到B執行了。這時主調度器shedule就負責完成相關處理工作然後將CPU的使用權交給進程B。總之,主調度器的工作就是完成進程間的切換。
週期性調度器
在前面說到的觸發內核施行任務調度的觸發點
中”時鐘中斷”就會調用週期性調度器scheduler_tick()
。
再來看看週期性調度器都幹些什麼吧。同樣是剛纔的那幅圖,不過現在我們關注的不是從進程A切換到進程B這個過程,而是把A在CPU上執行的過程放大後觀察細節。
在A享用它得到的CPU時間的過程中,系統會定時調用週期性調度器(即定時執行週期性調度函數scheduler_tick()
)。
在此版本的內核中,這個週期爲10ms(這個10ms是這樣得來的:內中定義了一個宏變量:HZ=100,它表示每秒鐘週期性調度器執行的次數,那麼時間間隔t=1/HZ=1/100s=10ms
。10ms是個什麼概念呢,我們粗略地計算一下:如果週期性調度程序每次執行100條指令,每秒執行100次,那麼一秒鐘週期性調度器在CPU上執行的指令就是1萬條。如果主頻爲1GHz的處理器每秒鐘執行10億條指令,就相當於,週期性調度器消耗的CPU只佔CPU總處理能力的 1萬/10億=10萬分之一,微乎其微)。爲了方便理解,上圖將A獲得的時間段分成長度爲10ms的小片(注意:只是爲了方便講解,假想成這樣的,內核並沒有做這樣的劃分)。
週期性調度器每10ms執行一次,那它都幹了些什麼呢?
- 首先它負責減少當前運行進程(CURRENT)的時間片計數。例如:進程A的結構體的成員
sum_exec_runtime
記錄了A在CPU上運行的總時間,週期性調度器會更新該時間爲:sum_exec_runtime+=10ms
。 - 其次,它會在需要時設置need_resched標誌。這會影響後續主調度器函數
schedule()
對該進程的處理,譬如搶佔。 - 在SMP機器中,該函數還要負責平衡多個處理器上的運行隊列。
總之,這個函數雖然稱爲週期性調度器,但它不負責實際的進程切換,它是爲主調度器函數schedule()
服務的。
有關時鐘中斷處理程序的細節,可以參考LKD的11.5章節。
小結:
置need_resched標誌。這會影響後續主調度器函數schedule()
對該進程的處理,譬如搶佔。
- 在SMP機器中,該函數還要負責平衡多個處理器上的運行隊列。
總之,這個函數雖然稱爲週期性調度器,但它不負責實際的進程切換,它是爲主調度器函數schedule()
服務的。
有關時鐘中斷處理程序的細節,可以參考LKD的11.5章節。
小結:
主調度器負責將CPU的使用權從一個進程切換到另一個進程。週期性調度器只是定時更新調度相關的統計信息。