一. 操作系統調度的原則
1. 什么是調度
當計算機系統死多道程序設計系統時,通常就會有多個進程或者線程競爭CPU,只要有兩個或者更多的進程處於就緒狀態,這種情況就會發生,如果只有一個CPU可以用,那么必須選擇下一個要運行的進程,在操作系統中,完成選擇工作的這一部分被稱為調度程序(scheduler)。該程序使用的算法稱為調度算法(scheduler algorithm)。
幾乎所有的進程的I/O請求或者計算都是交替突發的,注意,某些I/O活動可以看做是計算,例如,當CPU向視頻RAM復制數據以等待更新屏幕時,因為使用了CPU,所以是計算活動,而不是I/O活動,按照這種觀點,當一個進程等待外部設備完成工作而被阻塞時,才是I/O活動。

Fig 1 : CPU調度的突發使用和等待I/O的交替出現:a) CPU密集型進程;b)I/O密集型進程
2. 何時調度
- 在創建一個新進程后,需要決定是運行父進程還是子進程,需要決定是運行父進程還是子進程
- 一個進程退出后必須進行調度
- 在一個進程阻塞在I/O和信號量上或者由於其他原因阻塞的時候,必須選擇另一個進程開始運行。
- 在一個I/O發生中斷的時候,必須做出調度決策。
- 非搶占式調度:挑選一個進程,然后讓該進程運行到被阻塞。
- 搶占式調度:挑選一個進程讓他運行到某個固定時段的最大值。如果在改時間段結束時,該進程仍然在裕興,它就會被掛起,而調度程序挑選另一個進程運行,如果存在一個就緒進程。進行搶占式調度處理,需要在事件間隔的末端發生時鍾中斷,以便把CPU控制返回給調度程序,如果沒有可用的時鍾,那么非搶占式調度就是唯一的選擇。
3. 調度算法的三個目標
- 吞吐量 (throughout) : 系統每個小時完成的作業數量。
- 周轉時間 (turnaround time) : 一個批處理作業提交時刻到完成該作業完成時刻為止的統計平均時間。
- CPU利用率 (CPU Utilization) : 多用於度量批處理系統,反應CPU的使用情況
當然我們也可以不完全依靠操作系統的調度進程來對我們的進程進行調度,我們可以手動調整,即調整調度程序的參數。這種方法被稱為把調度機制與調度策略分離。
二. 批處理系統中的調度
對於批處理系統的調度,需要滿足以下幾個要求:
- 吞吐量——每小時最大作業量
- 周轉時間——保持從提交到終止間最小忙碌
- CPU利用率——始終保持CPU忙碌
1. 先來先去服務 (first-come first-served, FCFS)
這是一個非搶占式的調度算法。實現也很簡單,就是進程按照它們請求的CPU順序使用CPU,誰先申請誰就會在隊列的前面,后面申請的就不斷加入到隊尾即可。當一個正在運行的進程發生了阻塞,那么隊列中的第一個進程就會繼續運行,在阻塞的進程編程就緒時,就像一個新來到的作業一樣,排到隊列的末尾。
2. 最短作業優先 (shortest job first)
同樣也是一個非搶占式調度算法。實現起來也非常簡單,就是把作業的時間從小到大排列,並且按這個順序進行進程的運行。但要注意,這個算法要當所有的進程都可以同時運行的時候才是最優的。比如有4個作業ABCD(假設度可以同時運行),他們的作業時間分別是1,2,4,8,我們按照ABCD的順序進行進程的調度即可得最優的平均周轉時間。
注意平均周轉時間的計算方法 (k a0 + (k - 1) a1 + (k - 2) a2 ... +)/k,其中a0,a1,a2...分別是第k個作業運行的時間,比如上述例子的最優平均周轉時間為 (4*1+3*2 + 2*4 +8)/4 = 6.5
當作業不能同時運行時,最短作業優先算法不一定是最優的,比如現在有5個作業,從A到E,運行時間分別是2,4,1,1和1,他們的到達時間是0,0,3,3和3,開始的時候我們只能選擇A或者B運行,因為其他三個作業還沒有到達,使用最短作業優先。將按照ABCDE的順序開始運行,其平均等待時間是6.4,但是按照BCDEA的順序運行作業,其平均的等待時間則是6。
3. 最短剩余時間優先 (shortest remaining time next)
這個算法是最短作業優先算法的搶占式的版本,使用這個算法的時候,調度程序總是選擇則剩余運行時間最短的那個進程運行。再次提醒,有關的運行時間必須提前掌握。當一個新的作業到達打的時候,其整個時間同當前進程的剩余時間進行比較。其進程的運行時間必須被提前知道,當一個新的作業到達的時候,其整個時間同當前進程的剩余時間作比較。如果較新的進程比當前運行的進程需要更少的時間,當前進程就會被掛起,而運行新的進程。這種方式可以使新的短作業獲得良好的服務。
三. 交互式系統中的調度
1. 輪轉調度
最古老,最簡單,最公平而且使用最廣的算法是輪轉調度。每個進程被分配一個時間片(quantum),即允許該進程在該時間段中運行,如果時間片結束時該進程還在運行,則剝奪CPU並分配給另外一個進程。如果該進程在時間片結束前阻塞或者結束,則CPU立刻進行切換。如下圖所示:

