Linux進程調度

進程狀態

進程調度就是讓進程從一種狀態切換到另外一種狀態。Linux中進程的主要狀態以下,html

狀態 縮寫 含義
0 TASK_RUNNING R 正在運行或可運行
1 TASK_INTERRUPTIBLE S 可中斷的休眠
2 TASK_UNINTERRUPTIBLE D 不可中斷的休眠
4 __TASK_STOPPED T 中止狀態,當進程接收到SIGSTOP等signal信息
8 __TASK_TRACED t 跟蹤狀態,進程被debugger程序暫停,好比使用ptrace()調試
16 EXIT_ZOMBIE Z 殭屍狀態,進程結束時調用do_exit()先進入殭屍狀體
32 EXIT_DEAD X 死亡狀態,父進程使用waitpid()或wait4()回收死亡的子進程後,狀態由EXIT_ZOMBIE轉換爲EXIT_DEAD

下圖粗略的展現了進程狀態的切換,能夠更加直觀的理解各類進程狀態。算法

process_scheduler.png

上圖中須要補充說明的是,進程正在」佔有CPU執行「時應該處於TASK_RUNNING狀態。EXIT_ZOMBIE狀態應該是很短暫的,父進程須要使用wait類系統調用回收子進程,回收後進程狀態就轉變爲EXIT_DEAD,若是父進程一直不回收,子進程就變爲殭屍進程。__TASK_TRACED狀態只有在使用debugger是纔會出現,例如使用gdb設置斷點,進程在斷點上暫停時就處於跟蹤狀態。緩存

調度器

系統中包含許多進程,內核有責任讓全部進程都獲得運行,並讓重要的進程得到更多的運行。這就須要一種機制管理全部的進程,在內核中安排進程執行的模塊被稱爲調度器(scheduler)。數據結構

  • 調度器從處於就緒狀態的進程中挑選一個,並分配CPU時間片讓它運行。
  • 當進程運行的時間片耗盡時,調度器會將其轉換爲就緒狀態,插入到準備執行的進程隊列。
  • 進程在放棄CPU進入睡眠或暫停狀態後,若是被喚醒,調度器也會將其插入到準備隊列。

因此,調度器將解決兩個核心的問題:分配合適的時間片和合理安排進程執行順序。最原始的調度策略是按照優先級排列好進程,等到一個進程運行完了再運行優先級較低的一個,但這種策略徹底沒法發揮多任務系統的優點。所以,隨着時間推移,操做系統的調度器也屢次進化。app

Linux 2.4內核推出了O(n)調度器,O(n)調度器把時間分紅大量的微小時間片(Epoch)。在每一個時間片開始的時候,調度器會檢查全部處在就緒狀態的進程。調度器計算每一個進程的優先級,而後選擇優先級最高的進程來執行。一旦被調度器切換到執行,進程能夠不被打擾地用盡這個時間片。若是進程沒有用盡時間片,那麼該時間片的剩餘時間會增長到下一個時間片中。O(n)調度器在每次使用時間片前都要檢查全部就緒進程的優先級。這個檢查時間和進程中進程數目n成正比,這也正是該調度器複雜度爲O(n)的緣由。當計算機中有大量進程在運行時,這個調度器的性能將會被大大下降。函數

爲了解決O(n)調度器的性能問題,O(1)調度器被髮明瞭出來,並從Linux 2.6內核開始使用。O(1)調度器的創新之處在於,它會把進程按照優先級排好,放入特定的數據結構中。在選擇下一個要執行的進程時,調度器不用遍歷進程,就能夠直接選擇優先級最高的進程。O(1)調度器會用兩個隊列來存放進程。一個隊列稱爲活躍隊列,用於存儲那些待分配時間片的進程。另外一個隊列稱爲過時隊列,用於存儲那些已經享用過期間片的進程。O(1)調度器把時間片從活躍隊列中調出一個進程。這個進程用盡時間片,就會轉移到過時隊列。當活躍隊列的全部進程都被執行事後,調度器就會把活躍隊列和過時隊列對調,用一樣的方式繼續執行這些進程。性能

Linux 2.6.23版本起,徹底公平調度器(CFS,Completely Fair Scheduler)取代了O(1)調度器。CFS調度器不對進程進行任何形式的估計和猜想。這一點和O(1)區分互動和非互動進程的作法徹底不一樣。CFS調度器增長了一個虛擬運行時(virtual runtime)的概念。每次一個進程在CPU中被執行了一段時間,就會增長它虛擬運行時的記錄。在每次選擇要執行的進程時,不是選擇優先級最高的進程,而是選擇虛擬運行時最少的進程。徹底公平調度器用一種叫紅黑樹的數據結構取代了O(1)調度器的140個隊列。紅黑樹能夠高效地找到虛擬運行最小的進程。CFS調度器會根據進程的優先級來計算一個時間片因子。一樣是增長250納秒的虛擬運行時,優先級低的進程實際得到的可能只有200納秒,而優先級高的進程實際得到可能有300納秒。這樣,優先級高的進程就得到了更多的計算資源。spa

進程優先級

