長期以來,Linux一直把具備較好的平均系統響應時間和較高的吞吐量做爲調度算法的主要目標。但近年來,鑑於嵌入式系統的要求,Linux2.6在支持系統的實時性方面也作出了重大的改進。算法
Linux進程的時間片與權重參數
在處理器資源有限的系統中,全部進程都以輪流佔用處理器的方式交叉運行。爲使每一個進程都有運行的機會,調度器爲每一個進程分配了一個佔用處理器的時間額度,這個額度叫作進程的「時間片」,其初值就存放在進程控制塊的counter域中。進程每佔用處理器一次,系統就將此次所佔用時間從counter中扣除,由於counter反映了進程時間片的剩餘狀況,因此叫作剩餘時間片。函數
Linux調度的主要思想爲:調度器大體以全部進程時間片的總和爲一個調度週期;在每一個調度週期內能夠發生若干次調度,每次調度時,全部進程都以counter爲資本競爭處理器控制權,counter值大者勝出,優先運行;凡是已耗盡時間片(即counter=0)的,則當即退出本週期的競爭;當全部未被阻塞進程的時間片都耗盡,那就不等了。而後,由調度器從新爲進程分配時間片,開始下一個調度週期。this
Linux基於時間片調度的基本思想以下圖所示:.net
上面的調度方式主要體現的是一種公平性:若是一個進程的剩餘時間片多,那麼它在前期得到運行的時間片就少,爲了公平性,就應該適當的賦予它更高的優先級。可是這僅僅是一個整體的原則,爲了應付實際問題中的特殊情況,在上述平等原則的基礎上,爲了給特殊進程一些特殊照顧,在爲進程分配時間片時,Linux容許用戶經過一個系統調用nice()來設置參數nice影響進程的優先權。因此,系統真正用來肯定進程的優先權時,使用的依據爲權重參數weight,weight大的進程優先運行。設計
weight與nice、counter之間的關係大致以下:指針
weight 正比於 [counter+(20-nice)]blog
關於三者之間的這個關係將會在後面的內容中詳細的講到。隊列
由於nice是用戶在建立進程時肯定的,在進程的運行過程當中通常不會改變,因此叫作靜態優先級;counter則隨着進程時間片的小號在不斷減少,是變化的,因此叫作動態優先級。進程
調度策略
目前,標準Linux系統支持非實時(普通)和實時兩種進程。與此相對應的,Linux有兩種進程調度策略:普通進程調度和實時進程調度。所以,在每一個進程的進程控制塊中都有一個域policy,用來指明該進程爲什麼種進程,應該使用何種調度策略。ip
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時,才容許發生調度。另外,要注意,不能在中斷服務程序中調用調度器進行調度。 --------------------- 做者:Yngz_Miao 來源:CSDN 原文:https://blog.csdn.net/qq_38410730/article/details/81253686 版權聲明:本文爲博主原創文章,轉載請附上博文連接!