Linux的進程管理,進程調度

轉載於:https://blog.csdn.net/qq_38410730/article/details/81253686

長期以來,Linux一直把具有較好的平均系統響應時間和較高的吞吐量作爲調度算法的主要目標。但近年來,鑑於嵌入式系統的要求,Linux2.6在支持系統的實時性方面也做出了重大的改進。

 

Linux進程的時間片與權重參數


在處理器資源有限的系統中,所有進程都以輪流佔用處理器的方式交叉運行。爲使每個進程都有運行的機會,調度器爲每個進程分配了一個佔用處理器的時間額度,這個額度叫做進程的“時間片”,其初值就存放在進程控制塊的counter域中。進程每佔用處理器一次,系統就將這次所佔用時間從counter中扣除,因爲counter反映了進程時間片的剩餘情況,所以叫做剩餘時間片。

Linux調度的主要思想爲:調度器大致以所有進程時間片的總和爲一個調度週期;在每個調度週期內可以發生若干次調度,每次調度時,所有進程都以counter爲資本競爭處理器控制權,counter值大者勝出,優先運行;凡是已耗盡時間片(即counter=0)的,則立即退出本週期的競爭;當所有未被阻塞進程的時間片都耗盡,那就不等了。然後,由調度器重新爲進程分配時間片,開始下一個調度週期。

Linux基於時間片調度的基本思想如下圖所示:

上面的調度方式主要體現的是一種公平性:如果一個進程的剩餘時間片多,那麼它在前期獲得運行的時間片就少,爲了公平性,就應該適當的賦予它更高的優先級。但是這僅僅是一個總體的原則,爲了應付實際問題中的特殊狀況,在上述平等原則的基礎上,爲了給特殊進程一些特殊照顧,在爲進程分配時間片時,Linux允許用戶通過一個系統調用nice()來設置參數nice影響進程的優先權。所以,系統真正用來確定進程的優先權時,使用的依據爲權重參數weight,weight大的進程優先運行。

weight與nice、counter之間的關係大體如下:

weight 正比於 [counter+(20-nice)]

關於三者之間的這個關係將會在後面的內容中詳細的講到。

因爲nice是用戶在創建進程時確定的,在進程的運行過程中一般不會改變,所以叫做靜態優先級;counter則隨着進程時間片的小號在不斷減小,是變化的,所以叫做動態優先級。

 

調度策略


目前,標準Linux系統支持非實時(普通)和實時兩種進程。與此相對應的,Linux有兩種進程調度策略:普通進程調度和實時進程調度。因此,在每個進程的進程控制塊中都有一個域policy,用來指明該進程爲何種進程,應該使用何種調度策略。

Linux調度的總體思想是:實時進程優先於普通進程,實時進程以進程的緊急程度爲優先順序,併爲實時進程賦予固定的優先級;普通進程則以保證所有進程能平均佔用處理器時間爲原則。所以其具體做法就是:

對於實時進程來說,總的思想是爲實時進程賦予遠大於普通進程的固定權重參數weight,以確保實時進程的優先級。在此基礎上,還分爲兩種做法:一種與時間片無關,另一種與時間片有關;
對於普通進程來說,原則上以相等的weight作爲所有進程的初始權重值,即nice=0,然後在每次進行進程調度時,根據剩餘時間片對weight動態調整。
 

普通進程調度策略


如果進程控制塊的policy的值爲SCHED_OTHER,則該進程爲普通進程,適用於普通進程調度策略。

時間片的分配
當一個普通進程被創建時,系統會先爲它分配一個默認的時間片(nice=0)。在Linux2.4內核中,進程的默認時間片時按照下面的算法來計算的:

#if HZ < 200

#define TICK_SCALE(x)        ((x)>>2)

#elif HZ < 400

#define TICK_SCALE(x)        ((x)>>1)

#elif HZ < 800

#define TICK_SCALE(x)        (x)

#elif HZ < 1600

#define TICK_SCALE(x)        ((x)<<1)

#dele

#define TICK_SCALE(x)        ((x)<<2)

#endif

#define NICE_TO_TICKS(nice) (TICK_SCALE(20-(nice))+1)

......

在每個調度週期之前,調度器爲每個普通進程進程分配時間片的算法爲:

p->counter = (p->counter>>1) + NICE_TO_TICKS(p->nice);
其中,p爲進程控制塊指針。

這裏爲什麼需要加上p->counter>>1呢?這會在本文後面的內容中講到。

例如,用戶定義HZ爲100,而counter初值和nice的默認值爲0,於是可以計算出counter的值爲6 ticks(((20-0)>>2)+1),也就是說,進程時間片的默認值大概爲60ms(100Hz,10ms)。

weight的微調函數goodness()
儘管在默認情況下,系統爲所有的進程都分配了相等的時間片,但在實際運行時,常常因各種原因使得進程的運行總是有先有後,於是經過一段時間運行後,在各進程之間就會產生事實上的不公平的現象,也就是各個進程在實際使用其時間片的方面形成了差異。剩餘時間計數器counter的值就反映了這個差異的程度:該值大的,意味着這個進程佔用處理器的時間少(喫虧了);該值小的,意味着這個進程佔用處理器的時間多(佔便宜了)。