Fig 2:輪轉調度:a) 可運行進程列表;b) 進程B用完時間片后的可運行進程列表
時間片長度的設置在輪轉調度里面非常重要,因為從一個進程切換到另一個進程是需要一定的時間的——包括保存和裝入寄存器的值以及內存映像。更新各種表格和列表,清除和重新調入內存高速緩存等。進程切換有時候也被稱為上下文切換。時間片設置得太短會導致過多的進程切換。降低了CPU的效率;而設置得太長又會可能對短的交互請求的響應時間變長。
2. 優先級調度
輪轉調度隱含的假設是所有的進程都是相同的優先級的,通過動態地或者靜態地對進程設立優先級,我們可以實現優先級調度,優先級高的進程優先運行。
為了防止高優先級的進程無休止地運行下去,調度程序可以在每個時鍾周期結束時降低當前進程的優先級。如果這個動作導致該進程的優先級低於次高優先級的進程。則進行進程切換。一個可采用的方法是,每個進程可以賦予一個允許運行的最大時間片。當這個時間片用完時,下一個次高優先級的優先級進程可以獲得機會運行。
靜態優先級可以通過預先評估進程的重要性來確定。而動態優先級的確定可以按照以下的方法進行處理:

Fig 3:有4個優先級類的調度算法
上圖的算法一目了然,就是把所有的進程分為四個優先級組,而在各類進程的內部采用輪轉調度。如圖3,只要存在優先級為4的進程,則不會理會優先級3,2,1的進程。當優先級4的隊列為空后,才開始調度優先級3的進程。以此類推。
動態優先級調度要每隔一段時間調整進程的優先級,否則會發生飢餓。同時也要注意鎖封護和優先級反轉的問題。
3. 多級隊列
多級隊列實現的思想和優先級調度的思想類似,但是增加給不同優先級的分配不同的時間片。越低優先級分配的時間片越大,並且當一個進程用完被分配的時間后,會被降低一個優先級。這樣可以有效地讓短的交互進程讓出CPU。
而對於那些剛開始運行一段時間,然而后來又需要交互的進程,為了避免被永遠地懲罰。某些操作系統提供手動提高優先級的策略(比如按下F鍵可以提升優先級)。
4. 最短進程有限
如果我們把每一條執行的指令都看做是一個獨立的 “作業”,那么我們就可以利用批處理系統的一個思想 “最短作業常常伴隨着最短響應時間”,對進程的過去進行推測,並執行估計運行時間最短的那一個。假設某個終端上每條命令的估計運行時間為T0,現在假設測量到其下一次運行時間為T1。可以用這兩個值的加權和來改進估計時間。即aT0 + (1 - a)T1。通過選擇a的值,可以決定是盡快忘記老的運行時間,還是在一段唱的時間內記住他們。當a = 1/2時,可以得到如下序列:
T0 ,T0/2 + T1/2,T0/4 + T1/2 + T2/2,T0/8 + T1/8 + T2/4 + T3/2
可以看到三輪過后,T0在新的估計值中所占的比重下降到1/8,這樣的算法被稱為是老化 (aging) 。
5. 保證調度
保證調度是一個理想的調度,很難被實現。其基本實現思想是系統跟蹤每個進程自創建以來已使用了多少CPU時間。然后它計算各個進程應該獲得的CPU時間。即自創建以來的時間除以n,由於各個進程實際獲得的CPU時間是已知的。所以很容易計算出真正獲得的CPU時間和應該獲得的CPU的時間之比。比率為0.5說明一個進程只獲得了應得時間的一半,而比率為2.0則說明它已經獲得了應得時間的兩倍。(理想情況是,在一個有n個進程的單用戶系統中,若所有的進程都等價。則每個進程將獲得1/n的CPU時間)。
6. 彩票調度
彩票調度的基本思想是向進程提供各種系統資源(如CPU時間)的彩票,一旦需要作出一項調度決策的時候,就隨機抽出一張彩票,擁有該彩票的進程獲得該資源。在應用到CPU調度時候,系統可以掌握每秒鍾50次的一種彩票,作為獎勵每個獲獎者可以得到20ms的CPU時間。
彩票調度可以很簡單地實現優先級調度——只要讓每個進程的彩票配額不一樣就可以了,有更多彩票的進程獲得CPU的幾率要更大一點。同時進程間也可以進行彩票交換,比如當一個進程請求IO而被阻塞時,它可以把彩票讓給另一個進程,讓另一個進程獲得更大的得到CPU的幾率。
7. 公平分享調度
我們上面所考慮到的調度算法都是針對單用戶的。比如用戶一有9個進程,而用戶2有1個進程,假設我們進行輪轉調度,那么用戶1可以獲得90%的CPU的時間,但是用戶2只能獲得10%的CPU時間。在多用戶的系統必須考慮不同用戶的CPU的時間分配。
四. 實時系統中的調度
實時系統是一種時間起着主導作用的系統。最典型的就是多媒體實時系統,當我們在播放CD時,必須在非常短的時間內將流轉換成音樂,如果轉換時間過長,那么音樂就會產生問題。實時系統通常可以分為硬實時和軟實時,前者的含義是必須滿足絕對的截止時間,后者的含義是雖然不希望偶爾錯失截止時間,但是還可以容忍。
實時系統中的時間可以按照響應方式進一步分類為周期性(以規則的時間間隔發生)事件或者非周期(發生時間不可預知)事件。一個系統可能要響應多個周期性的事件流,系統可能無法響應所有事件。實時系統可調度的條件:
(有m個周期事件,事件i以周期Pi發生,並需要Ci秒CPU時間來處理事件),滿足這個條件的實時系統被稱為可調度的。