進程優先級影響調度器的時間片分配和進程執行順序。Linux根據進程特性,在優先級上把進程分爲兩大類:實時進程和普通進程。操作系統

  • 實時進程(Real-Time Process):優先級高、須要儘快被執行的進程。它們必定不能被普通進程所阻擋,例如視頻播放、各類監測系統。
  • 普通進程(Normal Process):優先級低、更長執行時間的進程。例如文本編譯器、批處理一段文檔、圖形渲染。

實時進程也並非真正的實時,一樣須要通過進程調度,只是會先級於普通進程運行。進程的優先級是一個0到139的整數。數字越小,優先級越高。其中,優先級0到99留給實時進程,100到139留給普通進程。普通進程的默認優先級時120,能夠經過nice命令來修改進程的默認優先級。下面的命令表示將默認優先級改成(120-20)。線程

$nice -n -20 ./app

普通進程的默認優先級稱爲靜態優先級,進程運行時實際採用的是動態優先級。調度程序經過增長或減小進程靜態優先級的值來獎勵IO消耗型進程或懲罰cpu消耗型進程,調整後的優先級稱爲動態優先級。動態優先級的計算公式以下。

動態優先級 = max(100 , min(靜態優先級 – bonus + 5 ,139))

bonus是範圍0~10的值,值小於5表示下降動態優先級以示懲罰,值大於5表示增長動態優先級以示獎賞。

調度策略

Linux系統中,實時進程和普通進程採起了不一樣的調度策略。實時進程使用是實時調度策略,有三種:SCHED_FIFO,SCHED_RR,SCHED_DEADLINE。

  • SCHED_FIFO:先進先出的實時進程。當調度器把CPU分配給進程的時候,它把該進程描述符保留在運行隊列鏈表的當前位置。若是沒有其餘可運行的更高優先級實時進程,進程就繼續使用CPU,想用多久就用多久,即便還有其他具備相同優先級的實時進程處於可運行狀態。由RT調度器實現。
  • SCHED_RR:時間片輪轉實時進程。當調度程序把CPU分配給進程的時候,它把該進程的描述符放在運行鏈表的末尾。這種策略保證對全部具備相同時間優先級的SCHED_RR實時進程公平地分配CPU時間。由RT調度器實現。
  • SCHED_DEADLINE:新支持的實時進程調度策略,針對突發型計算,且對延遲和完成時間高度敏感的任務適用。基於Earliest Deadline First (EDF) 調度算法。由DL調度器實現。

實時進程使用0~99的優先級,就緒進程使用隊列的方式組織。相同優先級的實時進程都保存在一個列表中,再根據優先級排列起來。調度器老是先選取優先級最高的進程來運行。實時進程在下列狀況下可讓出CPU,

  • 進程被更高優先級的實時進程搶佔。
  • 進程進入休眠狀態或暫停狀態。
  • 進程調用sched_yield()自願放棄CPU。
  • 使用SCHED_RR策略時,進程的時間片耗光。

普通進程的調度策略有三種,由CFS調度器實現,分別是:SCHED_NORMAL,SCHED_BATCH,SCHED_IDLE。

  • SCHED_NORMAL:(也叫SCHED_OTHER)用於普通進程,經過CFS調度器實現。
  • SCHED_BATCH:SCHED_NORMAL策略的分化版本,採用分時策略,根據動態優先級分配CPU運算資源。
  • SCHED_IDLE:優先級最低,在系統空閒時才跑這類進程。

調度設置函數

Linux進程調度的介紹就簡單說這些,主要是概念普及,實用爲主。下面簡單列舉一下調度相關的函數。

  • nice():調整普通進程的優先級,nice值從-20到19。
  • getpriority()/setpriority():獲取/設置線程的優先級。
  • sched_getscheduler()/sched_setscheduler():獲取/設置線程的調度策略。
  • sched_getparam()/sched_getparam():獲取/設置線程的調度參數,參數由struct sched_param描述。
  • sched_get_priority_max()/sched_get_priority_min():獲取指定策略的最大/最小優先級。
  • sched_rr_get_interval():獲取SCHED_RR策略下實時進程的時間片長度。
  • sched_getattr()/sched_setattr():獲取/設置調度策略和屬性。這個接口是其餘調度函數的超集。
  • sched_getaffinity()/sched_setaffinity():獲取/設置線程的CPU親和性(affinity)。
  • sched_yield():當前線程主動讓出CPU,讓其餘線程執行。

Affinity表示CPU的親和性,就是讓進程在指定的CPU上儘可能長時間地運行而不被遷移到其餘處理器,也稱爲CPU關聯性。再簡單的點的描述就將指定的進程或線程綁定到相應的CPU上。在多核運行的機器上,每一個CPU自己本身會有緩存,緩存着進程使用的信息。若是進程被調度到其餘CPU上,cache命中率就會下降。當綁定CPU後,程序就會一直在指定的CPU上運行,不會被調度到其餘CPU上,能夠提升性能。

參考文章:

調度器簡介,以及Linux的調度策略
進程描述及其生命週期
Linux進程的管理與調度(十七)

相關文章
相關標籤/搜索