《Linux內核設計與實現》第四章讀書筆記

第四章——進程調度

1、多任務

1.多任務系統分爲兩類:非搶佔式多任務和搶佔式linux

  • Linux是搶佔式的多任務模式。由調度程序來決定何時中止一個進程的運行。進程在被搶佔以前的時間是預先設置好的,稱爲時間片。時間片就是分配給每一個可運行進程的處理器時間段。算法

  • 非搶佔多任務模式下,除非進程本身主動中止運行,不然會一直執行。進程主動掛起本身的動做叫作讓步。這種模式緩存

2、Linux的進程調度

Linux2.5內核版本採用O(1)調度程序的新調度程序,對大服務器的工做負載很理想,但其缺乏叫交互進程。服務器

Linux2.6內核版本初期採用RSDL,反轉樓梯最後期限調度算法。最終採用CFS,徹底調度公平算法。模塊化

3、策略

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

(1)進程分爲I/O消耗型和處理器消耗型函數

  • I/O消耗型:進程的大部分時間用來提交I/O請求或是等待I/O請求。這樣的進程常常處於可運行狀態,但一般是短短一下子。由於它在等待更多的I/O請求時最後總會阻塞。學習

  • 處理器消耗型:把時間大多用在執行代碼上。除非被搶佔,不然一直不停地運行。對於這類,調度策略儘可能下降他們的調度頻率,而延長其運行時間。測試

(2)調度策略一般在兩個矛盾的目標中間尋找平衡:進程響應迅速(響應時間短)和最大系統利用率(高吞吐量)。優化

(3)Unix系統更傾向於I/O消耗型程序,以提供更好的程序響應速度。ui

2. 進程優先級

在某些系統中,優先級高的進程使用的時間片也比較長。調度程序老是選擇時間片未用盡並且優先級最高的進程運行。用戶和系統均可以經過設置進程的優先級來影響系統的調度。

Linux採用了兩種不一樣的優先級範圍——nice值和實時優先級值。

(1) nice值,範圍是-20到19,數值越大優先級越低,默認值爲0。Linux中,nice值則表明時間片的比例。能夠經過ps-el命令查看系統中的進程列表,結果中標記NI的一列就是進程對應的nice值。

(2) 實時優先級值,默認0到99,數值越大優先級越高。任何實時進程的優先級都高於普通的進程。
查看進程列表以及對應的實時優先級:ps-eo state,uid,pid,ppid,rtprio,time,comm.

3.時間片

代表進程被搶佔前所能持續運行的時間。長時間片致使系統交互表現欠佳。

關於Linux的CFS調度器:

(1)沒有直接分配時間片到進程,而是將處理器的使用比劃分給進程。nice值高、優先權低的進程被賦予低權重,喪失一小部分處理器使用比;nice值低、優先權高的進程被賦予高權重,搶得更多的處理器使用比。

(2)其搶佔時機取決於新的可運行程序消耗了多少處理器使用比。若是消耗的使用比比當前進程小,馬上投入運行,搶佔當前進程;不然推遲運行。

4、Linux調度算法

1.調度器類(模塊化結構)

  • Linux調度器以模塊方式提供,容許不一樣類型的進程有針對性地選擇調度算法。

  • 每一個調度器都有一個優先級,基礎的調度器代碼定義在kernel/sched.c文件中,它會按照優先級順序遍歷調度類,擁有一個可執行進程的最高優先級的調度器類選擇下一個要執行的程序。

  • 徹底公平調度(CFS)——針對普通進程的調度類,在Linux中稱爲SCHED_NORMAL(POSIX中稱爲SCHED_OTHER),其算法實現定義在kernel/sched_fair.c文件中。

2.Unix系統中的進程調度

Unix系統中,優先級以nice值形式輸出給用戶空間。產生以下問題:

(1) 將nice值對應給時間片,就必須nice單位值對應處處理器的絕對時間,致使進程切換沒法最優化進行。給定高nice值
(低優先級)的進程每每是後臺進程,且可能是計算密集型;而普通優先級的進程可能是前臺用戶任務,不合理。

(2) nice(一般使用相對nice值)系統調用實在原值上增長或者減小,不是在絕對值上操做。好比nice值減一的效果更多取決於其初始值。

