如果計算機支持多道程序設計,那么它會經常碰到多個進程或者線程在同一時刻競爭CPU。只要當兩個進程同時進入就緒狀態,這種情況就會發生。但是CPU只有一個,那么這時候就需要做一個選擇:到底接下來該選擇哪個進程運行。操作系統做這個選擇的部分就叫做調度器(scheduler),而使用到的算法叫做調度算法(scheduling algorithm)。
很多對進程調度使用的東西對於線程調度同樣適用。如果線程是內核管理的線程,那么調度的單位就是線程,而不管這個線程屬於哪個進程。
1.調度簡介
在以前還是批處理系統的時候,調度算法非常簡單:無非是運行磁帶上下一個作業(job)而已。但是對於多道程序設計的系統來說,調度算法變得比較復雜,這主要是因為多個用戶在同時等待服務,因此需要調度器決定到底是接下來是選擇一個批處理作業還是與遠程終端用戶交互。由於CPU屬於稀缺資源,因此,一個優秀的調度器能夠提升系統性能以及用戶滿意度。
但是隨着PC的發展,這種從兩方面情況發生了變化。第一,大多數時間都只有一個活躍的進程。畢竟一般來說,你在使用Word編輯文件時,不太可能還同時讓系統在后台編譯程序。這樣的情況下,調度器其實不需要做很多工作來決定到底運行哪個進程——Word幾乎就是唯一的選擇。第二,計算機的速度越來越快,以至於CPU可能已經不再那么稀缺了。對於很多PC的程序來說,其限制主要在於用戶能進行輸入的比例,而不是CPU能夠處理的比例。甚至就算是兩個程序真的同時進行,其實先調度哪個運行也無傷大雅,例如,一個Word和一個Excel同時在運行,先調度哪個都行,因為用戶可能同時在等待這兩個程序的輸出。
但是對於網絡服務器來說,這種情況又不一樣了。非常多的程序競爭CPU資源,因此,調度又變得非常重要了。舉例來說,調度器需要決定到底是讓日常統計進程運行還是讓響應用戶請求的進程運行。
調度器除了需要考慮選擇哪個進程運行,還需要考慮到系統的性能。畢竟,進程之間的切換是開銷很大的,這涉及到從用戶態到內核狀態的切換,進程狀態的保存、恢復等等操作。
2.進程行為
幾乎所有的進程都在計算和I/O操作之間交替進行。如圖所示:

