大廠面試愛問的「調度算法」,20 張圖一舉拿下


前言

最近,我偷偷潛伏在各大技術羣,因爲秋招在即,看到不少小夥伴分享的大廠面經。

然後發現,操作系統的知識點考察還是比較多的,大廠就是大廠就愛問基礎知識。其中,關於操作系統的「調度算法」考察也算比較頻繁。

所以,我這邊總結了操作系統的三大調度機制,分別是「進程調度/頁面置換/磁盤調度算法」,供大家複習,希望大家在秋招能斬獲自己心意的 offer。

本文提綱本文提綱

正文

進程調度算法

進程調度算法也稱 CPU 調度算法,畢竟進程是由 CPU 調度的。

當 CPU 空閒時,操作系統就選擇內存中的某個「就緒狀態」的進程,並給其分配 CPU。

什麼時候會發生 CPU 調度呢?通常有以下情況:

  1. 當進程從運行狀態轉到等待狀態;
  2. 當進程從運行狀態轉到就緒狀態;
  3. 當進程從等待狀態轉到就緒狀態;
  4. 當進程從運行狀態轉到終止狀態;

其中發生在 1 和 4 兩種情況下的調度稱爲「非搶佔式調度」,2 和 3 兩種情況下發生的調度稱爲「搶佔式調度」。

非搶佔式的意思就是,當進程正在運行時,它就會一直運行,直到該進程完成或發生某個事件而被阻塞時,纔會把 CPU 讓給其他進程。

而搶佔式調度,顧名思義就是進程正在運行的時,可以被打斷,使其把 CPU 讓給其他進程。那搶佔的原則一般有三種,分別是時間片原則、優先權原則、短作業優先原則。

你可能會好奇爲什麼第 3 種情況也會發生 CPU 調度呢?假設有一個進程是處於等待狀態的,但是它的優先級比較高,如果該進程等待的事件發生了,它就會轉到就緒狀態,一旦它轉到就緒狀態,如果我們的調度算法是以優先級來進行調度的,那麼它就會立馬搶佔正在運行的進程,所以這個時候就會發生 CPU 調度。

那第 2 種狀態通常是時間片到的情況,因爲時間片到了就會發生中斷,於是就會搶佔正在運行的進程,從而佔用 CPU。

調度算法影響的是等待時間(進程在就緒隊列中等待調度的時間總和),而不能影響進程真在使用 CPU 的時間和 I/O 時間。

接下來,說說常見的調度算法:

  • 先來先服務調度算法
  • 最短作業優先調度算法
  • 高響應比優先調度算法
  • 時間片輪轉調度算法
  • 最高優先級調度算法
  • 多級反饋隊列調度算法

先來先服務調度算法

最簡單的一個調度算法,就是非搶佔式的先來先服務(First Come First Severd, FCFS)算法了。

FCFS 調度算法FCFS 調度算法

顧名思義,先來後到,每次從就緒隊列選擇最先進入隊列的進程,然後一直運行,直到進程退出或被阻塞,纔會繼續從隊列中選擇第一個進程接着運行。

這似乎很公平,但是當一個長作業先運行了,那麼後面的短作業等待的時間就會很長,不利於短作業。

FCFS 對長作業有利,適用於 CPU 繁忙型作業的系統,而不適用於 I/O 繁忙型作業的系統。

最短作業優先調度算法

最短作業優先(Shortest Job First, SJF)調度算法同樣也是顧名思義,它會優先選擇運行時間最短的進程來運行,這有助於提高系統的吞吐量。

SJF 調度算法SJF 調度算法

這顯然對長作業不利,很容易造成一種極端現象。

比如,一個長作業在就緒隊列等待運行,而這個就緒隊列有非常多的短作業,那麼就會使得長作業不斷的往後推,週轉時間變長,致使長作業長期不會被運行。

高響應比優先調度算法

前面的「先來先服務調度算法」和「最短作業優先調度算法」都沒有很好的權衡短作業和長作業。

那麼,高響應比優先 (Highest Response Ratio Next, HRRN)調度算法主要是權衡了短作業和長作業。

每次進行進程調度時,先計算「響應比優先級」,然後把「響應比優先級」最高的進程投入運行,「響應比優先級」的計算公式:

從上面的公式,可以發現:

  • 如果兩個進程的「等待時間」相同時,「要求的服務時間」越短,「響應比」就越高,這樣短作業的進程容易被選中運行;
  • 如果兩個進程「要求的服務時間」相同時,「等待時間」越長,「響應比」就越高,這就兼顧到了長作業進程,因爲進程的響應比可以隨時間等待的增加而提高,當其等待時間足夠長時,其響應比便可以升到很高,從而獲得運行的機會;

