下進程與線程的調度策略

***#linux 下進程與線程的調度策略#
進程被操作系統創建,並需要相當多的“開支”,進程包含如下程序資源和程序執行狀態信息:

  1. 進程ID,進程羣組ID,用戶ID,羣組ID
  2. 環境
  3. 工作目錄
  4. 程序指令
  5. 寄存器
  6. 文件描述符
  7. 信號動作
  8. 共享庫
  9. 進程間通信工具(例如消息隊列,管道,信號量,共享內存)
    線程是進程的一個執行流,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。一個進程由幾個線程組成(擁有很多相對獨立的執行流的用戶程序共享應用程序的大部分數據結構),線程與同屬一個進程的其他的線程共享進程所擁有的全部資源。
    線程使用和在進程內的生存,仍由操作系統來安排並且獨立的實體來運行,很大程度上是因爲它們爲可執行代碼的存在複製了剛剛好的基本資源。
    這個獨立的控制流之所以可以實現,是因爲線程維護着如下的東西:
  10. 棧指針
  11. 寄存器
  12. 調度屬性(例如規則和優先級)
  13. 等待序列和阻塞信號
  14. 線程擁有的數據
    所以,總的來說,Unix環境裏的線程有如下特點:
    它生存在進程中,並使用進程資源;
    擁有它自己獨立的控制流,前提是隻要它的父進程還存在,並且OS支持它;
    它僅僅複製可以使它自己調度的必要的資源;
    它可能會同其它與之同等獨立的線程分享進程資源;
    如果父進程死掉那麼它也會死掉——或者類似的事情;
    它是輕量級的,因爲大部分的開支已經在它的進程創建時完成了。
    因爲在同一進程內的線程分享資源,所以:
    一個線程對共享的系統資源做出的改變(例如關閉一個文件)會被所有的其它線程看到;
    指向同一地址的兩個指針的數據是相同的;
    對同一塊內存進行讀寫操作是可行的,但需要程序員作明確的同步處理操作。
    ##進程的調度策略##
  • 關於進程的優先級
    進程的優先級有2種度量方法,一種是nice值,一種是實時優先級。
    nice值的範圍是-20~+19,值越大優先級越低,也就是說nice值爲-20的進程優先級最大。
    實時優先級的範圍是0~99,與nice值的定義相反,實時優先級是值越大優先級越高。
    實時進程都是一些對響應時間要求比較高的進程,因此係統中有實時優先級高的進程處於運行隊列的話,它們會搶佔一般的進程的運行時間。

進程的2種優先級會讓人不好理解,到底哪個優先級更優先?一個進程同時有2種優先級怎麼辦?
其實linux的內核早就有了解決辦法。
對於第一個問題,到底哪個優先級更優先?
答案是實時優先級高於nice值,在內核中,實時優先級的範圍是 0~MAX_RT_PRIO-1 MAX_RT_PRIO的定義參見 include/linux/sched.h
1611 #define MAX_USER_RT_PRIO 100
1612 #define MAX_RT_PRIO MAX_USER_RT_PRIO
nice值在內核中的範圍是 MAX_RT_PRIO~MAX_RT_PRIO+40 即 MAX_RT_PRIO~MAX_PRIO
1614 #define MAX_PRIO (MAX_RT_PRIO + 40)

第二個問題,一個進程同時有2種優先級怎麼辦?
答案很簡單,就是一個進程不可能有2個優先級。一個進程有了實時優先級就沒有Nice值,有了Nice值就沒有實時優先級。
我們可以通過以下命令查看進程的實時優先級和Nice值:(其中RTPRIO是實時優先級,NI是Nice值)