(3) 若是執行nice值到時間片的映射,須要分配一個絕對時間片(必須在內核的測試範圍內)。即時間片必須是定時器節拍的整數倍。

(4) 基於優先級的調度器爲了優化交互任務,喚醒相關進程,即便時間片已經用盡。有時會給某些特殊的睡眠/喚醒用例一個玩弄調度器的後門,使得給定進程打破公平原則,得到更多處理器時間,損害其餘進程利益。

總結:實質問題是分配絕對的時間片引起的固定的切換頻率給公平性形成了很大變數。

3.公平調度

  • 目標延遲:CFS爲完美多任務中的無限小調度週期的近似值設立的一個目標。越小的調度週期將帶來越好的交互性,同時也更接近完美的多任務。但必須承受更高的切換代價和更差的系統總吞吐能力。

  • 最小粒度:CFS引人每一個進程得到的時間片底線,這個底線稱爲最小粒度。

CFS中,任何進程所得到的處理器時間是由它本身和其餘全部可運行進程nice值的相對差值決定。任何nice值對應的絕對時間是處理器的使用比。

5、Linux調度的實現

CFS相關代碼四個組成部分:時間記帳、進程選擇、調度器入口和睡眠和喚醒。

1. 時間記帳

全部的調度器對進程運行時間作記帳。

調度器實體結構

CFS使用調度器實體結構(名爲se的成員變量,嵌入在進程描述符struct task_struct內)進行追蹤程序並記帳:

虛擬實時
  • vruntime變量存放進程的虛擬運行時間,紀錄一個程序到底運行了多長時間以及還應該再運行多長時間。
  • update_curr()函數計算當前進程執行時間,存放在變量delta_exec中;
  • __update_curr()函數根據當前進程可運行總數對運行時間進行加權計算;
  • 最終權重值和當前運行進程的vruntime相加。

注意:update_curr()是由系統定時器週期性調用的,不管進程出於何種狀態。

2. 進程選擇

CFS調度算法的核心:選擇具備最小vruntime的任務。

挑選下一個任務

CFS進程選擇算法——運行rbtree最左邊葉子節點所表明的進程。__pick_next_entity()函數實現,定義在kernel/sched_fair.c文件中。

向樹中加入進程
  • 進程變爲可執行狀態(被喚醒)或者經過fork()調用第一次建立進程時,CFS將進程加入rbtree。
  • enqueue_entity()函數實現了將進程加入rbtree 中,以及如何緩存最左葉子節點。
  • 該函數更新運行時間和其餘一些統計數據,而後調用_enqueue_entity()進行繁重的插入操做,把數據項真正插入到紅黑樹中。
  • 平衡二叉樹的基本規則是,若是鍵值小於當前節點的鍵值,則需轉向樹的左分 支:相反若是大於當前節點的鍵值,則轉向右分支。

    從樹中刪除進程

    刪除動做發生在進程堵塞或者終止時,實際工做由輔助函數__dequeue_entity()完成。

3. 調度器入口

(1)進程調度的主要入口點是schedule()函數,定義在kernel/sched.c文件中。

(2)schedule()會調用pick_next_task()——以優先級爲序,從高到低,依次檢查每個調度類,並從最高優先級的調度類中選擇最高優先級的進程:

注意:該函數的核心是for()循環;pick_next_task()實現會調用pick_next_entity()。

4. 睡眠和喚醒

  • 休眠的進程處於一個特殊的不可執行狀態。

  • 休眠有兩種進程狀態:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。

    TASK_INTERRUPTIBLE接收到信號會提早被喚醒,並響應該信號;而TASK_UNINTERRUPTIBLE的進程會忽略信號。
    等待隊列

    休眠經過等待隊列進行處理。

進程加入到一個等待隊列:

(1)調用宏DEFINE_WAIT()建立一個等待隊列的項。

(2)調用 add_ wait_ queue()把本身加入到隊列中。

(3)調用 prepareto wait()方法將進程的狀態變動爲TASK_ INTERRUPTIBLE 或TASK_ UNINTERRUPTIBLE。

(4)若是狀態被設置爲TASK_INTERRUPTIBLE,則信號喚醒進程。

(5)當進程被喚醒的時候,它會再次檢查條件是否爲真。