在實時系統中我們可以嘗試設置一個主控時鍾,該時鍾每秒滴答適當的次數,例如針對NTSC制式的視頻,每秒滴答30次,在時鍾的每一個觸發下,所有的進程都以相同的次序相繼運行,當一個進程完成其工作時,它將發出suspend系統調用釋放CPU直到主控時鍾再次觸發。當主控時鍾再次響應,所有的進程再次以相同的次序運行。只要進程數較少,所有的工作都可以在一幀的時間內完成。采用輪轉調度就可以了。但是模型相當不靠譜,因為不同的進程可能以不同的頻率運行,具有不用的工作量,並且具有不同的最終時限。

Fig 4:三個周期性的進程,每個進程播放一部電影,每一部電影的幀率以及每幀的處理需求有所不同
一般實時調度模型:多個進程競爭CPU,每個進程有自己的工作量和最終時限。假設系統知道每個進程必須以什么樣的頻率運行,有多少工作要做以及下一個最終時限是什么。多個相互競爭的進程,其中若干進程或者全部進程都必須滿足的最終時限的調度稱為是實時調度。
1. 速率單調調度(實時靜態算法)
速率單調調度算法(Rate Monotonic Scheduling,RMS)可以用於滿足以下條件的進程:
- 每個周期性進程必須在其周期內完成。
- 沒有進程依賴於任何其他進程。
- 每一個進程在一次突發中需要相同的CPU時間量。
- 任何非周期進程都沒有最終時限。
- 進程搶占時刻發生而沒有系統開銷。(理想模型)
單調速率算法按照以下規則給進程設立優先級:比如A進程每30ms運行一次,則每秒運行33次,則獲得優先級33;B進程每秒運行20次,則獲得優先級20,所以優先級與速率成線性關系,這就是這個算法的名字的來歷。RMS算法是最優的實時靜態算法中。
但是要注意的是,RMS算法並不是在什么情況下都能正常運行的,下面會有一個RMS算法不能調度的情形。
Liu和Layland證明了在任何周期性進程系統,如果:
則RMS可以正常工作,隨着m->無窮,那么最大利用率就會逼近ln2。比如當m=3時,最大允許利用率為0.780,所以在三個周期性進程的系統中,當CPU利用率小於0.780,那么RMS總可以工作的,但是反過來說,如果CPU利用率大於0.780,並不能說明RMS不能工作(對於特定的周期和運行時間可以的調度成功)。