$ ps -eo state,uid,pid,ppid,rtprio,ni,time,comm
S   UID   PID  PPID RTPRIO  NI     TIME COMMAND
S     0     1     0      -   0 00:00:00 systemd
S     0     2     0      -   0 00:00:00 kthreadd
S     0     3     2      -   0 00:00:00 ksoftirqd/0
S     0     6     2     99   - 00:00:00 migration/0
S     0     7     2     99   - 00:00:00 watchdog/0
S     0     8     2     99   - 00:00:00 migration/1
S     0    10     2      -   0 00:00:00 ksoftirqd/1
S     0    12     2     99   - 00:00:00 watchdog/1
S     0    13     2     99   - 00:00:00 migration/2
S     0    15     2      -   0 00:00:00 ksoftirqd/2
S     0    16     2     99   - 00:00:00 watchdog/2
S     0    17     2     99   - 00:00:00 migration/3
S     0    19     2      -   0 00:00:00 ksoftirqd/3
S     0    20     2     99   - 00:00:00 watchdog/3
S     0    21     2      - -20 00:00:00 cpuset
S     0    22     2      - -20 00:00:00 khelper
  • 關於時間片
    有了優先級,可以決定誰先運行了。但是對於調度程序來說,並不是運行一次就結束了,還必須知道間隔多久進行下次調度。
    於是就有了時間片的概念。時間片是一個數值,表示一個進程被搶佔前能持續運行的時間。
    也可以認爲是進程在下次調度發生前運行的時間(除非進程主動放棄CPU,或者有實時進程來搶佔CPU)。
    時間片的大小設置並不簡單,設大了,系統響應變慢(調度週期長);設小了,進程頻繁切換帶來的處理器消耗。默認的時間片一般是10ms

  • 調度實現原理(基於優先級和時間片)
    下面舉個直觀的例子來說明:
    假設系統中只有3個進程ProcessA(NI=+10),ProcessB(NI=0),ProcessC(NI=-10),NI表示進程的nice值,時間片=10ms

  1. 調度前,把進程優先級按一定的權重映射成時間片(這裏假設優先級高一級相當於多5msCPU時間)。
    假設ProcessA分配了一個時間片10ms,那麼ProcessB的優先級比ProcessA高10(nice值越小優先級越高),ProcessB應該分配105+10=60ms,以此類推,ProcessC分配205+10=110ms
  2. 開始調度時,優先調度分配CPU時間多的進程。由於ProcessA(10ms),ProcessB(60ms),ProcessC(110ms)。顯然先調度ProcessC
  3. 10ms(一個時間片)後,再次調度時,ProcessA(10ms),ProcessB(60ms),ProcessC(100ms)。ProcessC剛運行了10ms,所以變成100ms。此時仍然先調度ProcessC
  4. 再調度4次後(4個時間片),ProcessA(10ms),ProcessB(60ms),ProcessC(60ms)。此時ProcessB和ProcessC的CPU時間一樣,這時得看ProcessB和ProcessC誰在CPU運行隊列的前面,假設ProcessB在前面,則調度ProcessB
  5. 10ms(一個時間片)後,ProcessA(10ms),ProcessB(50ms),ProcessC(60ms)。再次調度ProcessC
  6. ProcessB和ProcessC交替運行,直至ProcessA(10ms),ProcessB(10ms),ProcessC(10ms)。
    這時得看ProcessA,ProcessB,ProcessC誰在CPU運行隊列的前面就先調度誰。這裏假設調度ProcessA
  7. 10ms(一個時間片)後,ProcessA(時間片用完後退出),ProcessB(10ms),ProcessC(10ms)。
  8. 再過2個時間片,ProcessB和ProcessC也運行完退出。
    這個例子很簡單,主要是爲了說明調度的原理,實際的調度算法雖然不會這麼簡單,但是基本的實現原理也是類似的:
    1)確定每個進程能佔用多少CPU時間(這裏確定CPU時間的算法有很多,根據不同的需求會不一樣)
    2)佔用CPU時間多的先運行
    3)運行完後,扣除運行進程的CPU時間,再回到 1)
  • Linux上調度實現的方法
    Linux上的調度算法是不斷髮展的,在2.6.23內核以後,採用了“完全公平調度算法”,簡稱CFS。
    CFS算法在分配每個進程的CPU時間時,不是分配給它們一個絕對的CPU時間,而是根據進程的優先級分配給它們一個佔用CPU時間的百分比。
    比如ProcessA(NI=1),ProcessB(NI=3),ProcessC(NI=6),在CFS算法中,分別佔用CPU的百分比爲:ProcessA(10%),ProcessB(30%),ProcessC(60%)
    因爲總共是100%,ProcessB的優先級是ProcessA的3倍,ProcessC的優先級是ProcessA的6倍。

