調度:多級反饋隊列

多級反饋隊列(Multi-level Feedback Queue, MLFQ)是有Corbato在1962年提出的,用於兼容時分共享系統。現在其經過多年的優化,已經被應用於很多現代操作系統中。多級反饋隊列是爲了解決兩方面問題。一:優化週轉時間。在之前的進程調度中曾經提及過,這需要通過有線執行短工作來實現,但是問題是很少有進程可以在一開始就能正確預測它的工作要運行多久。第二個問題是降低響應時間,這可以通過時間片輪轉等方法實現,但這些策略的週轉時間卻很差。所以出現了多級反饋隊列,多級反饋隊列是使用歷史經驗來預測未來的一種方式,操作系統中很多地方都採用這種技術,操作系統以外的計算機技術,例如硬件的分支預測、緩存算法等也都有這種思想的使用。

 

一、基本規則

正如它的名字所描述的那樣,MLFQ中有多個隊列,每個隊列有不同的優先級。任何時刻,一個工作只能存在於一個隊列中。MLFQ總是優先執行較高優先級的工作(即那些在較高級隊列中的工作)。每個隊列中可能會有多個工作,它們具有同樣的優先級。在這種情況下,我們就對這些工作採用輪轉調度。至此,我們得到了MLFQ的兩條基本規則:

規則1:如果A的優先級大於B的優先級,運行A不運行B
規則2:如果A的優先級等於B的優先級,輪轉運行A和B

同時也就發現了MLFQ的關鍵就在於多個隊列的優先級如何設置,因爲只有具有合理的優先級設置策略才能讓操作系統真正的按照自己想要的順序不停的切換執行進程。MLFQ不會爲每個隊列或每個工作設置一個固定的優先級,前面已經說了,MLFQ是使用歷史經驗來預測未來的一種方式,所以會在運行的過程中通過隊列的反饋不斷的調整對應的優先級。最簡單的例子就是當你有兩個程序同時在進行下載操作,那麼當你使用鼠標點擊其中一個程序,這個程序就需要立即響應你的鼠標操作,那麼此時他的優先級就要被提高。

 

二、優先級的設置

考慮到實際使用中進程可能是有各種各樣的情況,例如會有運行時間很短會頻繁放棄CPU的多個小任務同時執行,也可能會有需要很多CPU時間,但響應時間卻不重要的長時間計算密集型工作,等等等等,所以需要在前面的基礎上調整算法:

規則3:工作進入系統時,放在最高優先級(最上層)隊列
規則4a:工作用完整個時間片後,降低其優先級(移入低一級隊列)
規則4b:如果工作在其時間片以內主動釋放CPU,則優先級不變

場景1:單個長工作

假如系統中只有一個需要長時間運行的工作,那麼按照當前的MLFQ調度執行的情況就如下圖:

                                                            

首先,工作剛剛到來時直接進入最高優先級(Q2),因爲此時沒有人與他爭奪CPU。在執行一個10ms的時間片後,調度程序將工作的優先級降低,放進Q1隊列。在Q1執行一個時間片後,再次降低優先級進入系統的最低優先級(Q0),之後就一直留在那裏。

場景2:長工作+短工作

A是一個長時間運行的CPU計算密集型工作,B是一個運行時間很短的交互型工作。假設A執行一段時間後B到達,那麼此時運行情況如下:

                                                            

A(用黑色表示)在B到達之前的運行狀態與場景1一致。B(用灰色表示)在時間爲100ms時到達,此時會直接被加入最高優先級隊列。由於它的運行時間很短(只有20ms),經過兩個時間片,在被移入最低優先級隊列之前,B執行完畢。然後A繼續運行(在低優先級)。

可以看到MLFQ算法的目標是:在不知道工作是短工作還是長工作時,就假設其是短工作,並賦予最高優先級。如果確實是短工作,則很快會執行完畢,否則將被慢慢移入低優先級隊列,而這時該工作也被認爲是長工作了。通過這種方式,MLFQ近似於SJF。

場景3:結合I/O

根據上述規則4b,如果進程在時間片用完之前主動放棄CPU,則保持它的優先級不變。這條規則的意圖很簡單:假設交互型工作中有大量的I/O操作(比如等待用戶的鍵盤或鼠標輸入),它會在時間片用完之前放棄CPU。在這種情況下,我們不想處罰它,只是保持它的優先級不變。

下圖展示了這個運行過程,交互型工作B(用灰色表示)每執行1ms便需要進行I/O操作,它與長時間運行的工作A(用黑色表示)競爭CPU。MLFQ算法保持B在最高優先級,因爲B總是讓出CPU。如果B是交互型工作,MLFQ就進一步實現了它的目標,讓交互型工作快速運行。

                                                              

至此,可以看到一個基礎版本的MLFQ。它與之前的調度算法比起來有了進步,長工作之間可以公平地分享CPU,又能給短工作或交互型工作很好的響應時間。但是,也能看到這種算法有一些非常嚴重的缺點。首先,會有飢餓問題。如果系統有“太多”交互型工作,就會不斷佔用CPU,導致長工作永遠無法得到CPU,但實際上在這種情況下,長工作也應該在合適的時間被執行才能不會被認爲是已經停止工作。其次,某些用戶會用一些手段欺騙調度程序,讓它給予進程遠超公平的資源。例如,進程在時間片用完之前,調用一個I/O操作(比如訪問一個無關的文件),從而主動釋放CPU。如此便可以保持在高優先級,佔用更多的CPU時間。假如可以將進程設置爲每運行99%的時間片時間就主動放棄一次CPU,那麼這個進程可以幾乎獨佔CPU。最後,一個程序可能在不同時間表現不同。一個計算密集的進程可能在某段時間表現爲一個交互型的進程,但在當前的算法下,它無法再回到最高優先級。