(6)當條件知足後,進程將本身設置爲 TASK_ RUNNING 並調用 finish_ wait()方法把本身移 出等待隊列。

喚醒

喚醒操做由函數wake_up()進行

  • 它會調用函數try_to wake_up(),將進程設置爲TASK_RUNNING狀態,調用enqueue_task()將進程放入紅黑樹中。若是被喚醒的進程優先級比當前正在執行的進程的優先級高,還要設置need resched標誌。
  • 有時存在虛假的喚醒。

6、搶佔和上下文切換

由context_switch()函數(定義在kernel/sched.c中)負責處理。每當一個新的進程被選出來準備運行的時候,schedule()就會調用該函數,完成兩項基本工做:

調用switch_mm(),負責把虛擬內存從上一個進程映射切換到新的進程中;
調用switch_to(),負責從上一個進程的處理器狀態切換到新進程的處理器狀態。以進程爲對象進行管理和保存。

內核提供need_ resched標誌代表是否須要從新執行一次調度。該標誌對內核是一個信息,標識其餘進程應當被運行了,要儘快調度程序。

1.用戶搶佔(會檢查need_ resched標誌)

發生時機:

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

    2.內核搶佔

(1)只要沒有鎖,內核就能夠進程搶佔;鎖是非搶佔區域的標誌。

(2)爲了支持搶佔,每一個進程的thread_info都加入了preempt_count計數器(初值爲0,每當使用鎖的時候就加1,釋放鎖的時候數值減1),當數值爲0的時候,內核就能夠搶佔。

(3)若是內核中進程被阻塞或顯示調用了schedule(),內核搶佔也會顯示發生。

總結——內核搶佔發生在:

  • 中斷處理程序正在執行且返回內核空間以前;
  • 內核代碼再一次具備可搶佔性的時候;
  • 內核中的任務顯式地調用schedule函數。

    7、實時調度策略

    linux 提供兩種實時調度策略SCHED_FIFO和SCHED_RR。

  • SCHED_FIFO實現了一種簡單的、先入先出的調度算法:它不使用時間片.處於可運行狀態的SCHED_FIFO 級的進程會比任何SCHED_NORMAL 級的進程都先獲得調度。
  • SCHED_RR級的進程在耗盡事先分配給它的時間後就不能再繼續執行了.即 SC阻止RR 是帶有時鬧片的SCHED_FIFO-這是一種實時輪流調度算掛。

這兩種算法實現的都是靜態優先級。Linux實時調度算法是軟實時工做方式——內核調度進程,儘可能使進程在它的限定時間到來前運行,但內核不能保證可以總能知足。

實時優先級範圍是0到MAX_RT_PRIO減1。默認狀況下,MAX_RT_PRIO爲100,nice值從-20到19直接對應的是100到139的實時優先級範圍。

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值。

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

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

內核提供的強制處理器綁定的方法很簡單:

  • 首先,當處理進行第一次建立時,它繼承了其父進程的相關掩碼。因爲父進程運行在指定處理器上, 子進程也運行在相應處理器上。
  • 其次,當處理器綁定關係改變時,內核會採用「移植錢程」把任務推到合曲的處理器上。
  • 最後,加載平衡器只把任務拉到容許的處理器上。

    3.放棄處理器時間

    Linux經過sched_yield()系統調用,提供了一種讓進程顯式地將處理器時間讓給其餘等待執行進程的機制,它是經過將進程從活動隊列中(由於進程正在執行,因此它確定位於此隊列當中)移到過時隊列中實現的。

內核代碼爲了方便,能夠直接調用sched_yield(),先要肯定給定進程確實處於可執行狀態,而後再調用sched_yield(),用戶空間的應用程序直接使用sched_yield()系統調用就能夠 。

小結

進程調度程序是內核重要的組成部分,由於運行着的進程首先在使用計算機(至少在咱們大多數人看來)。從Unix漸漸完善到Linux的CFS調度程序,逐步儘可能知足各方面的需求,本章也主要的介紹了進程調度所遵循的基本原理、具體實現、調度算法以及目前Linux內核所使用的接口。經過這章的學習,對之前的知識好比第五章系統調用有了更加深入的理解。

相關文章
相關標籤/搜索