Linux上的CFS算法主要有以下步驟:(還是以ProcessA(10%),ProcessB(30%),ProcessC(60%)爲例)
1)計算每個進程的vruntime(注1),通過update_curr()函數更新進程的vruntime。
2)選擇具有最小vruntime的進程投入運行。(注2)
3)進程運行完後,更新進程的vruntime,轉入步驟2) (注3)

注1. 這裏的vruntime是進程虛擬運行的時間的總和。vruntime定義在:kernel/sched_fair.c 文件的 struct sched_entity 中。

注2. 這裏有點不好理解,根據vruntime來選擇要運行的進程,似乎和每個進程所佔的CPU時間百分比沒有關係了。
1)比如先運行ProcessC,(vr是vruntime的縮寫),則10ms後:ProcessA(vr=0),ProcessB(vr=0),ProcessC(vr=10)
2)那麼下次調度只能運行ProcessA或者ProcessB。(因爲會選擇具有最小vruntime的進程)
長時間來看的話,ProcessA、ProcessB、ProcessC是公平的交替運行的,和優先級沒有關係。
而實際上vruntime並不是實際的運行時間,它是實際運行時間進行加權運算後的結果。
比如上面3個進程中ProcessA(10%)只分配了CPU總的處理時間的10%,那麼ProcessA運行10ms的話,它的vruntime會增加100ms。
以此類推,ProcessB運行10ms的話,它的vruntime會增加(100/3)ms,ProcessC運行10ms的話,它的vruntime會增加(100/6)ms。
實際的運行時,由於ProcessC的vruntime增加的最慢,所以它會獲得最多的CPU處理時間。
上面的加權算法是我自己爲了理解方便簡化的,Linux對vruntime的加權方法還得去看源碼-

注3.Linux爲了能快速的找到具有最小vruntime,將所有的進程的存儲在一個紅黑樹中。這樣樹的最左邊的葉子節點就是具有最小vruntime的進程,新的進程加入或有舊的進程退出時都會更新這棵樹。

其實Linux上的調度器是以模塊方式提供的,每個調度器有不同的優先級,所以可以同時存在多種調度算法。
每個進程可以選擇自己的調度器,Linux調度時,首先按調度器的優先級選擇一個調度器,再選擇這個調度器下的進程。

  1. 調度相關的系統調用
    調度相關的系統調用主要有2類:
  1. 與調度策略和進程優先級相關 (就是上面的提到的各種參數,優先級,時間片等等) - 下表中的前8個
  2. 與處理器相關 - 下表中的最後3個
    系統調用
    描述
    nice()
    設置進程的nice值
    sched_setscheduler()
    設置進程的調度策略,即設置進程採取何種調度算法
    sched_getscheduler()
    獲取進程的調度算法
    sched_setparam()
    設置進程的實時優先級
    sched_getparam()
    獲取進程的實時優先級
    sched_get_priority_max()
    獲取實時優先級的最大值,由於用戶權限的問題,非root用戶並不能設置實時優先級爲99
    sched_get_priority_min()
    獲取實時優先級的最小值,理由與上面類似
    sched_rr_get_interval()
    獲取進程的時間片
    sched_setaffinity()
    設置進程的處理親和力,其實就是保存在task_struct中的cpu_allowed這個掩碼標誌。該掩碼的每一位對應一個系統中可用的處理器,默認所有位都被設置,即該進程可以再系統中所有處理器上執行。
    用戶可以通過此函數設置不同的掩碼,使得進程只能在系統中某一個或某幾個處理器上運行。
    sched_getaffinity()
    獲取進程的處理親和力
    sched_yield()
    暫時讓出處理器
    ##線程的調度策略##
  1. SCHED_OTHER 分時調度策略,(默認的)
  2. SCHED_FIFO實時調度策略,先到先服務
  3. SCHED_RR實時調度策略,時間片輪轉
    實時進程將得到優先調用,實時進程根據實時優先級決定調度權值,分時進程則通過nice和counter值決定權值,nice越小,counter越大,被調度的概率越大,也就是曾經使用了cpu最少的進程將會得到優先調度。

