《Linux內核設計與實現》第四章學習筆記

第四章 進程調度

【學習時間:1小時45分 撰寫博客時間:2小時10分鐘】算法

【學習內容:Linux的進程調度實現、搶佔和上下文切換、與調度相關的系統調用】安全

調度程序負責決定將哪一個進程投入運行,什麼時候運行以及運行多長時間。進程調度程序可看作在可運行態進程之間分配有限的處理器時間資源的內核子系統。服務器

最大限度利用處理器時間的原則:只要有能夠執行的進程,那麼總會有程序正在執行。網絡

1、多任務

1.概念:多任務操做系統就是能同時併發地交互執行多個進程的操做系統,在單處理器機器上這會產生多個進程在同時運行的幻覺,在多處理器機器上,這會使多個進程在不一樣的處理機上真正同時、並行地運行。併發

  • 不管在單處理器或者多處理器機器上,多任務操做系統都能使多個進程處於堵塞或者睡眠狀態,也就是說,實際上不被投入執行,直到工做確實就緒
  • 這些任務儘管位於內存,但並不處於可運行狀態。相反,這些進程利用內核阻塞本身,直到某一事件(鍵盤輸入網絡數據、過一段時間等)發生。所以,現代Linux系統也許有100個進程在內存,可是隻有一個處於可運行狀態

2. 分類:多任務系統能夠劃分爲兩類函數

  • 非搶佔式多任務。進程會一直執行直到本身主動中止運行(這一步驟稱爲讓步)
  • 搶佔式多任務。Linux/Unix使用的是搶佔式的方式;強制的掛起進程的動做就叫作搶佔。進程在被搶佔以前可以運行的時間是預先設置好的(也就是進程的時間片)

2、Linux的進程調度

在Linux 2.5開發系列的內核中,調度程序作了大手術,開始採用了一種叫作O(1)調度程序的新調度程序——它是由於其算法的行爲而得名的。性能

  • 它解決了先前版本Linux調度程序的許多不足,引入了許多強大的新特性和性能特徵,這裏主要要感謝靜態時間片算法和針對每一處理器的運行隊列,它們幫助咱們擺脫了先前調度程序設計上的限制
  • O(1)調度程序雖然對於大服務器的工做負載很理想,可是在有不少交互程序要運行的桌面系統上則表現不佳,由於其缺乏交互進程,自2.6內核系統開發初期,開發人員爲了提升對交互程序的調度性能引入了新的進程調度算法,其中最爲著名的是「反轉樓梯最後期限調度算法,該算法吸收了隊列理論,將公平調度的概念引入了Linux調度程序。而且最終在2.6.23內核版本中替代了O(1)調度算法,它此刻被稱爲「徹底公平調度算法」,或者簡稱CFS

3、策略

  策略決定調度程序在什麼時候讓什麼進程運行。學習

3.1 I/O消耗型和處理器消耗型的進程

1. I/O消耗型進程優化

  • 進程的大部分時間用來提交I/O請求或者等待I/O請求
  • 多數用戶圖形界面(GUI)都屬於I/O密集型

2. 處理器耗費型spa

  • 時間大多數用在執行代碼上
  • 例如MATLAB
  • 每每要延長運行時間並下降調度頻率

3. 調度策略一般要在兩個矛盾的目標中間尋找平衡:進程響應迅速(響應時間短)和最大系統利用率(高吞吐量),爲了知足上述需求,調度程序一般採用一套很是複雜的算法來決定最值得運行的進程投入運行,可是它每每並不保證低優先級進程會被公平對待,Unⅸ系統的調度程序更傾向於I/O消耗型程序,以提供更好的程序響應速度,Linux爲了保證交互式應用和桌面系統的性能,因此對進程的響應作了優化(縮短響應想間)更傾向於優先調度I/O消耗型進程,雖然如此,調度程序也並未忽略處理器消耗型的進程。

3.2 進程優先級

1. 基於優先級的調度:優先極高的進程先運行,相同優先級的進程按照輪轉方式進行調度。

2. 優先級分爲兩類:

  • nice值(從-20——+19):默認值爲0;數值越大意味着優先級越低;能夠經過 ps-el查看系統進程列表並找到NI標記列對應的優先級
  • 實時優先級(從0——99):越高的實時優先級級數意味着進程優先級越高

注:兩者互不交互。

3.3 時間片

  1. 時間片表示進程在被搶佔以前所可以持續運行的時間。
  2. 調度策略必須肯定一個默認的時間片。
  3. Linux的CFS調度器並無直接劃分時間片到進程,而是將處理器的使用比例劃分給了進程。即其搶佔時機取決於新的可執行程序消耗了多少處理器使用比,若是消耗的使用比比當前進程小,則新進程當即投入運行搶佔當前進程。

3.4 調度策略的活動

4、Linux調度算法