時間片輪轉調度算法

最古老、最簡單、最公平且使用最廣的算法就是時間片輪轉(Round Robin, RR)調度算法

RR 調度算法RR 調度算法

每個進程被分配一個時間段,稱爲時間片(Quantum),即允許該進程在該時間段中運行。

  • 如果時間片用完,進程還在運行,那麼將會把此進程從 CPU 釋放出來,並把 CPU 分配另外一個進程;
  • 如果該進程在時間片結束前阻塞或結束,則 CPU 立即進行切換;

另外,時間片的長度就是一個很關鍵的點:

  • 如果時間片設得太短會導致過多的進程上下文切換,降低了 CPU 效率;
  • 如果設得太長又可能引起對短作業進程的響應時間變長。將

通常時間片設爲 20ms~50ms 通常是一個比較合理的折中值。

最高優先級調度算法

前面的「時間片輪轉算法」做了個假設,即讓所有的進程同等重要,也不偏袒誰,大家的運行時間都一樣。

但是,對於多用戶計算機系統就有不同的看法了,它們希望調度是有優先級的,即希望調度程序能從就緒隊列中選擇最高優先級的進程進行運行,這稱爲最高優先級(Highest Priority First,HPF)調度算法

進程的優先級可以分爲,靜態優先級或動態優先級:

  • 靜態優先級:創建進程時候,就已經確定了優先級了,然後整個運行時間優先級都不會變化;
  • 動態優先級:根據進程的動態變化調整優先級,比如如果進程運行時間增加,則降低其優先級,如果進程等待時間(就緒隊列的等待時間)增加,則升高其優先級,也就是隨着時間的推移增加等待進程的優先級

該算法也有兩種處理優先級高的方法,非搶佔式和搶佔式:

  • 非搶佔式:當就緒隊列中出現優先級高的進程,運行完當前進程,再選擇優先級高的進程。
  • 搶佔式:當就緒隊列中出現優先級高的進程,當前進程掛起,調度優先級高的進程運行。

但是依然有缺點,可能會導致低優先級的進程永遠不會運行。

多級反饋隊列調度算法

多級反饋隊列(Multilevel Feedback Queue)調度算法是「時間片輪轉算法」和「最高優先級算法」的綜合和發展。

顧名思義:

  • 「多級」表示有多個隊列,每個隊列優先級從高到低,同時優先級越高時間片越短。
  • 「反饋」表示如果有新的進程加入優先級高的隊列時,立刻停止當前正在運行的進程,轉而去運行優先級高的隊列;
多級反饋隊列多級反饋隊列

來看看,它是如何工作的:

  • 設置了多個隊列,賦予每個隊列不同的優先級,每個隊列優先級從高到低,同時優先級越高時間片越短
  • 新的進程會被放入到第一級隊列的末尾,按先來先服務的原則排隊等待被調度,如果在第一級隊列規定的時間片沒運行完成,則將其轉入到第二級隊列的末尾,以此類推,直至完成;
  • 當較高優先級的隊列爲空,才調度較低優先級的隊列中的進程運行。如果進程運行時,有新進程進入較高優先級的隊列,則停止當前運行的進程並將其移入到原隊列末尾,接着讓較高優先級的進程運行;

可以發現,對於短作業可能可以在第一級隊列很快被處理完。對於長作業,如果在第一級隊列處理不完,可以移入下次隊列等待被執行,雖然等待的時間變長了,但是運行時間也會更長了,所以該算法很好的兼顧了長短作業,同時有較好的響應時間。


內存頁面置換算法

在瞭解內存頁面置換算法前,我們得先談一下缺頁異常(缺頁中斷)

當 CPU 訪問的頁面不在物理內存時,便會產生一個缺頁中斷,請求操作系統將所缺頁調入到物理內存。那它與一般中斷的主要區別在於:

  • 缺頁中斷在指令執行「期間」產生和處理中斷信號,而一般中斷在一條指令執行「完成」後檢查和處理中斷信號。
  • 缺頁中斷返回到該指令的開始重新執行「該指令」,而一般中斷返回回到該指令的「下一個指令」執行。

我們來看一下缺頁中斷的處理流程,如下圖:

缺頁中斷的處理流程缺頁中斷的處理流程
  1. 在 CPU 裏訪問一條 Load M 指令,然後 CPU 會去找 M 所對應的頁表項。
  2. 如果該頁表項的狀態位是「有效的」,那 CPU 就可以直接去訪問物理內存了,如果狀態位是「無效的」,則 CPU 則會發送缺頁中斷請求。
  3. 操作系統收到了缺頁中斷,則會執行缺頁中斷處理函數,先會查找該頁面在磁盤中的頁面的位置。
  4. 找到磁盤中對應的頁面後,需要把該頁面換入到物理內存中,但是在換入前,需要在物理內存中找空閒頁,如果找到空閒頁,就把頁面換入到物理內存中。
  5. 頁面從磁盤換入到物理內存完成後,則把頁表項中的狀態位修改爲「有效的」。
  6. 最後,CPU 重新執行導致缺頁異常的指令。

上面所說的過程,第 4 步是能在物理內存找到空閒頁的情況,那如果找不到呢?

找不到空閒頁的話,就說明此時內存已滿了,這時候,就需要「頁面置換算法」選擇一個物理頁,如果該物理頁有被修改過(髒頁),則把它換出到磁盤,然後把該被置換出去的頁表項的狀態改成「無效的」,最後把正在訪問的頁面裝入到這個物理頁中。

這裏提一下,頁表項通常有如下圖的字段:

那其中:

  • 狀態位:用於表示該頁是否有效,也就是說是否在物理內存中,供程序訪問時參考。
  • 訪問字段:用於記錄該頁在一段時間被訪問的次數,供頁面置換算法選擇出頁面時參考。
  • 修改位:表示該頁在調入內存後是否有被修改過,由於內存中的每一頁都在磁盤上保留一份副本,因此,如果沒有修改,在置換該頁時就不需要將該頁寫回到磁盤上,以減少系統的開銷;如果已經被修改,則將該頁重寫到磁盤上,以保證磁盤中所保留的始終是最新的副本。
  • 硬盤地址:用於指出該頁在硬盤上的地址,通常是物理塊號,供調入該頁時使用。

這裏我整理了虛擬內存的管理整個流程,你可以從下面這張圖看到:

虛擬內存的流程虛擬內存的流程

所以,頁面置換算法的功能是,當出現缺頁異常,需調入新頁面而內存已滿時,選擇被置換的物理頁面,也就是說選擇一個物理頁面換出到磁盤,然後把需要訪問的頁面換入到物理頁。