2. 最早最終時限調度(實時動態算法)
最早最終時限優先算法(Earliest Deadline First,EDF)是一個動態算法,它不要求進程是周期性的,也不要求每個CPU突發具有相同的運行時間。只要有一個進程需要CPU時間,它就宣布它的到來和最終時限。調度程序維持一個可運行的進程列表,該列表按最終時限排序,EDF算法運行列表中的第一個進程,也是具有最近最終時限的進程。當一個新的進程就緒時,系統進行檢查以了解其最終時限是否發生在當前運行的進程結束之前。如果是這樣,那么新的進程就搶占當前正在運行的進程。
EDF算法對於任何一組可調度的進程總是可以工作的。

Fig 5:RMS和EDF調度實例1
在上述例子中,RMS和EDF算法都可以正常調度,對於RMS算法,前80ms的都是很好理解的,因為優先級A>B>C(A的優先級是33,B是25,C是20),所以我們只用把ABC三個進程輪轉調度即可。注意A可以搶占B,B可以搶占C,所以在90ms的時候,A和B都處於就緒態,但是A的優先級要比B要高,所以A會搶占B,當A運行完以后,繼續運行C。
而對於EDF算法,在前90ms中,與RMS算法的處理結果都是一樣的,然而在90ms時,A和B選擇的最終時限都是一樣的,但是EDF算法規定,當當前運行的進程和其他進程的最終時限一樣時,不搶占當前運行的進程,防止而外的開銷,所以B繼續運行,然后B運行完以后調度A。

Fig 6:RMS調度失敗的例子
現在我們換一個例子,假設A進程每個周期運行15ms,而不是10ms,這個時候RMS算法就會調度失敗了,首先在RMS算法中,ABC的優先級不變,因為RMS算法中的優先級只與進程的運行頻率有關,而與進程的運行時間是無關的。在t = 45ms的時候,調度B,而由於C的優先級低於B,所以C無法搶占B而錯過其最終時限。RMS算法調度失敗。
現在我們來看EDF算法,當t = 30時,A,B和C發生競爭,而這個時候A的最終時限是60,B的最終時限是80,而C是50,所以調度算法決定調度C進程,當C進程運行完后其最終時限變為100,所以調度算法調度A,以此類推。當t = 90時,也發生了剛才A和B發生競爭的情況,EDF選擇不切換進程,繼續運行B。
五. 線程調度
線程調度主要是分為用戶級線程調度和內核級線程調度,他們的區別就是在於內核是否認識到有不同的線程,如下圖所示:

Fig 7: a)用戶級線程的可能調度,有50ms時間片的進程以及每次運行5ms CPU的線程 b) 擁有相同特征的內核級線程的調度
對於用戶級線程,CPU給一個進程分配CPU后,由進程在用戶空間上控制線程的調度,這種調度不會影響其他進程,當這個進程的時間片用完后,內核會調度其他進程。而對於內核級線程,它不用考慮被調度的線程是屬於哪個進程的。這個時候時間片是線程擁有的。
用戶級線程和內核級線程的性能是有差別的。一般來說用戶級線程的切換非常輕松。但是對於內核級線程,線程切換需要保存完整的上下文,修改內存映像,使高速緩存失效等操作。這些操作是費時的。