4.1 調度器類

  1. Linux調度器是以模塊方式提供的(也就是調度器類),目的是容許不一樣類型的進程能夠有針對性地選擇調度算法。
  2. 調度器類容許多種不一樣的可動態添加的調度算法並存,調度屬於本身範疇的進程。
  3. 每一個調度器都有一個優先級,基礎的調度器代碼定義在sched_ fair.c文件中,它會按照優先級順序遍歷調度類,擁有一個可執行進程的最高優先級的調度器類勝出,去選擇下面要執行的那一個程序。徹底公平調度(CS)是一個針對普通進程的調度類,在Linux中稱爲SCHED_NORMAL。

4.2 Unix系統中的進程調度

  1. 將nice值映射到時間片的話,就必須將nice值對應處處理器的絕對時間;這樣會致使進程切換沒法最優進行。
  2. 若是使用相對nice值,所帶來的效果將會極大取決於其nice的初始值。
  3. 若是執行nice值到時間片的映射,時間片極大受制於定時器。

  CFS採用的方法是對時間片分配方式進行根本性的從新設計(就進程調度器而言)徹底摒棄時間片而是分配給進程一個處理器使用比重,經過這種方式,CFS的確保了進程調度中能有恆定的公平性,而將切換頻率置於不斷變更中。

4.3 公平調度

CFS基於一個簡單的理念:進程調度的效果應當如同系統具有一個理想中的完美任務處理器。CFS的作法以下:

  • 容許每一個進程運行一段時間、循環輪轉、選擇運行最少的進程做爲下一個運行進程
  • nice值做爲進程得到的處理器運行比的權重(而不是徹底由nice決定時間片)
  • 每一個進程都按照其權重在所有的可運行進程中所佔的比例對應的「時間片」來運行

在理想狀況下,完美的多任務處理器模型應該是這樣的:咱們能在5ms內同時運行兩個進程,它們各自使用處理器一半的能力。

5、Linux調度的實現

CFS相關代碼位於kernel/sched_fair.c中。它有四個組成部分:

  • 時間記帳
  • 進程選擇
  • 調度器入口
  • 睡眠和喚醒

5.1 時間記帳

  全部的調度器都必須對進程運行時間作記帳。多數Unix系統,分配一個時間片給每個進程。那麼當每次系統時鐘節拍發生時,時間片都會被減小一個節拍週期。

1. 調度器實體結構

  調度器實體結構做爲一個名爲se的成員變量,嵌入在進程描述符struct task_ struct內。

2. 虛擬實時

  • vruntime變量存放進程的虛擬運行時間
  • update_ curr()計算當前進程的執行時間,是由系統定時器週期性調用的

5.2 進程選擇

1. CFS算法核心:選擇具備最小vrntime的任務

2. 具體作法:利用紅黑樹rbtree(以節點形式存儲數據的二叉樹)

3. 舉例:

  • 選擇下一個任務:從根節點中序遍歷二叉樹,一直到葉子節點(也就是vrntime最小的進程)
  • 向樹中加入進程:在進程變爲可執行狀態或者經過fork()調用第一次建立進程
  • 從樹中刪除進程:發生在進程阻塞或者終止的時候

5.3 調度器入口

  1. 進程調度的主要入口點是函數schedule(),定義在kernel/sched.c中。這正是內和其餘部分用於調度進程調度器的入口。
  2. 這一函數最重要的工做就是調用pick_ next_ state(),依次檢查每個調度類,並從最高優先級的調度類中,選擇最高優先級進程。

5.4 睡眠和喚醒

1. 等待隊列:休眠經過等待隊列進行處理,等待隊列是由某些事件發生的進程組成的簡單鏈表。

  • 進程把本身標記成休眠狀態,從可執行紅黑樹中移除
  • 放入等待隊列——由等待某些時間發生的進程組成的鏈表,內核用wake_ queue_ head_ t來表明等待隊列
  • 將進程加入到一個等待隊列中:

2. 喚醒

喚醒操做由函數wake_ up()進行:

  • 它會調用函數try_ to _ wake_ up()將進程設置爲TASK_ RUNNING狀態,調用enqueue_ task()將進程放入紅黑樹中
  • 存在虛假喚醒進程的狀態

6、搶佔和上下文切換

上下文切換,就是從一個可執行進程切換到另外一個可執行進程,由定義在kernel/schedule.c中的context_ switch()函數負責處理。完成了兩項工做:

  • 調用switch_mm(),負責把虛擬內存從上一個進程映射切換到新的進程中
  • 調用switch_to(),負責從上一個進程的處理器狀態切換到新進程的處理器狀態

