探討可能的策略範圍以前,咱們先作一些簡化假設。這些假設與系統中運行的進程有關,有時候統稱爲工做負載(workload)。在這裏咱們對工做負載所作的假設是不切實際的,但未來會放寬這些假設。如今,咱們對操做系統中運行的進程(有時也叫工做任務)作出以下的假設:算法
除了作出工做負載假設以外,還須要一個東西能讓咱們比較不一樣的調度策略:調度指標。指標是咱們用來衡量某些東西的東西,在進程調度中,有一些不一樣的指標是有意義的。緩存
如今,讓咱們簡化一下,只用一個指標:週轉時間(turnaround time)。任務的週轉時間定義爲任務完成時間減去任務到達系統的時間。性能
週轉時間是一個性能(performance)指標,另外一個有趣的指標是公平(fairness),性能和公平在調度系統中每每是矛盾的。優化
咱們能夠實現的最基本的算法,被稱爲先進先出(First In First Out或FIFO)調度,有時候也稱爲先到先服務(First Come First Served或FCFS)。spa
FIFO有一些積極的特性:它很簡單,並且易於實現,可是在某些狀況下的性能不好。操作系統
假設咱們有3個任務(A、B和C),A運行100s,而B和C運行10s。並且都幾乎同時到達,A比B早一點點,而後B比C早到達一點點。此時系統的平均週轉時間是比較高的:110s((100 + 110 + 120)/ 3 = 110)。orm
這個問題一般被稱爲護航效應(convoy effect),一些耗時較少的潛在資源消費者被排在重量級的資源消費者以後。生命週期
事實證實,一個很是簡單的方法解決了這個問題。這個新的調度準則被稱爲最短任務優先(ShortestJob First,SJF):先運行最短的任務,而後是次短的任務,如此下去。隊列
事實上,考慮到全部工做同時到達的假設,咱們能夠證實SJF確實是一個最優(optimal)調度算法。可是咱們的假設仍然是不切實際的,讓咱們放寬另外一個假設。咱們能夠針對假設2,如今假設工做能夠隨時到達,而不是同時到達,這會致使什麼問題?進程
舉個例子,假設A在t = 0時到達,且須要運行100s。而B和C在t = 10到達,且各須要運行10s。即便B和C在A以後不久到達,它們仍然被迫等到A完成,從而遭遇一樣的護航問題。
爲了解決這個問題,須要放寬假設條件(工做必須保持運行直到完成)。咱們還須要調度程序自己的一些機制。鑑於咱們先前關於時鐘中斷和上下文切換的討論,當B和C到達時,調度程序固然能夠作其餘事情:它能夠搶佔(preempt)工做A,並決定運行另外一個工做,或許稍後繼續工做A。根據咱們的定義,SJF是一種非搶佔式(non-preemptive)調度程序,所以存在上述問題。
有一個調度程序徹底就是這樣作的:向SJF添加搶佔,稱爲最短完成時間優先(ShortestTime-to-Completion First,STCF)或搶佔式最短做業優先(Preemptive Shortest Job First ,PSJF)調度程序。每當新工做進入系統時,它就會肯定剩餘工做和新工做中,誰的剩餘時間最少,而後調度該工做。所以,在咱們的例子中,STCF將搶佔A並運行B和C以完成。只有在它們完成後,才能調度A的剩餘時間。和之前同樣,考慮到咱們的新假設,STCF可證實是最優的。
若是咱們知道任務長度,並且任務只使用CPU,而咱們惟一的衡量是週轉時間,STCF將是一個很好的策略。然而,引入分時系統改變了這一切。如今,用戶將會坐在終端前面,同時也要求系統的交互性好。所以,一個新的度量標準誕生了:響應時間(response time)。響應時間定義爲從任務到達系統到首次運行的時間。
STCF和相關方法在響應時間上並非很好。例如,若是3個工做同時到達,第三個工做必須等待前兩個工做所有運行後才能運行。這種方法雖然有很好的週轉時間,但對於響應時間和交互性是至關糟糕的。
爲了解決這個問題,咱們將介紹一種新的調度算法,一般被稱爲輪轉(Round-Robin,RR)調度。基本思想很簡單:RR在一個時間片(time slice,有時稱爲調度量子,schedulingquantum)內運行一個工做,而後切換到運行隊列中的下一個任務,而不是運行一個任務直到結束。它反覆執行,直到全部任務完成。
時間片長度對於RR是相當重要的。越短,RR在響應時間上表現越好。然而,時間片過短是有問題的:忽然上下文切換的成本將影響總體性能。上下文切換的成本不只僅來自保存和恢復少許寄存器的操做系統操做,程序運行時,它們在CPU高速緩存、TLB、分支預測器和其餘片上硬件中創建了大量的狀態。切換到另外一個工做會致使此狀態被刷新,且與當前運行的做業相關的新狀態被引入,這可能致使顯著的性能成本。
若是響應時間是咱們的惟一指標,那麼帶有合理時間片的RR,就會是很是好的調度程序。若是週轉時間是咱們的指標,那麼RR倒是最糟糕的策略之一。更通常地說,任何公平(fair)的政策(如RR),即在小規模的時間內將CPU均勻分配到活動進程之間,在週轉時間這類指標上表現不佳。
首先,咱們得放寬假設4:由於幾乎全部的程序都要執行I/O。調度程序顯然要在工做發起I/O請求時作出決定,應該在CPU上安排另外一項工做,調度程序還必須在I/O完成時作出決定。
有了應對I/O的基本方法,咱們來看最後的假設:調度程序知道每一個工做的長度。如前所述,這多是能夠作出的最糟糕的假設。事實上,在一個通用的操做系統中,系統一般對每一個做業的長度知之甚少。
咱們將介紹一種著名的調度方法——多級反饋隊列(Multi-level Feedback Queue,MLFQ)。該調度程序通過多年的一系列優化,出如今許多現代操做系統中。多級反饋隊列須要解決兩方面的問題。首先,它要優化週轉時間。其次,MLFQ但願給交互用戶(如用戶坐在屏幕前,等着進程結束)很好的交互體驗,所以須要下降響應時間。
MLFQ中有許多獨立的隊列(queue),每一個隊列有不一樣的優先級(priority level)。任什麼時候刻,一個工做只能存在於一個隊列中。MLFQ老是優先執行較高優先級的工做(即在較高級隊列中的工做)。
固然,每一個隊列中可能會有多個工做,所以具備一樣的優先級。在這種狀況下,咱們就對這些工做採用輪轉調度。
至此,咱們獲得了MLFQ的兩條基本規則:
咱們必須決定,在一個工做的生命週期中,MLFQ如何改變其優先級(在哪一個隊列中)。要作到這一點,咱們必須記得工做負載:既有運行時間很短、頻繁放棄CPU的交互型工做,也有須要不少CPU時間、響應時間卻不重要的長時間計算密集型工做。下面是咱們第一次嘗試優先級調整算法。
然而,這種算法有一些很是嚴重的缺點。
首先,會有飢餓(starvation)問題。若是系統有「太多」交互型工做,就會不斷佔用CPU,致使長工做永遠沒法獲得CPU。
其次,聰明的用戶會重寫程序,愚弄調度程序(game the scheduler)。愚弄調度程序指的是用一些卑鄙的手段欺騙調度程序,讓它給你遠超公平的資源。例如進程在時間片用完以前,調用一個I/O操做(好比訪問一個無關的文件),從而主動釋放CPU。如此即可以保持在高優先級,佔用更多的CPU時間。
最後,一個程序可能在不一樣時間表現不一樣。一個計算密集的進程可能在某段時間表現爲一個交互型的進程。用咱們目前的方法,它不會享受系統中其餘交互型工做的待遇。
一個簡單的思路是週期性地提高(boost)全部工做的優先級。能夠有不少方法作到,咱們就用最簡單的:將全部工做扔到最高優先級隊列。因而有了以下的新規則:
新規則解決了兩個問題。首先,進程不會餓死——在最高優先級隊列中,它會以輪轉的方式,與其餘高優先級工做分享CPU,從而最終得到執行。其次,若是一個CPU密集型工做變成了交互型,當它優先級提高時,調度程序會正確對待它。
不過,添加時間段S致使了明顯的問題:S的值應該如何設置?若是S設置得過高,長工做會飢餓;若是設置得過低,交互型工做又得不到合適的CPU時間比例。
如今還有一個問題要解決:如何阻止調度程序被愚弄?能夠看出,這裏的元兇是規則4a和4b,致使工做在時間片之內釋放CPU,就保留它的優先級。那麼應該怎麼作?
這裏的解決方案,是爲MLFQ的每層隊列提供更完善的CPU計時方式(accounting)。調度程序應該記錄一個進程在某一層中消耗的總時間,而不是在調度時從新計時。只要進程用完了本身的配額,就將它降到低一優先級的隊列中去。
所以,咱們重寫規則4a和4b:
關於MLFQ調度算法還有一些問題。其中一個大問題是如何配置一個調度程序,例如,配置多少隊列?每一層隊列的時間片配置多大?爲了不飢餓問題以及進程行爲改變,應該多久提高一次進程的優先級?這些問題都沒有顯而易見的答案,所以只有利用對工做負載的經驗,以及後續對調度程序的調優,纔會致使使人滿意的平衡。
例如,大多數的MLFQ變體都支持不一樣隊列可變的時間片長度。高優先級隊列一般只有較短的時間片(好比10ms或者更少),於是這一層的交互工做能夠更快地切換。相反,低優先級隊列中更多的是CPU密集型工做,配置更長的時間片會取得更好的效果。