20135327郭皓--讀書筆記五

第四章 進程調度

  • 調度程序負責決定將哪一個進程投入運行,什麼時候運行以及運行多長時鬧。進程調度程序(經常 簡稱調度程序)可看作在可運行態進程之間分配有限的處理器時間資源的內核子系統。
  • 調度程序沒有太複雜的原理。最大限度地利用處理器時間的原則是,只要有能夠執行的進 程,那麼就總會有進程正在執行。

4.1 多任務

  • 多任務操做系統就是能同時併發地交互執行多個進程的操做系統。 
  • 多任務系統能夠劃分爲兩類:非搶佔式多任務(cooperative multitasking)和搶佔式多任務 (preemptive multitasking)。
  • 這個強制的掛起動做就叫作搶佔(preemption).進程在被搶佔以前可以運行 的時間是預先設置好的,並且有一個專門的名字,叫進程的時間片(timeslice )。時間片實際上 就是分配給每一個可運行進程的處理器時間段。
  • 在非搶佔式多任務模式下,除非進程本身主動中止運行,不然它會一直執行。進程主動掛起本身的操做稱爲讓步(yielding)。 

4.2 Linux 的進程調度

  • 反轉樓梯最後期限調度算法——RSDL
  • 徹底公平調度算撞——CFS

4.3 策略

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

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

進程能夠被分爲I/O消耗型和處理器消耗型。前者指進程的大部分時間用來提交I/O請求或是等待I/O請求。相反,處理器消耗型進程把時間大多用在執行代碼上。除非被搶佔,不然它們一般都一直 不停地運行,由於它們沒有太多的I/O需求。linux

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

4.3.2 進程優先級

  • 調度算站中最基本的一類就是基於優先級的調度。
  • 調度程序老是選擇時間片採用盡並且優先級最高的進程運行。用戶和系統均可以經過設置進程的優先級來影響系統的調度。
  • Linux 採用了兩種不一樣的優先級範圍。第一種是用nice值,色的範圍是從-20到+19,默 認值爲0 :越大的 nice 值意味着更低的優先級。
  • 第二種範圍是實時優先級,其值是可配置的,默認狀況下它的變化範圍是從0到99(包括0和99)。與nice值意義相反,越高的實時優先級數值意味着進程優先級越高。

4.3.3 時間片

  • 時間片是一個數值,它代表進程在被搶佔前所能持續運行的時間。
  • 從上面的爭論中能夠看出,任何長時間片都將致使系統交互表現欠佳。
  • 在Linux中使用新的CFS調度器,其搶佔時機取決於新的可運行程序消耗了多少處理器使用比。若是消耗的使用比比當前進程小,刷新進程馬上拉入運行,搶佔當前進程。不然,將推遲其運行。安全

4.4 Linux 調度算法

4.4.1 調度器類

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

     

4.4.2 Unix系統中的進程調度

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

4.4.3 公平調度

CFS的出發點基於一個簡單的理念:進程調度的效果應如同系統具有一個理想中的完美多任務處理器。併發

任何進程所得到的處理器時間是由它本身和其餘全部可運行進程nice值的相對差值決定的。 nice值對時間片的做用再也不是算數加權,而是幾何加權。任何回回值對應的絕對時間再也不是一個絕對值,而是處理器的使用比。 CFS稱爲公平調度器是由於它確保給每一個進程公平 的處理器使用比。正如咱們知道的,CFS不是完美的公平,它只是近乎完美的多任務。可是它確實在多進程環境下,下降了調度延遲帶來的不公平性。

4.5 Linux調度的實現

在討論了採用 CFS調度算法的動機和其內部邏輯後,咱們如今能夠開始具體探索CFS是如何得以實現的。其相關代碼位於文件 kernel/sched_fair.c 中.咱們將特別關注其四個組成部分:模塊化

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

4.5.1 時間記帳

全部的調度器都必須對進程運行時間作記帳。函數

  1. 調度器實體結構 
CFS再也不有時間片的概念,可是它也必須維護每一個進程運行的時間記帳,由於它須要確保每一個進程只在公平分配給它的處理器時間內運行。CFS使用調度器實體結構(定義在文件<linux/sched.h>的 struct_sched _entity中)來追蹤進程運行記帳

  2. 虛擬實時 oop

vruntime變量存放進程的虛擬運行時間,該運行時間(花在運行上的時間和)的計算是通過了全部可運行進程總數的標準化(或者說是被加權的)。虛擬時間是以由於單位的,因此vruntime 和定時器節拍再也不相關。
所以CFS使用vruntime變量來 記錄一個程序到底運行了多長時間以及它還應該再運行多久。 

4.5.2 進程選擇

CFS使用紅黑樹來組織可運行進程隊列,並利用其迅速找到最小vruntime值的進程。在Linux中,紅黑樹稱爲rbtree,它是一個自平衡二叉搜索樹。spa

1. 挑選下一個任務 操作系統

2. 向樹中加入進程 設計

3. 從樹中則除進程 

 

4.5.3 調度器入口

進程調度的主要入口點是函數 schedule(),它定義在文件kemel/sched.c中。它正是內核其餘部分用子調用進程調度器的入口:選擇哪一個進程能夠運行,什麼時候將其投入運行。 Schedule()一般都須要和一個具體的調度類相關聯,也就是說,它會找到一個最高優先級的調度類一一後者須要有本身的可運行隊列,而後問後者誰纔是下一個該運行的進程。

4.5.4 睡眠和喚醒

1. 等待隊列

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

2)調用 add_wait_ queue()把本身加入到隊列中。該隊列會在進程等待的條件知足時喚醒它。 固然咱們必須在其餘地方撰寫相關代碼,在事件發生時,對等待隊列執行 wake_up()操做。