6.1 用戶搶佔

  在內核返回用戶空間的時候,它知道本身是安全的,由於既然它能夠繼續去執行當前進程,那麼它固然能夠再去選擇一個新的進程去執行。因此,內核不管是在中斷處理程序仍是在系統調用後返回,都會檢查need_resched標誌,若是它被設置了,那麼,內核會選擇一個其餘(更合適的進程投入運行。從中斷處理程序或系統調用返回的返回路徑都是跟體系結構相關的,在entry.S(此文件不只包含內核入口部分的程序,內核退出部分的相關代碼也在其中)文件中經過彙編語言來實現。簡而言之,用戶搶佔在如下狀況時產生:

  • 從系統調返回用戶空間時。
  • 從中斷處理程序返回用戶空間時

6.2 內核搶佔

  與其餘大部分的Unⅸ變體和其餘大部分的操做系統不一樣,Linux完整地支持內核搶佔,在不支持內核搶佔的內核中,內核代碼能夠一直執行,到它完成爲止,也就是說,調度程序沒有辦法在一個內核級的任務正在執行的時候從新調度——內核中的各任務是以協做方式調度的不具有搶佔性。內核代碼一直要執行到完成(返回用戶空間)或明顯的阻塞爲止,在2.6版的內核中,內核引入了搶佔能力;如今,只要從新調度是安全的,內核就能夠在任什麼時候間搶佔正在執行的任務。

7、實時調度策略

  Linux的實時調度算法提供了―種軟實時工做方式,軟實時的含義是,內核調度進程,盡力使進程在它的限定時間到來前運行,但內核不保證總能知足這些進程的要求。相反,硬實時系統保證在必定條件下,能夠知足任何調度的要求。Linux對於實時任務的調度不作任何保證。雖然不能保證硬實時工做方式,但Linux的實時調度算法的性能仍是很不錯的。2.6版的內核能夠知足嚴格的時間要求。

8、與調度相關的系統調用

8.1 與調度策略和優先級相關的系統調用

  • sched_ setscheduler()和 sched_ getscheduler()分別用於設置和獲取進程的調度策略和實時優先級。與其餘的系統調用類似,它們的實現也是由許多參數檢查、初始化和清理構成的。其實最重要的工做在於讀取或改寫進程task_ struct的policy和rt_ priority的值
  • sched_ setscheduler()和 sched_ getscheduler()分別用於設置和獲取進程的實時優先級。這兩個系統調用獲取封裝在sched_ param特殊結構體的rt_ priority中。實時調度策略的的最大優先級:是MAX_ USERRT_PRIO減1。最小優先級等於1
  • 對於―個普通的進程,nice函數能夠將給定進程的靜態優先級增長一個給定的量。只有超級用戶才能在調用它時使用負值,從而提升進程的優先級。nice函數會調用內核的set_ user_ nice函數,這個函數會設置進程的的task_ struct的static_ prio值

8.2 與處理器綁定有關的系統調用

  Linux調度程序提供強制的處理器綁定機制。雖然它盡力經過一種軟的(或者說天然的)親和性試圖使進程儘可能在同一個處理器上運行,但它也容許用戶強制指定「這個進程不管如何都必須在這些處理器上運行」。這種強制的親和性保存在進程的一個位掩碼標誌中。該掩碼標誌的每一位對應一個系統可用的處理器,默認狀況下全部的位都被設置。

8.3 放棄處理器時間

  Linux經過sched_ yield()系統調用,提供了一種讓進程顯式地將處理器時間讓給其餘等待執行進程的機制,它是經過將進程從活動隊列中(由於進程正在執行,因此它確定位於此隊列當中)移到過時隊列中實現的,由此產生的效果不只搶佔了該進程並將其放入優先級隊列的最後面,還將其放入過時隊列中—這樣能確保在一段時間內它都不會再被執行了,因爲實時進程不會過時,因此屬於例外,它們只被移動到其優先級隊列的最後面(不會放到過時隊列中)。

  在Linux以的早期版本中,進程只會被放置到優先級隊列的末尾,放棄的時間每每不會太長,如今,應用程序甚至內核代碼在調用sched_ yield()前,應該仔細考慮是否真的但願放棄處理器時間。內核代碼爲了方便,能夠直接調用sched_ yield(),先要肯定給定進程確實處於可執行狀態,而後再調用sched_ yield(),用戶空間的應用程序直接使用sched_ yield()系統調用就能夠。

總結

  經過對本章進程調度的學習,我瞭解到進程調度程序是內核重要的組成部分,由於運行着的進程首先在使用計算機。可是知足進程調度的各類須要是較難實現的。例如公平調度中,越小的調度週期就會表現出越好的交互性,也更接近於「同時完成多任務」這一目標。然而系統必須承受更高的切換代價和更差的系統吞吐量,即魚與熊掌不可兼得。不過,Linux內核的新CFS調度程序儘可能知足了各個方面的需求,並以較完善的可伸縮性和新穎的方法提供了最佳的解決方案。

相關文章
相關標籤/搜索