一般來說,CPU運行一段時間,然后調用一個系統調用來讀或者寫文件,之后CPU繼續運行,如果還需要數據則再進行I/O操作。有些I/O活動實際上是作為計算看待。例如,如果CPU將數據拷貝給顯卡內存來更新屏幕,這時候就不能作為I/O看待,因為這時候CPU還在運算。這里所說的I/O是指當一個進程等待外部設備完成工作而進入阻塞狀態的狀態。
對於上面圖中的a部分,大部分時間用子啊計算上,我們稱之為計算傾向(compute-bound);而b圖大部分時間在等待I/O操作,因此稱作I/O傾向的(I/O-bound)。
隨着CPU速度變快,進程逐步向I/O傾向轉變。因此,對I/O傾向進程的調度會成為未來一個非常重要的課題。
3.調度的時機
對於進程調度來說,一個重要的問題是何時進行調度。
第一,創建新進程的時候,到底是讓父進程運行還是讓子進程運行,這就需要進行進程調度,在這種情況下,選擇哪個進程都無傷大雅。
第二,當有進程退出時,需要進行進程調度,從其他的就緒狀態的進程集合中挑選一個運行。如果說沒有進程處在就緒狀態,那么系統提供一個system-idle的進程運行。
第三,當有進程因為I/O、或者信號量或者別的什么原因阻塞時,需要進行進程調度。有的時候,進程阻塞的原因可能會印象調度決定。比如,進程A非常重要,它需要等待進程B離開其臨界區。因此,調度器需要調度進程B運行,以便其離開臨界區,並且最終使得進程A繼續運行。但是可惜的是,調度器往往無法獲得足夠的信息來做這些決定。
第四,當I/O中斷發生,需要做進程調度。假設進程A因為I/O請求而進入阻塞狀態,等待I/O設備完成操作。這時,別的就緒進程可能被調度器選中,開始運行。當I/O設備就緒,I/O中斷發生,這時,調度器往往需要將正在運行的進程阻塞,而選擇之前等待I/O設備的進程重新進入運行態。當然,到底是讓之前的進程重新運行還是讓正在運行的進程繼續運行都是可行的,這具體取決於調度器。
第五,假設硬件始終提供50-60Hz頻率的時鍾中斷,那么,在每個時鍾中斷,或者每第k個時鍾中斷需要進行進程調度。從調度算法如何應對時鍾中斷,可以將調度算法分為兩種。第一種調度算法選中了一個進程運行后,會一直讓這個進程運行,直到它因為I/O或者其他原因而阻塞,或者說它自願讓出CPU資源。我們稱之為非搶占式的(nonpreemptive)。事實上,進程調度是不會在時鍾中斷的過程中做出。往往是當時鍾中斷的處理結束之后,中斷前運行的進程此時會被恢復。另外一種是搶占式的,這種調度算法往往選擇一個進程,讓它運行一個固定的最大時間。如果時間到了,它還在運行,調度器暫停這個進程,並且選擇一個其他的進程運行。
4.調度算法的類別
不同的環境下是需要不同的調度算法的。這是因為,不同的應用環境有不同的目標。這里主要需要討論三方面的環境:
- 批處理系統
- 交互式系統
- 實時系統
批處理系統目前依然廣泛應用於商業和工業控制,特別是金融、保險等行業。在批處理環境下,沒有用戶坐在終端前等待響應。因此,非搶占式或者搶占式但是有非常長的時間間隔的調度算法是可以接受的。這種情況下減少了進程切換,因此性能得到了提升。
對於交互式系統,搶占式是有必要的,這樣能避免一個進程霸占CPU不放,而別的進程得不到服務。服務器也進入這個范疇,因為服務器需要服務非常多的用戶,而這些用戶都很忙。
在那些有實時限制的系統中,搶占式基本上沒什么必要,因為進程本身就知道它自己無法運行很長的時間,因此它們會趕緊干活,然后快速的阻塞。實時系統和交互式系統不同之處在於實時系統一般是為了將目前的應用程序發展到極致,而交互式系統是通用的,它可以運行各種程序。
5.調度算法的目標
下面列出一些調度算法的目標,換句話說,判斷一個調度算法優劣的標准和依據:=
- 所有系統
公平(Fairness):給所有進程一個公平的獲取CPU的機會與份額。
強制實施(Policy Enforcement):聲明的措施必須實施。
平衡(Balance):使系統所有部分保持忙碌。
- 批處理系統
吞吐量(Throughput):每小時最多的作業。
回轉時間(Turnaround Time):將提交任務和任務結束之間的時間間隔最小。
CPU使用率(CPU Utilization):使CPU一直處在忙碌狀態。
- 交互式系統
響應時間(Response Time):快速響應請求。
均衡性(proportionality):滿足用戶的預期。
- 實時系統
趕上截至時間(Meet deadlines):避免丟失數據。
可預期(Predictability):在多媒體系統中避免品質退化。
上面的這些目標都還算比較顯而易見,因此就不一一詳細說了。
6.批處理系統的調度算法
- 先到先服務(First-come-first-served)
非搶占式的調度算法中,這種可能是最簡單的。現請求CPU資源的先安排運行。基本上只需要一個簡單的隊列存放所有就緒的進程。新到的進程放入隊尾,從隊首取出先來的就緒進程,讓它運行,不管運行多久都讓它運行。這種算法的巨大優勢在於非常簡單,易於理解和實現。此外,類似於早排隊買火車票的人先買到票,從這種角度來說,這個算法有其合理性。
這個算法也有其巨大的劣勢。假設有一個計算傾向的進程,每次運行一秒,有很多I/O傾向的進程,它們使用很少的CPU資源,但是需要執行1000次I/O操作才能結束。現在第一個進程運行了1秒,然后讀取磁盤數據,這時其他進程開始進行I/O操作,等到第一個進程獲取到了磁盤數據,於是它再運行1s,然后其他進程緊接着讀取數據。假設一個I/O操作需要1s,那么一個進程結束就需要1000s,這是很致命的。
- 短作業優先(Shortest Job First)
這種批處理系統的調度算法也是一種非搶占式的調度算法,這種算法假設進程的運行時間是提前知道的。現在舉例說明。假設在一個保險公司,我們很清楚一個處理1000條理賠的批處理作業需要多少時間(這種重復的工作每天都在進行,因此時間是可以提前知道的)。現在我們假設有ABCD四種類型的理賠批處理作業,需要的時間分別是8,4,4,4分鍾。如下圖所示:

如果調度算法調度的順序如圖a所示,那么A的回轉時間(Turnaround Time)是8,B的回轉時間是12,C是16,D是20,平均回轉時間是14分鍾。現在看如圖b所示的調度順序,也就是短作業優先的調度算法。B的回轉時間是4,C的是8,D的是12,A的是20,那么平均回轉時間是11分鍾,這樣的結果好於a圖所示的情況。現在我們做一個一般性的分析,假設四個作業需要的時間是a,b,c,d。第一個的回轉時間是a,第二個是a+b,第三個是a+b+c,第四個是a+b+c+d。平均回轉時間是(4a+3b+2c+a)/4。很明顯,a占的比重最大,想要整體平均回轉時間低,a必須是最少的,以此類推,b應該是次少的。需要特別指出的是,最短作業優先的算法需要所有作業同時處在待調度運行的狀態才行。也就是,在0時刻,所有作業都是就緒的。
- 最短剩余時間優先(Shortest Remaining Time Next)
這個算法是短作業優先算法的搶占式版本。這個算法下,調度器始終選擇完成工作剩余時間最短的進程來運行,一個作業需要的時間必須提前知道。當一個新的作業進來,調度器會將它需要的運行時間和正在運行的作業完成工作的剩余時間作比較,如果新作業的時間更短,那么調度器暫停正在運行的進程,轉而運行新作業。