那其算法目標則是,儘可能減少頁面的換入換出的次數,常見的頁面置換算法有如下幾種:

  • 最佳頁面置換算法(OPT
  • 先進先出置換算法(FIFO
  • 最近最久未使用的置換算法(LRU
  • 時鐘頁面置換算法(Lock
  • 最不常用置換算法(LFU

最佳頁面置換算法

最佳頁面置換算法基本思路是,置換在「未來」最長時間不訪問的頁面

所以,該算法實現需要計算內存中每個邏輯頁面的「下一次」訪問時間,然後比較,選擇未來最長時間不訪問的頁面。

我們舉個例子,假設一開始有 3 個空閒的物理頁,然後有請求的頁面序列,那它的置換過程如下圖:

最佳頁面置換算法最佳頁面置換算法

在這個請求的頁面序列中,缺頁共發生了 7 次(空閒頁換入 3 次 + 最優頁面置換 4 次),頁面置換共發生了 4 次。

這很理想,但是實際系統中無法實現,因爲程序訪問頁面時是動態的,我們是無法預知每個頁面在「下一次」訪問前的等待時間。

所以,最佳頁面置換算法作用是爲了衡量你的算法的效率,你的算法效率越接近該算法的效率,那麼說明你的算法是高效的。

先進先出置換算法

既然我們無法預知頁面在下一次訪問前所需的等待時間,那我們可以選擇在內存駐留時間很長的頁面進行中置換,這個就是「先進先出置換」算法的思想。

還是以前面的請求的頁面序列作爲例子,假設使用先進先出置換算法,則過程如下圖:

先進先出置換算法先進先出置換算法

在這個請求的頁面序列中,缺頁共發生了 10 次,頁面置換共發生了 7 次,跟最佳頁面置換算法比較起來,性能明顯差了很多。

最近最久未使用的置換算法

最近最久未使用(LRU)的置換算法的基本思路是,發生缺頁時,選擇最長時間沒有被訪問的頁面進行置換,也就是說,該算法假設已經很久沒有使用的頁面很有可能在未來較長的一段時間內仍然不會被使用。

這種算法近似最優置換算法,最優置換算法是通過「未來」的使用情況來推測要淘汰的頁面,而 LRU 則是通過「歷史」的使用情況來推測要淘汰的頁面。

還是以前面的請求的頁面序列作爲例子,假設使用最近最久未使用的置換算法,則過程如下圖:

最近最久未使用的置換算法最近最久未使用的置換算法

在這個請求的頁面序列中,缺頁共發生了 9 次,頁面置換共發生了 6 次,跟先進先出置換算法比較起來,性能提高了一些。

雖然 LRU 在理論上是可以實現的,但代價很高。爲了完全實現 LRU,需要在內存中維護一個所有頁面的鏈表,最近最多使用的頁面在表頭,最近最少使用的頁面在表尾。

困難的是,在每次訪問內存時都必須要更新「整個鏈表」。在鏈表中找到一個頁面,刪除它,然後把它移動到表頭是一個非常費時的操作。

所以,LRU 雖然看上去不錯,但是由於開銷比較大,實際應用中比較少使用。

時鐘頁面置換算法

那有沒有一種即能優化置換的次數,也能方便實現的算法呢?

時鐘頁面置換算法就可以兩者兼得,它跟 LRU 近似,又是對 FIFO 的一種改進。

該算法的思路是,把所有的頁面都保存在一個類似鐘面的「環形鏈表」中,一個錶針指向最老的頁面。

當發生缺頁中斷時,算法首先檢查錶針指向的頁面:

  • 如果它的訪問位位是 0 就淘汰該頁面,並把新的頁面插入這個位置,然後把錶針前移一個位置;
  • 如果訪問位是 1 就清除訪問位,並把錶針前移一個位置,重複這個過程直到找到了一個訪問位爲 0 的頁面爲止;

我畫了一副時鐘頁面置換算法的工作流程圖,你可以在下方看到:

時鐘頁面置換算法時鐘頁面置換算法

瞭解了這個算法的工作方式,就明白爲什麼它被稱爲時鐘(Clock)算法了。

最不常用算法

最不常用(LFU)算法,這名字聽起來很調皮,但是它的意思不是指這個算法不常用,而是當發生缺頁中斷時,選擇「訪問次數」最少的那個頁面,並將其淘汰

它的實現方式是,對每個頁面設置一個「訪問計數器」,每當一個頁面被訪問時,該頁面的訪問計數器就累加 1。在發生缺頁中斷時,淘汰計數器值最小的那個頁面。

看起來很簡單,每個頁面加一個計數器就可以實現了,但是在操作系統中實現的時候,我們需要考慮效率和硬件成本的。

要增加一個計數器來實現,這個硬件成本是比較高的,另外如果要對這個計數器查找哪個頁面訪問次數最小,查找鏈表本身,如果鏈表長度很大,是非常耗時的,效率不高。

但還有個問題,LFU 算法只考慮了頻率問題,沒考慮時間的問題,比如有些頁面在過去時間裏訪問的頻率很高,但是現在已經沒有訪問了,而當前頻繁訪問的頁面由於沒有這些頁面訪問的次數高,在發生缺頁中斷時,就會可能會誤傷當前剛開始頻繁訪問,但訪問次數還不高的頁面。

那這個問題的解決的辦法還是有的,可以定期減少訪問的次數,比如當發生時間中斷時,把過去時間訪問的頁面的訪問次數除以 2,也就說,隨着時間的流失,以前的高訪問次數的頁面會慢慢減少,相當於加大了被置換的概率。


磁盤調度算法

我們來看看磁盤的結構,如下圖:

磁盤的結構磁盤的結構

常見的機械磁盤是上圖左邊的樣子,中間圓的部分是磁盤的盤片,一般會有多個盤片,每個盤面都有自己的磁頭。右邊的圖就是一個盤片的結構,盤片中的每一層分爲多個磁道,每個磁道分多個扇區,每個扇區是 512 字節。那麼,多個具有相同編號的磁道形成一個圓柱,稱之爲磁盤的柱面,如上圖裏中間的樣子。

磁盤調度算法的目的很簡單,就是爲了提高磁盤的訪問性能,一般是通過優化磁盤的訪問請求順序來做到的。

尋道的時間是磁盤訪問最耗時的部分,如果請求順序優化的得當,必然可以節省一些不必要的尋道時間,從而提高磁盤的訪問性能。

假設有下面一個請求序列,每個數字代表磁道的位置:

98,183,37,122,14,124,65,67

初始磁頭當前的位置是在第 53 磁道。

接下來,分別對以上的序列,作爲每個調度算法的例子,那常見的磁盤調度算法有:

  • 先來先服務算法
  • 最短尋道時間優先算法
  • 掃描算法算法
  • 循環掃描算法
  • LOOK 與 C-LOOK 算法

先來先服務

先來先服務(First-Come,First-Served,FCFS),顧名思義,先到來的請求,先被服務。

那按照這個序列的話:

98,183,37,122,14,124,65,67

那麼,磁盤的寫入順序是從左到右,如下圖:

先來先服務先來先服務

先來先服務算法總共移動了 640 個磁道的距離,這麼一看這種算法,比較簡單粗暴,但是如果大量進程競爭使用磁盤,請求訪問的磁道可能會很分散,那先來先服務算法在性能上就會顯得很差,因爲尋道時間過長。

最短尋道時間優先

最短尋道時間優先(Shortest Seek First,SSF)算法的工作方式是,優先選擇從當前磁頭位置所需尋道時間最短的請求,還是以這個序列爲例子:

98,183,37,122,14,124,65,67

那麼,那麼根據距離磁頭( 53 位置)最近的請求的算法,具體的請求則會是下列從左到右的順序:

65,67,37,14,98,122,124,183

最短尋道時間優先最短尋道時間優先

磁頭移動的總距離是 236 磁道,相比先來先服務性能提高了不少。

但這個算法可能存在某些請求的飢餓,因爲本次例子我們是靜態的序列,看不出問題,假設是一個動態的請求,如果後續來的請求都是小於 183
磁道的,那麼 183 磁道可能永遠不會被響應,於是就產生了飢餓現象,這裏產生飢餓的原因是磁頭在一小塊區域來回移動

掃描算法

最短尋道時間優先算法會產生飢餓的原因在於:磁頭有可能再一個小區域內來回得移動。

爲了防止這個問題,可以規定:磁頭在一個方向上移動,訪問所有未完成的請求,直到磁頭到達該方向上的最後的磁道,才調換方向,這就是掃描(Scan)算法

這種算法也叫做電梯算法,比如電梯保持按一個方向移動,直到在那個方向上沒有請求爲止,然後改變方向。

還是以這個序列爲例子,磁頭的初始位置是 53:

98,183,37,122,14,124,65,67

那麼,假設掃描調度算先朝磁道號減少的方向移動,具體請求則會是下列從左到右的順序:

37,14,0,65,67,98,122,124,183

掃描算法掃描算法

磁頭先響應左邊的請求,直到到達最左端( 0 磁道)後,纔開始反向移動,響應右邊的請求。

掃描調度算法性能較好,不會產生飢餓現象,但是存在這樣的問題,中間部分的磁道會比較佔便宜,中間部分相比其他部分響應的頻率會比較多,也就是說每個磁道的響應頻率存在差異。

循環掃描算法

掃描算法使得每個磁道響應的頻率存在差異,那麼要優化這個問題的話,可以總是按相同的方向進行掃描,使得每個磁道的響應頻率基本一致。

循環掃描(Circular Scan, CSCAN )規定:只有磁頭朝某個特定方向移動時,才處理磁道訪問請求,而返回時直接快速移動至最靠邊緣的磁道,也就是復位磁頭,這個過程是很快的,並且返回中途不處理任何請求,該算法的特點,就是磁道只響應一個方向上的請求

還是以這個序列爲例子,磁頭的初始位置是 53:

98,183,37,122,14,124,65,67

那麼,假設循環掃描調度算先朝磁道增加的方向移動,具體請求會是下列從左到右的順序:

65,67,98,122,124,183,1990,14,37

循環掃描算法循環掃描算法

磁頭先響應了右邊的請求,直到碰到了最右端的磁道 199,就立即回到磁盤的開始處(磁道 0),但這個返回的途中是不響應任何請求的,直到到達最開始的磁道後,才繼續順序響應右邊的請求。

循環掃描算法相比於掃描算法,對於各個位置磁道響應頻率相對比較平均。

LOOK 與 C-LOOK算法

我們前面說到的掃描算法和循環掃描算法,都是磁頭移動到磁盤「最始端或最末端」纔開始調換方向。

那這其實是可以優化的,優化的思路就是磁頭在移動到「最遠的請求」位置,然後立即反向移動。

那針對 SCAN 算法的優化則叫 LOOK 算法,它的工作方式,磁頭在每個方向上僅僅移動到最遠的請求位置,然後立即反向移動,而不需要移動到磁盤的最始端或最末端,反向移動的途中會響應請求

LOOK 算法LOOK 算法

而針 C-SCAN 算法的優化則叫 C-LOOK,它的工作方式,磁頭在每個方向上僅僅移動到最遠的請求位置,然後立即反向移動,而不需要移動到磁盤的最始端或最末端,反向移動的途中不會響應請求

C-LOOK 算法C-LOOK 算法

絮叨

大家好,我是小林,一個專爲大家圖解的工具人,我們下次見!

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