進程調度基本概念
多道程序的目標就是始終允許某個進程運行以最大化CPU利用率,多個進程通時存在於內存中,操作系統通過進程調度程序按特定的調度算法來調度就緒隊列中的進程到CPU,從而最大限度的利用CPU。
需要進行CPU調度的情況可以分為四種:
- 當一個進程從運行狀態切換到等待狀態時(如I/O請求,wait()調用以便等待一個子進程的結束)
- 當一個進程從運行狀態切換到就緒狀態時(如出現了中斷)
- 當一個進程從等待狀態切換到就緒狀態時(如I/O完成)
- 當一個進程終止時
如果調度只能發生在第一種和第四種情況下,那么調度方案稱為非搶占的或協作的;否則調度方案就是搶占的。
調度准則:
- CPU使用率
- 吞吐量
- 周轉時間
- 響應時間
- 等待時間
進程調度算法
先到先服務調度(FCFS)
先請求CPU的進程首先分配到CPU。FCFS策略可以通過FIFO隊列容易的實現,當一個進程進入就緒隊列時它的PCB被鏈接到隊列尾部。當CPU空閑時,它會分配給位於就緒隊列頭部的進程,並將這個進程從隊列中移除。
缺點:平均等待時間很長
| 進程 | 執行時間 |
|---|---|
| 24 | |
| 3 | |
| 3 |
如果上面三個進程順序到達,且按FCFS順序處理,那么平均等待時間為(0+24+27)/3=17(ms)
如果是:
| 進程 | 執行時間 |
|---|---|
| 3 | |
| 3 | |
| 24 |
那么平均等待時間就是(6+0+3)/3=3(ms),所以進程的CPU執行時間變化波動很大的話,那么采用這種調度策略的平均等待時間變化也會很大。
另外,考慮動態情況下的FCFS調度性能,假設一個CPU密集型進程和多個I/O密集型進程隨着進程在系統中的運行,可能發生:CPU密集進程得到CPU,並使用。在這段時間中,所有其他進程會處理他們的I/O,並轉移到就緒隊列來進行等待CPU。當這些進程在等待時,I/O設備一直處於空閑狀態。當CPU密集型進程完成CPU執行並移到I/O設備,所有I/O密集型進程,由於只有很短的cpu執行,所以很快執行完並一會到I/O隊列。這時CPU處於空閑。這種由於其他進程都等待一個大進程釋放CPU的現象叫做護航效果。
注意,FCFS調度算法是非搶占式的,一旦CPU分配給了一個進程,該進程就會使用CPU直到釋放為止,所以FCFS不適用於分時系統。
最短作業優先調度(SJF)
該算法將每個進程與下次CPU執行的長度關聯起來。當CPU空閑時,它會被賦給具有最短CPU執行的進程。如果兩個進程具有同樣長度的CPU執行,可以有FCFS來處理。
可以證明SJF調度算法是最優的。因為對於給定的一組進程,SJF算法的平均等待時間最小。通過將短進程移到長進程之前,短進程的等待時間減少大於長進程的等待時間增加,所以平均等待時間減少。
但是如何才能知道下次CPU執行的長度?對於批處理系統的長期調度,可將用戶提交作業時指定的進程時限作為長度。SJF調度常用於長期調度。
雖然SJF是最優的,但是它不能在短期CPU調度級別上加以實現,因為沒有辦法知道下次CPU執行的長度。一種方法是試圖近似SJF調度,通過計算下一個CPU執行長度的近似值,可以選擇具有預測最短CPU執行的進程來運行。
下次CPU執行通常預測為以前CPU執行的測量長度的指數平均。設
為第n個CPU執行長度,設
為下次CPU執行預測值。對於
,0
1,定義:
= + (1 - )
值
包括最近信息,而
存儲了過去歷史,參數
控制最近和過去歷史在預測中的權重。如果
=0,那么
=
,最近歷史沒有影響(當前情形是瞬態);如果
=1,那么
=
,只有最近CPU執行才重要(過去歷史被認為是陳舊的,無關的)。一般情況下,定義
=
。
為了理解指數平均行為,通過替換
,可以展開
,從而得到:
= + (1 - ) + … + β + …+
通常 和(1 - )小於1,所以后面項的權重值比前面項的權重要小。
SJF算法可以是搶占的也可以是非搶占的。新進程的下次CPU執行,與當前運行進程的尚未完成的CPU執行相比,可能還要小,搶占SJF算法會搶占當前運行進程,而非搶占SJF算法會允許當前運行進程以先完成CPU執行。搶占SJF調度有時稱為最短剩余時間優先調度
例子
| 進程 | 到達時間 | 執行時間 |
|---|---|---|
| 0 | 8 | |
| 1 | 4 | |
| 2 | 9 | |
| 3 | 5 |
按照SJF調度,這個例子的平均等待時間是[(10 - 1) + (1 - 1) + (17 - 2) + (5 - 3)]/4=26/4=6.5ms。如果使用非搶占SJF調度算法,平均等待時間為7.75ms。
優先級調度
SJF算法就是通過優先級調度算法的一個特例,每個進程都有一個優先級與之關聯,而具有最高優先級的進程會被分配到CPU,具有相同優先級的進程按照FCFS順序調度。
優先級的定義可以是分為內部的或外部的。內部定義的優先級采用一些測量數據來計算進程優先級。外部定義的優先級采用操作系統之外的准則,如進程重要性等。
優先級調用可以是搶占式的也可以是非搶占式的。對於搶占式的那么較高優先級進程來時會搶占CPU;對於非搶占式的,只是將新進程加到就緒隊列的頭部。
優先級調度算法的一個主要問題就是無窮堵塞或飢餓。即讓某個低優先級進程無窮等待CPU。常見的解決方式是老化,老化逐漸增加在系統中等待很長時間的進程的優先級。
輪轉調度(RR)
輪轉調度算法是專門為分時系統設計德爾,類似與FCFS調度,並增加了搶占以切換進程。將一個較小的時間單元定義為時間量或時間片。時間片的大小通常為10ms-100ms。就緒隊列作為一個循環隊列,CPU調度程序循環整個就緒隊列,為每個進程分配不超過一個時間片的CPU。
在RR調度算法中,沒有進程會被連續分配超過一個時間片的CPU(除非是唯一可運行的進程),如果進程的CPU執行超過了一個時間片,那么該進程會被搶占,並被放回就緒隊列中,所以RR調度算法是搶占的。
RR調度算法的性能很大程度上取決於時間片的大小,如果時間片非常大,那么RR算法與FCFS算法一樣,如果時間片很小,那么會造成大量的上下文切換。如果上下文切換時間約為時間片的10%,那么越10%的CPU時間會被浪費在上下文切換上,上下文切換的時間一般少於10ms。
多級隊列調度
多級隊列調度算法就是將就緒隊列分成多個單獨隊列,根據進程屬性(如內存大小,進程優先級等)將每個進程永久分到一個隊列中,每個隊列有自己的調度算法。此外,隊列之間應該頁存在調度,通常采用固定優先級搶占調度。
多級反饋隊列調度
在多級隊列調度算法中,進程被永久的分配給一個隊列,這種設置的優點是調度開銷小,缺點是不夠靈活。
多級反饋隊列調度算法允許進程在隊列之間遷移。這種想法是根據不同CPU執行的特點來區分進程,如果進程使用過多的CPU時間,那么將會被遷移到更低優先級的隊列。這種方案將I/O密集型和交互進程放在更高優先級隊列上。此外在較低優先級隊列中等待過長的進程會被一到更高優先級隊列中,這種形式的老化阻止飢餓的發生。
通常情況下,多級反饋調度程序可由下列參數來進行定義:
隊列的數量
每個隊列的調度算法
用以確定何時升級到更高優先級隊列的方法
用以確定何時降級到更低優先級隊列的方法
用以確定進程在需要服務時將會進入哪個隊列中的方法
線程調度
在支持線程的操作系統上,內核級線程才是操作系統所調度的。用戶級線程是由線程庫來進行管理的,而內核並不知道他們。用戶級線程為了運行在CPU上最終應映射到相關的內核級線程上,這種映射不是直接的,可能采用輕量級進程(LWP)。
用戶級線程和內核級線程的一個區別就是他們如何調度的。
對於多對一和多對多模型的系統線程庫會調用用戶級線程,以便在可用的LWP上運行。這種方案成為進程競爭范圍(PCS),因為競爭CPU是發生在同一進程的線程之間。
為了決定哪個內核級線程調度到一個處理器上,內核采用系統競爭范圍(SCS)。采用SCS調度來競爭CPU,發生在系統內所有線程之間。采用一對一模型的系統,如Window、Linux、Solaris,只采用SCS調度。
通常情況下,PCS通常采用優先級調用,即調度程序選擇具有最高優先級的,可運行的線程,且允許一個更高優先級的線程來搶占當前運行的線程。
Pthreads調度
在通過POSIX Pthreads來創建線程時允許指定PCS或SCS。Pthreads采用如下競爭范圍的值:
PTHREAD_SCOPE_PROCESS :按PCS來調度線程
PTHREAD_SCOPE_SYSTEM:按SCS來調度線程
對於實現多對多模型的系統,PTHREAD_SCOPE_PROCESS策略調度用戶級線程可用LWP,LWP的數量通過線程庫來維護。PTHREAD_SCOPE_SYSTEM調度策略會創建一個LWP,並將多對多模型系統的每個用戶級線程綁定到LWP,實際采用一對一策略來映射線程。
Pthreads IPC提供兩個函數,用來獲取和設置競爭范圍策略:
pthread_attr_setscope(pthread_attr_t *attr, int scope)
pthread_attr_getscope(pthread_attr_t *attr, int *scope)
這兩個函數第一個參數是包含線程屬性值的指針,pthread_attr_setscope()第二個參數的值是PTHREAD_SCOPE_PROCESS或PTHREAD_SCOPE_SYSTEM
,pthread_attr_getscope()第二個參數的值是int值的指針,用於獲取競爭范圍的當前值。
示例:
#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 5
void *runner(void *param);
int main(int argc, char *argv[])
{
int i, scope;
pthread_t tid[NUM_THREADS];
pthread_attr_t attr;
pthread_attr_init(&attr);
if(pthread_attr_getscope(&attr, &scope) != 0) {
fprintf(stderr, "Unable to get scheduling scope\n");
}
else {
if(scope == PTHREAD_SCOPE_PROCESS) {
printf("PTHREAD_SCOPE_PROCESS\n");
}
else if(scope == PTHREAD_SCOPE_SYSTEM) {
printf("PTHREAD_SCOPE_SYSTEM\n");
}
else {
fprintf(stderr, "Illegal scope value.\n");
}
}
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
for(i = 0; i < NUM_THREADS; i++) {
pthread_create(&tid[i], &attr, runner, NULL);
}
for(i = 0; i < NUM_THREADS; i++) {
pthread_join(tid[i], NULL);
}
return 0;
}
void *runner(void *param)
{
int i;
for(i = 0; i<500000; i++) {};
printf("this is thread\n");
pthread_exit(0);
}
