一、O(1)調度算法
1.1:優先級數組
O(1)算法的:一個核心數據結構即為prio_array結構體。
該結構體中有一個用來表示進程動態優先級的數組queue,它包括了每一種優先級進程所形成的鏈表。
1 |
#define MAX_USER_RT_PRIO 100 |
2 |
#define MAX_RT_PRIO MAX_USER_RT_PRIO |
3 |
#define MAX_PRIO (MAX_RT_PRIO + 40) |
4 |
typedef struct prio_array prio_array_t; |
5 |
struct prio_array { |
6 |
unsigned int nr_active; |
7 |
unsigned long bitmap[BITMAP_SIZE]; |
8 |
struct list_head queue[MAX_PRIO]; |
9 |
}; |
因為進程優先級的最大值為139,因此MAX_PRIO的最大值取140(詳細的是,普通進程使用100到139的優先級。實時進程使用0到99的優先級)。因此,queue數組中包括140個可執行狀態的進程鏈表,每一條優先級鏈表上的進程都具有同樣的優先級,而不同進程鏈表上的進程都擁有不同的優先級。
除此之外,prio_array結構中還包含一個優先級位圖bitmap。該位圖使用一個位(bit)來代表一個優先級,而140個優先級最少須要5個32位來表示,因此BITMAP_SIZE的值取5。起初。該位圖中的全部位都被置0,當某個優先級的進程處於可執行狀態時。該優先級所相應的位就被置1。
因此,O(1)算法中查找系統最高的優先級就轉化成查找優先級位圖中第一個被置1的位。
與2.4內核中依次比較每一個進程的優先級不同,因為進程優先級個數是定值,因此查找最佳優先級的時間恆定。它不會像曾經的方法那樣受可運行進程數量的影響。
假設確定了優先級。那么選取下一個進程就簡單了,僅僅需在queue數組中相應的鏈表上選取一個進程就可以。
1.2:活動進程和過期進程
在操作系統原理課上我們知道,當處於執行態的進程用完時間片后就會處於就緒態。此時調度程序再從就緒態的進程中選取一個作為即將要執行的進程。
而在詳細Linux內核中,就緒態和執行態統一稱為可執行態(TASK_RUNNING)。
對於系統內處於可執行狀態的進程,我們能夠分為三類。首先是正處於執行狀態的那個進程;其次,有一部分處於可執行狀態的進程則還沒實用完他們的時間片。他們等待被執行;剩下的進程已經用完了自己的時間片,在其它進程沒實用完它們的時間片之前,他們不能再被執行。
據此,我們將進程分為兩類,活動進程,那些還沒實用完時間片的進程。過期進程。那些已經用完時間片的進程。因此,調度程序的工作就是在活動進程集合中選取一個最佳優先級的進程,假設該進程時間片恰好用完,就將該進程放入過期進程集合中。
在可執行隊列結構中,arrays數組的兩個元素分別用來表示剛才所述的活動進程集合和過期進程集合,active和expired兩個指針分別直接指向這兩個集合。
關於可執行隊列和兩個優先級數組的關系可參考以下的圖:
正如上面分析的那樣,可執行隊列結構和優先級數組結構使得Q(1)調度算法在有限的時間內就能夠完畢,它不依賴系統內可執行進程的數量。
1.3:時間片的計算
Linux2.4版本號的內核調度算法理解起來簡單:在每次進程切換時。內核依次掃描就緒隊列上的每個進程,計算每個進程的優先級,再選擇出優先級最高的進程來執行;雖然這個算法理解簡單。可是它花費在選擇優先級最高進程上的時間卻不容忽視。
系統中可執行的進程越多。花費的時間就越大。時間復雜度為O(n)。
偽代碼例如以下:
1 |
for (系統中的每一個進程) { |
2 |
又一次計算時間片; |
3 |
又一次計算優先級; |
4 |
} |
而2.6內核所採用的O(1)算法則非常好的攻克了這個問題,該算法能夠在恆定的時間內為每一個進程又一次分配好時間片,並且在恆定的時間內能夠選取一個最高優先級的進程,重要的是這兩個過程都與系統中可執行的進程數無關,這也正是該算法取名為O(1)的緣故。
O(1)算法採用過期進程數組和活躍進程數組解決以往調度算法所帶來的O(n)復雜度問題。過期數組中的進程都已經用完了時間片。而活躍數組的進程還擁有時間片。當一個進程用完自己的時間片后,它就被移動到過期進程數組中。同一時候這個過期進程在被移動之前就已經計算好了新的時間片。
能夠看到O(1)調度算法是採用分散計算時間片的方法,並不像以往算法中集中為全部可執行進程又一次計算時間片。
當活躍進程數組中沒有不論什么進程時。說明此時全部可執行的進程都用完了自己的時間片。那么此時僅僅須要交換一下兩個數組就可以將過期進程切換為活躍進程,進而繼續被調度程序所調度。兩個數組之間的切換事實上就是指針之間的交換,因此花費的時間是恆定的。以下的代碼說明了兩個數組之間的交換:
1 |
struct prop_array *array = rq->active; |
2 |
if (array->nr_active != 0) { |
3 |
rq->active = rq->expired; |
4 |
rq->expired = array; |
5 |
} |
通過分散計算時間片、交換過期和活躍兩個進程集合的方法能夠使得O(1)算法在恆定的時間內為每一個進程又一次計算好時間片。
進程調度的本質就是在當前可執行的進程集合中選擇一個最佳的進程,這個最佳則是以進程的動態優先級為選取標准的。
無論是過期進程集合還是活躍進程集合,都將每一個優先級的進程組成一個鏈表,因此每一個集合就有140個不同優先級的進程鏈表。同一時候。兩個集合中還採用優先級位圖來標記每一個優先級鏈表中是否存在進程。
調度程序在選取最高優先級的進程時。首先利用優先級位圖從高到低找到第一個被設置的位,該位相應着一條進程鏈表。這個鏈表中的進程是當前系統全部可執行進程中優先級最高的。在該優先級鏈表中選取頭一個進程,它擁有最高的優先級。即為調度程序立即要執行的進程。
上述進程的選取過程可用下述代碼描寫敘述:
01 |
struct task_struct *prev, *next; |
02 |
struct list_head *queue; |
03 |
struct prio_array *array; |
04 |
int idx; |
05 |
06 |
prev = current; |
07 |
array = rq->active; |
08 |
idx = sehed_find_first_bit(array->bitmap); |
09 |
queue = array->queue + idx; |
10 |
next = list_entry(queue->next, struct task_struct, run_list); |
11 |
if (prev != next) |
12 |
context_switch(); |
sehed_find_first_bit()用於在位圖中高速查找第一個被設置的位。假設prev和next不是一個進程。那么此時進程切換就開始運行。
通過上述的內容能夠發現。在恆定的時間又一次分配時間片和選擇一個最佳進程是Q(1)算法的核心。
二、全然公平(CFS)調度算法
一個調度算法中最重要的兩點就是調度哪個進程以及該被調度進程的執行時間是多少,以下我們分別來討論
2.1:調度哪個進程
O(1)算法是依據進程的優先級來選擇調度進程的,而CFS是依據進程的虛擬執行時間來進行調度的,當然,該虛擬執行時間也會受到優先級的影響。但不全是。以下看看什么是虛擬執行時間。以及怎樣依據它來選擇下一個調度進程。
CFS算法的初衷就是讓全部進程同一時候執行在一個CPU上,比如兩個進程都須要執行10ms的時間,則CFS算法下,連個進程同一時候執行在CPU上,且時間為20ms,而不是每一個進程分別執行10ms。可是這僅僅是一種理想的執行方式,CFS為了近似這樣的執行算法,就提出了虛擬執行時間(vruntime)的概念。
vruntime記錄了一個可執行進程到當前時刻為止執行的總時間(須要以進程總數n進行歸一化,而且依據進程的優先級進行加權)。依據vruntime的定義能夠知道,vruntime越大,說明該進程執行的越久,所以被調度的可能性就越小。所以我們的調度算法就是每次選擇vruntime值最小的進程進行調度。內核中使用紅黑樹能夠方便的得到vruntime值最小的進程。
至於每一個進程怎樣更新自己的vruntime?內核中是依照例如以下方式來更新的:vruntime += delta* NICE_0_LOAD/ se.weight;當中:
NICE_0_LOAD是個定值。及系統默認的進程的權值;se,weight是當前進程的權重(優先級越高。權重越大);
delta是當前進程執行的時間;我們能夠得出這么個關系:vruntime與delta成正比,即當前執行時間越長vruntime增長越快
vruntime與se.weight成反比,即權重越大vunruntime增長越慢。簡單來說,一個進程的優先級越高,並且該進程執行的時間越少,則該進程的vruntime就越小,該進程被調度的可能性就越高。
2.2:調度進程的執行時間
如今知道了怎樣調度進程了,可是當該進程執行時,它的執行時間是多少呢?
CFS的執行時間是有當前系統中全部可調度進程的優先級的比重來確定的,假如如今進程中有三個可調度進程A、B、C,它們的優先級分別為5,10,15,則它們的時間片分別為5/30,10/30,15/30。
而不是由自己的時間片計算得來的,這種話,優先級為1,2的兩個進程與優先級為50,100的兩個進程分的時間片是同樣的。
簡單來說,CFS採用的全部進程優先級的比重來計算每一個進程的時間片的,是相對的而不是絕對的。
這樣就從以上兩個方面來分析了CFS進程調度算法