SHCED_RR和SCHED_FIFO的不同:
當採用SHCED_RR策略的進程的時間片用完,系統將重新分配時間片,並置於就緒隊列尾。放在隊列尾保證了所有具有相同優先級的RR任務的調度公平。
SCHED_FIFO一旦佔用cpu則一直運行。一直運行直到有 更高優先級任務到達或自己放棄 。
如果有相同優先級的實時進程(根據優先級計算的調度權值是一樣的)已經準備好,FIFO時必須等待該進程主動放棄後纔可以運行這個優先級相同的任務。而RR可以讓每個任務都執行一段時間。
相同點:
RR和FIFO都只用於實時任務。
創建時優先級大於0(1-99)。
按照可搶佔優先級調度算法進行。
就緒態的實時任務立即搶佔非實時任務。
當所有任務都採用分時調度策略時(SCHED_OTHER):

  1. 創建任務指定採用分時調度策略,並指定優先級nice值(-20~19)。
  2. 將根據每個任務的nice值確定在cpu上的執行時間( counter )。
  3. 如果沒有等待資源,則將該任務加入到就緒隊列中。
  4. 調度程序遍歷就緒隊列中的任務,通過對每個任務動態優先級的計算(counter+20-nice)結果,選擇計算結果最大的一個去運行,當這個時間片用完後(counter減至0)或者主動放棄cpu時,該任務將被放在就緒隊列末尾(時間片用完)或等待隊列(因等待資源而放棄cpu)中。
  5. 此時調度程序重複上面計算過程,轉到第4步。
  6. 重點內容當調度程序發現所有就緒任務計算所得的權值都爲不大於0時,重複第2步。

當所有任務都採用FIFO調度策略時(SCHED_FIFO):
1.創建進程時指定採用FIFO,並設置實時優先級rt_priority(1-99)。
2.如果沒有等待資源,則將該任務加入到就緒隊列中。
3.調度程序遍歷就緒隊列,根據實時優先級計算調度權值,選擇權值最高的任務使用cpu, 該FIFO任務將一直佔有cpu直到有優先級更高的任務就緒(即使優先級相同也不行)或者主動放棄(等待資源)。
4.調度程序發現有優先級更高的任務到達(高優先級任務可能被中斷或定時器任務喚醒,再或被當前運行的任務喚醒,等等),則調度程序立即在當前任務堆棧中保存當前cpu寄存器的所有數據,重新從高優先級任務的堆棧中加載寄存器數據到cpu,此時高優先級的任務開始運行。重複第3步。
5.如果當前任務因等待資源而主動放棄cpu使用權,則該任務將從就緒隊列中刪除,加入等待隊列,此時重複第3步。
當所有任務都採用RR調度策略(SCHED_RR)時:
1.創建任務時指定調度參數爲RR, 並設置任務的實時優先級和nice值(nice值將會轉換爲該任務的時間片的長度)。
2.如果沒有等待資源,則將該任務加入到就緒隊列中。
3.調度程序遍歷就緒隊列,根據實時優先級計算調度權值,選擇權值最高的任務使用cpu。
4. 如果就緒隊列中的RR任務時間片爲0,則會根據nice值設置該任務的時間片,同時將該任務放入就緒隊列的末尾 。重複步驟3。
5.當前任務由於等待資源而主動退出cpu,則其加入等待隊列中。重複步驟3。***

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