於是可以得到下一步的優化思路:對於一個進程不能將其認爲是固定的長工作或是短工作,需要根據他實際的運行情況不停的調整他的狀態。那麼調整一個進程狀態的時機選擇條件該如何設置?考慮到操作系統需要基於時鐘中斷,那麼可以人爲的模擬出一種時鐘中斷,也就是定時調整所有進程的優先級。於是可以得到一條新的規則:

規則5:每經過一段時間,就將系統中所有工作重新加入最高優先級隊列

當加入規則5之後,可以看到進程餓死的情況不會存在了,因爲每隔一段時間每個進程都會變成最高優先級,也就是每隔一定時間就至少會被執行一次。同樣,操作系統也就不會再被欺騙了,或者說被欺騙也無所謂,因爲此時這種欺騙已經可以認爲是合法的,規則5所做的其實只是以操作系統的角度允許了這種欺騙行爲,當然欺騙還是與規則5不一樣的,規則的主要目標是爲了避免進程在長短兩種工作狀態切換時操作系統無法及時響應,而欺騙是單純的長工作想要獨佔CPU。

在看一種新場景:長工作與兩個交互型短工作競爭CPU時的行爲。

下圖左邊沒有優先級提升,長工作在兩個短工作到達後被餓死。右邊每50ms就有一次優先級提升,因此至少保證長工作每過50ms就被提升到最高優先級,從而定期獲得執行。

                    

解決了前面的問題,但此時又得到了一個新的問題,調整優先級的時間間隔該如何確定?如果時間間隔過長,那麼可能會執行完短工作才能將長工作的優先級調整,此時調整也沒什麼意義了;如果時間過短,長工作有因爲不斷被設爲最高優先級會耽誤短工作的儘快執行結束。經過前面的對操作系統不斷完善,此時自然可以想到根據時間中斷來設置時間間隔。但是如果簡單的使用時鐘中斷來設置時間間隔那麼不就又回到了時間片輪轉算法了。所以就有了一個新的解決方案:爲MLFQ的每層隊列提供更完善的計時方式,也就是調度程序應該記錄每個進程在某一層中消耗的總時間,而不是在調度時重新計時。只要進程用完了自己的配額,就將它降到低一級隊列中去。不論它是一次用完的,還是拆成很多次用完。在這種方式下,可以重新規則4,得到:

規則4:一旦工作用完了其在某一層中的時間配額(無論中間主動放棄了多少次CPU),就降低其優先級(移入低一級隊列)

此時來看一種情況:下圖對比了在規則4a、4b的策略下(左),以及在新的規則4(右)的策略下,同樣試圖欺騙調度程序的進程的表現。沒有規則4的保護時,進程可以在每個時間片結束前發起一次I/O操作,從而壟斷CPU時間。有了這樣的保護後,不論進程的I/O行爲如何,都會慢慢地降低優先級,因而無法獲得超過公平的CPU時間比例。

                                                              

 

最後其實MLFQ還有一些問題沒有確切的答案。例如:配置多少個隊列?每層隊列時間片該爲多大?具體該多久提升一次所有進程的優先級?這些其實沒有一個令所有人百分百滿意的答案,所以這些都是在對操作系統的不斷優化中不斷修改設置最終得到一個令大多數人至少是開發者滿意的結果。例如,大多數的MLFQ變體都支持不同隊列可變的時間片長度。高優先級隊列通常只有較短的時間片(比如10ms或者更少),因而這一層的交互工作可以更快地切換。相反,低優先級隊列中更多的是CPU密集型工作,配置更長的時間片會取得更好的效果。後來John Ousterhout教授提出了Ousterhout定律,也就是提供一組決定進程在生命週期中如何調整優先級,每層時間片多大,多久調整優先級的表。這個表可以由管理員更改,從而使得調度程序的行爲方式不同。在不對錶修改時,就採用默認的執行方式。另外現在這篇文章裏所有對MLFQ的介紹都是基於操作系統超脫於MLFQ來講的,也就是沒有將操作系統看作是一個應用程序,但實際上有一些操作系統會將自己也看做是一個應用程序,但會將自己始終設置爲最高優先級,並且不允許用戶更改,這也是一種很有意思的實現方式。

 

最後,MLFQ的有趣原因在於它不需要對進程的運行信息有足夠的瞭解,只要按照自己既有的規則執行即可實現對進程的合理調度。MLFQ基本可以滿足各種工作的需求,對於短時間運行的交互性工作,可以獲得類似於SJF/STCF的良好的全局性;對於長時間運行的計算密集型工作也可以讓其在不長時間佔用CPU的情況下不斷地執行。所以現在很多系統都具有MLFQ,雖然具體時間可能天差地別,這些系統包括UNIX系統、Solaris以及Windows NT和其後的Windows系列操作系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章