爲了儘可能地縮小上述的這個差異,Linux的調度器在調度週期中每次調度時,都遍歷就緒列表中的所有進程,並按照各個進程的counter當前值調用函數goodness()對所有進程的weight進行調整,想辦法讓counter值大的進程的weight大一些,而counter值小的進程的weight小一些。

函數goodness()的主要代碼如下:

/*
返回值:
        -1000:從不選擇這個
        0:過期進程,重新計算計數值(但它仍舊可能被選中)
        正值:goodness值(越大越好)
        +1000:實時進程,選擇這個
*/
 
static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)
{
     int weight;
     weight = -1;
 
     if (p->policy & SCHED_YIELD) //p->policy表示進程的調度策略,SCHED_YIELD是一種策略,不參與處理器的競爭!
#define SCHED_YIELD          0x10
          goto out;
 
     //非實時進程
     if (p->policy == SCHED_OTHER) {
          weight = p->counter;                //weight的主要成分是counter
          if (!weight)                        //如果時間片用盡
               goto out;
              
#ifdef CONFIG_SMP
          if (p->processor == this_cpu)
               weight += PROC_CHANGE_PENALTY;
#endif
 
          if (p->mm == this_mm || !p->mm)
               weight += 1;
          weight += 20 - p->nice;            //nice越小,權值越大
          goto out;
     }
 
     //實時進程
     weight = 1000 + p->rt_priority;
out:
     return weight;
}
這個函數中就能看出:weight 正比於 [counter+(20-nice)]。

普通進程調度算法中的一些細節
當普通就緒隊列中所有非等待進程counter值都減爲0時,就在schedule()中對每個進程利用下面的代碼:

p->counter = (p->counter>>1) + NICE_TO_TICKS(p->nice);
對所有的進程時間片counter重新賦值。

在重新賦值時,對於耗盡時間片的進程,由於參與計算的counter等於0,因此這個算法就是恢復counter的初值;而對於處於等待狀態的進程,由於參與計算的counter大於0,因此這個算法實際上就是把counter的剩餘值折半再加上初值。也就是說,因等待而損失了時間片的進程在counter重新被賦值時,系統會適當地給它一些“照顧”,以使其在下一個調度週期中獲得更多的時間片,並擁有更高的權重weight。

一個進程的等待次數比較多,通常意味着它的I/O操作比較密集。通過對時間片進行疊加的做法給等待進程賦予更高的優先級,體現了Linux對I/O操作密集型進程的一種體貼。

可見,SCHED_OTHER策略本質上是一種比例共享的調度策略,它的這種設計方法能夠保證進程調度時的公平性:一個低優先級的進程在每一個週期中也會得到自己應得的那些運行時間;同時,它提供了nice使用戶可以對進程優先級進行干預。

 

實時進程調度策略


凡是進程控制塊的policy的值爲SCHED_FIFO或SCHED_RR的進程,調度器都將其視爲實時進程。

進程控制塊中的域rt_pripority就是實時進程的優先級,其範圍時1~99。這一屬性將在調度時用於對進程權重參數weight的計算。

從上面介紹的函數goodness()代碼中可以看到,計算實時進程weight的代碼相當簡單,即:

weight = 1000 + p->rt_priority;
也就是說,Linux是按照嚴格的優先級別來選擇待運行進程的,並且實時進程的權重weight遠高於普通進程。

普通進程參數counter、nice,實時進程參數rt_pripority,調度器調度依據的參數weight之間的關係如下圖所示:

Linux允許多個實時進程具有相同的一個優先級別,Linux把所有的實時進程按照其優先級組織成若干個隊列,對於同一隊列的實時進程可以採用先來先服務和時間片輪轉調度兩種調度策略。

先來先服務調度(SCHED_FIFO)
該策略是符合POSIX標準的FIFO(先入先出)調度規則。即在同一級別的隊列中,先就緒的進程先入隊,後就緒的進程後入隊。

調度器在選擇運行進程時,就以優先級rt_pripority爲序查詢各個隊列,當發現隊列中有就緒進程時,就運行處於隊列頭位置的進程。其後,它就會一直運行,除非出現下述情況之一:

進程被具有更高優先級別進程剝奪了處理器;
進程自己因爲請求資源而堵塞;
進程自己主動放棄處理器。
時間片輪轉調度(SCHED_RR)
該策略時符合POSIX標準的RR(循環Round-Robin)調度規則。

這種策略與SCHED_FIFI類似,也根據優先級別把就緒進程分成若干個隊列,但每個隊列被組織成一個循環隊列,並且每個進程都擁有一個時間片。調度時,調度器仍然以優先級別爲序查詢就緒進程對列。當發現就緒進程隊列後,也是運行處在隊列頭部的進程,但是當這個進程將自己的時間片耗完之後,調度器把這個進程放到其隊列的隊尾,並且再選擇處在隊頭的進程來運行,當然前提條件是這時沒有更高優先級別的實時進程就緒。

 

Linux調度時機


進程調度的時機與引起進程調度的原因和進程調度的方式有關。Linux進程調度的時機主要有:

進程狀態轉換的時刻,如進程中止、進程睡眠等;
可運行隊列中新增加一個進程時;
當前進程的時間片用完時;
進程從系統調用返回用戶態時;
內核處理完中斷後,進程返回用戶態時。
但必須注意當前進程控制塊中的域need_resched的值爲非0時,才允許發生調度。另外,要注意,不能在中斷服務程序中調用調度器進行調度。

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