3)調用 prepare_to_ wait()方法將進程的狀態變動爲TASK_INTERRUPTIBLE 或TASK_ UNINTERRUPTIBLE。並且該函數若是有必要的話會將進程加回到等待隊列,這是在接下來的循環遍歷中所須要的。

4)若是狀態被設置爲 TASK_INTERRUPTIBLE,則信號喚醒進程。這就是所謂的僞喚醒(喚醒不是由於事件的發生),所以檢查並處理倍號。

5)當進程被喚醒的時候,它會再次檢查條件是否爲真。若是是,它就退出循環:若是不是,它再次調用 schedule()並一直重複這步操做。

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

2. 喚醒 

喚醒操做經過函數wake_upO 進行,它會喚醒指定的等待隊列上的全部進程。

 

4.6 搶佔和上下文切換

上下文切換,也就是從一個可執行進程切換到另外一個可執行進程,由定義在 kernel/ sched.c 中的 context_switch()函數負責處理。每當一個新的進程被選出來準備投入運行的時候, schedule() 就會調用該函數。它完成了兩項基本的工做:

  • 調用聲明在 <asm/mmu_ context.h>中的 switch_mm(), 該函數負責把虛擬內存從上一個進程映射切換到新進程中。 
  • 調用聲明在 <asm/system.h> 中的 switch_to(),該函數負責從上一個進程的處理器狀態切換 到新進程的處理器狀態。這包括保存、恢復檢信息和寄存器信息,還有其餘任何與體系結 ’ 構相關的狀態信息,都必須以每一個進程爲對象進行管理和保存。

內核必須知道在何時調用 schedule()。若是僅靠用戶程序代碼顯式地調用 schedule(),它 們可能就會永遠地執行下去。相反,內核提供了一個 need_resched標誌來代表是否須要從新執行 一次調度

4.6.1 用戶搶佔

內核即將返回用戶空間的時候,若是 need_resched標誌被設置,會致使 schedule()被調用, 此時就會發生用戶搶佔。

用戶搶佔在如下狀況時產生:

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

4.6.2 內核搶佔

爲了支持內核搶佔所傲的第一處變更,就是爲每一個進程的thread_info引入preempt_count 計數器。該計數器初始值爲0,每當使用鎖的時候數值加1,釋放鎖的時候數值減1。當數值爲0的時候,內核就可執行搶佔。從中斷返回內核空間的時候,內核會檢查 need_resched 和preempt_ count 的值。若是 need_resched被設置,而且 preempt_count 爲0的話,這說明有一個更爲重要的任務須要執行而且能夠安全地搶佔,此時,調度程序就會被調用。若是 preempt_ count不爲0, 說明當前任務持有鎖,因此搶佔是不安全的。這時,內核就會像一般那樣直接從中斷返回當前執行進程.若是當前進程持有的全部的鎖都被釋放了,preempt_count就會從新爲0。此時,釋放鎖的代碼會檢查 need_resched是否被設置。若是是的話,就會調用調度程序。

內核搶佔會發生在:

  • 中斷處理程序正在執行,且返回內核空間以前。
  • 內核代碼再一次具備可搶佔性的時候。 
  • 若是內核中的任務顯式地調用 schedule()。
  • 若是內核中的任務阻塞(這一樣也會導敖調用 schedule())。

4.7 實時調度策略

  • Linux 提供了兩種實時調度策略:SCHED_FIFO 和 SCHED_RR。而普通的、非實時的調度策略是 SCHED_NORMAL。
  • SCHED_FIFO 實現了一種簡單的、先入先出的調度算法:它不使用時間片.處於可運行狀態的SCHED_FIFO級的進程會比任何SCHED_NORMAL級的進程都先獲得調度。
  • SCHED_RR與 SCHED_FIFO 大致相同,只是SCHED_RR級的進程在耗盡事先分配給它的時間後就不能再繼續執行了。
  • 這兩種實時算法實現的都是靜態優先級。內核不爲實時進程計算動態優先級.這能保證給定優先級別的實時進程總能搶佔優先級比它低的進程。 
  • Linux的實時調度算掛提供了一種軟實時工做方式。軟實時的含義是,內核調度進程,盡力使進程在它的限定時間到來前運行,但內核不保證總能知足這些進程的要求。

4.8 與調度相關的系統調用

4.8.1 與調度策略和優先組相關的系統調用

sched _ setscheduler()和 sched__getscheduler()分別用於設置和獲取進程的調度策略和實時優先級。與其餘的系統調用類似,它們的實現也是由許多參數檢查、初始化和清理構成的。其實最重要的工做在於讀取或改寫進程 tast_struct的policy和rt_priority的值。 

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

Linux調度程序提供強制的處理器綁定(processor affinity)機制。也就是說,雖然它盡力經過一種軟的(或者說天然的)親和性試圖使進程儘可能在同一個處理器上運行,但它也容許用戶 強制指定「這個進程不管如何都必須在這些處理器上運行」。

4.8.3 放棄處理器時間

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

4.9 小結

  進程調度程序是內核重要的組成部分,由於運行着的進程首先在使用計算機。 然而,知足進程調度的各類須要毫不是垂手可得的:很難找到「一刀切」的算法,既適合衆多的可運行進程,又具備可伸縮性,還能在調度週期和吞吐量之間求得平衡,同時還知足各類負載的需求。不過,Linux 內核的新CFS調度程序儘可能知足了各個方面的需求,並以較完善的可伸縮性和新穎的方挫提供了最佳的解決方案。

相關文章
相關標籤/搜索