轉自:http://www.cnblogs.com/zhaoyl/archive/2012/09/04/2671156.htmlhtml
Linux進程調度的目標linux
1.高效性:高效意味着在相同的時間下要完成更多的任務。調度程序會被頻繁的執行,因此調度程序要儘量的高效;算法
2.增強交互性能:在系統至關的負載下,也要保證系統的響應時間;shell
3.保證公平和避免飢渴;緩存
4.SMP調度:調度程序必須支持多處理系統;數據結構
5.軟實時調度:系統必須有效的調用實時進程,但不保證必定知足其要求;多線程
Linux進程優先級負載均衡
進程提供了兩種優先級,一種是普通的進程優先級,第二個是實時優先級。前者適用SCHED_NORMAL調度策略,後者可選SCHED_FIFO或SCHED_RR調度策略。任什麼時候候,實時進程的優先級都高於普通進程,實時進程只會被更高級的實時進程搶佔,同級實時進程之間是按照FIFO(一次機會作完)或者RR(屢次輪轉)規則調度的。異步
首先,說下實時進程的調度socket
實時進程,只有靜態優先級,由於內核不會再根據休眠等因素對其靜態優先級作調整,其範圍在0~MAX_RT_PRIO-1間。默認MAX_RT_PRIO配置爲100,也即,默認的實時優先級範圍是0~99。而nice值,影響的是優先級在MAX_RT_PRIO~MAX_RT_PRIO+40範圍內的進程。
不一樣與普通進程,系統調度時,實時優先級高的進程老是先於優先級低的進程執行。知道實時優先級高的實時進程沒法執行。實時進程老是被認爲處於活動狀態。若是有數個 優先級相同的實時進程,那麼系統就會按照進程出如今隊列上的順序選擇進程。假設當前CPU運行的實時進程A的優先級爲a,而此時有個優先級爲b的實時進程B進入可運行狀態,那麼只要b<a,系統將中斷A的執行,而優先執行B,直到B沒法執行(不管A,B爲什麼種實時進程)。
不一樣調度策略的實時進程只有在相同優先級時纔有可比性:
1. 對於FIFO的進程,意味着只有當前進程執行完畢纔會輪到其餘進程執行。因而可知至關霸道。
2. 對於RR的進程。一旦時間片消耗完畢,則會將該進程置於隊列的末尾,而後運行其餘相同優先級的進程,若是沒有其餘相同優先級的進程,則該進程會繼續執行。
總而言之,對於實時進程,高優先級的進程就是大爺。它執行到無法執行了,才輪到低優先級的進程執行。等級制度至關森嚴啊。
重頭戲,說下非實時進程調度
引子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
將當前目錄下的documents目錄打包,但不但願tar佔用太多CPU:
nice -19 tar zcf pack.tar.gz documents
這個「-19」中的「-」僅表示參數前綴;因此,若是但願賦予tar進程最高的優先級,則執行:
nice --19 tar zcf pack.tar.gz documents
也可修改已經存在的進程的優先級:
將PID爲1799的進程優先級設置爲最低:
renice 19 1799
renice命令與nice命令的優先級參數的形式是相反的,直接以優先級值做爲參數便可,無「-」前綴說法。
|
言歸正傳
Linux對普通的進程,根據動態優先級進行調度。而動態優先級是由靜態優先級(static_prio)調整而來。Linux下,靜態優先級是用戶不可見的,隱藏在內核中。而內核提供給用戶一個能夠影響靜態優先級的接口,那就是nice值,二者關係以下:
static_prio=MAX_RT_PRIO +nice+ 20
nice值的範圍是-20~19,於是靜態優先級範圍在100~139之間。nice數值越大就使得static_prio越大,最終進程優先級就越低。
ps -el 命令執行結果:NI列顯示的每一個進程的nice值,PRI是進程的優先級(若是是實時進程就是靜態優先級,若是是非實時進程,就是動態優先級)
而進程的時間片就是徹底依賴 static_prio 定製的,見下圖,摘自《深刻理解linux內核》,
咱們前面也說了,系統調度時,還會考慮其餘因素,於是會計算出一個叫進程動態優先級的東西,根據此來實施調度。由於,不只要考慮靜態優先級,也要考慮進程的屬性。例如若是進程屬於交互式進程,那麼能夠適當的調高它的優先級,使得界面反應地更加迅速,從而使用戶獲得更好的體驗。Linux2.6 在這方面有了較大的提升。Linux2.6認爲,交互式進程能夠從平均睡眠時間這樣一個measurement進行判斷。進程過去的睡眠時間越多,則越有可能屬於交互式進程。則系統調度時,會給該進程更多的獎勵(bonus),以便該進程有更多的機會可以執行。獎勵(bonus)從0到10不等。
系統會嚴格按照動態優先級高低的順序安排進程執行。動態優先級高的進程進入非運行狀態,或者時間片消耗完畢纔會輪到動態優先級較低的進程執行。動態優先級的計算主要考慮兩個因素:靜態優先級,進程的平均睡眠時間也即bonus。計算公式以下,
dynamic_prio = max (100, min (static_prio - bonus + 5, 139))
在調度時,Linux2.6 使用了一個小小的trick,就是算法中經典的空間換時間的思想[還沒對照源碼確認],使得計算最優進程可以在O(1)的時間內完成。
爲何根據睡眠和運行時間肯定獎懲分數是合理的
睡眠和CPU耗時反應了進程IO密集和CPU密集兩大瞬時特色,不一樣時期,一個進程可能便是CPU密集型也是IO密集型進程。對於表現爲IO密集的進程,應該常常運行,但每次時間片不要太長。對於表現爲CPU密集的進程,CPU不該該讓其常常運行,但每次運行時間片要長。交互進程爲例,假如以前其其大部分時間在於等待CPU,這時爲了調高相應速度,就須要增長獎勵分。另外一方面,若是此進程老是耗盡每次分配給它的時間片,爲了對其餘進程公平,就要增長這個進程的懲罰分數。能夠參考CFS的virtutime機制.
現代方法CFS
再也不單純依靠進程優先級絕對值,而是參考其絕對值,綜合考慮全部進程的時間,給出當前調度時間單位內其應有的權重,也就是,每一個進程的權重X單位時間=應獲cpu時間,可是這個應得的cpu時間不該過小(假設閾值爲1ms),不然會由於切換得不償失。可是,當進程足夠多時候,確定有不少不一樣權重的進程得到相同的時間——最低閾值1ms,因此,CFS只是近似徹底公平。
詳情參考 《linux內核cfs淺析》
Linux進程狀態機
進程是經過fork系列的系統調用(fork、clone、vfork)來建立的,內核(或內核模塊)也能夠經過kernel_thread函數建立內核進程。這些建立子進程的函數本質上都完成了相同的功能——將調用進程複製一份,獲得子進程。(能夠經過選項參數來決定各類資源是共享、仍是私有。)
那麼既然調用進程處於TASK_RUNNING狀態(不然,它若不是正在運行,又怎麼進行調用?),則子進程默認也處於TASK_RUNNING狀態。
另外,在系統調用clone和內核函數kernel_thread也接受CLONE_STOPPED選項,從而將子進程的初始狀態置爲 TASK_STOPPED。
進程建立後,狀態可能發生一系列的變化,直到進程退出。而儘管進程狀態有好幾種,可是進程狀態的變遷卻只有兩個方向——從TASK_RUNNING狀態變爲非TASK_RUNNING狀態、或者從非TASK_RUNNING狀態變爲TASK_RUNNING狀態。總之,TASK_RUNNING是必經之路,不可能兩個非RUN狀態直接轉換。
也就是說,若是給一個TASK_INTERRUPTIBLE狀態的進程發送SIGKILL信號,這個進程將先被喚醒(進入TASK_RUNNING狀態),而後再響應SIGKILL信號而退出(變爲TASK_DEAD狀態)。並不會從TASK_INTERRUPTIBLE狀態直接退出。
進程從非TASK_RUNNING狀態變爲TASK_RUNNING狀態,是由別的進程(也多是中斷處理程序)執行喚醒操做來實現的。執行喚醒的進程設置被喚醒進程的狀態爲TASK_RUNNING,而後將其task_struct結構加入到某個CPU的可執行隊列中。因而被喚醒的進程將有機會被調度執行。
而進程從TASK_RUNNING狀態變爲非TASK_RUNNING狀態,則有兩種途徑:
一、響應信號而進入TASK_STOPED狀態、或TASK_DEAD狀態;
二、執行系統調用主動進入TASK_INTERRUPTIBLE狀態(如nanosleep系統調用)、或TASK_DEAD狀態(如exit系統調用);或因爲執行系統調用須要的資源得不到滿 足,而進入TASK_INTERRUPTIBLE狀態或TASK_UNINTERRUPTIBLE狀態(如select系統調用)。
顯然,這兩種狀況都只能發生在進程正在CPU上執行的狀況下。
經過ps命令咱們可以查看到系統中存在的進程,以及它們的狀態:
R(TASK_RUNNING),可執行狀態。
只有在該狀態的進程纔可能在CPU上運行。而同一時刻可能有多個進程處於可執行狀態,這些進程的task_struct結構(進程控制塊)被放入對應CPU的可執行隊列中(一個進程最多隻能出如今一個CPU的可執行隊列中)。進程調度器的任務就是從各個CPU的可執行隊列中分別選擇一個進程在該CPU上運行。
只要可執行隊列不爲空,其對應的CPU就不能偷懶,就要執行其中某個進程。通常稱此時的CPU「忙碌」。對應的,CPU「空閒」就是指其對應的可執行隊列爲空,以至於CPU無事可作。
有人問,爲何死循環程序會致使CPU佔用高呢?由於死循環程序基本上老是處於TASK_RUNNING狀態(進程處於可執行隊列中)。除非一些很是極端狀況(好比系統內存嚴重緊缺,致使進程的某些須要使用的頁面被換出,而且在頁面須要換入時又沒法分配到內存……),不然這個進程不會睡眠。因此CPU的可執行隊列老是不爲空(至少有這麼個進程存在),CPU也就不會「空閒」。
不少操做系統教科書將正在CPU上執行的進程定義爲RUNNING狀態、而將可執行可是還沒有被調度執行的進程定義爲READY狀態,這兩種狀態在linux下統一爲 TASK_RUNNING狀態。
S(TASK_INTERRUPTIBLE),可中斷的睡眠狀態。
處於這個狀態的進程由於等待某某事件的發生(好比等待socket鏈接、等待信號量),而被掛起。這些進程的task_struct結構被放入對應事件的等待隊列中。當這些事件發生時(由外部中斷觸發、或由其餘進程觸發),對應的等待隊列中的一個或多個進程將被喚醒。
經過ps命令咱們會看到,通常狀況下,進程列表中的絕大多數進程都處於TASK_INTERRUPTIBLE狀態(除非機器的負載很高)。畢竟CPU就這麼一兩個,進程動輒幾十上百個,若是不是絕大多數進程都在睡眠,CPU又怎麼響應得過來。
D(TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態。
與TASK_INTERRUPTIBLE狀態相似,進程處於睡眠狀態,可是此刻進程是不可中斷的。不可中斷,指的並非CPU不響應外部硬件的中斷,而是指進程不響應異步信號。
絕大多數狀況下,進程處在睡眠狀態時,老是應該可以響應異步信號的。不然你將驚奇的發現,kill -9居然殺不死一個正在睡眠的進程了!因而咱們也很好理解,爲何ps命令看到的進程幾乎不會出現TASK_UNINTERRUPTIBLE狀態,而老是TASK_INTERRUPTIBLE狀態。
而TASK_UNINTERRUPTIBLE狀態存在的意義就在於,內核的某些處理流程是不能被打斷的。若是響應異步信號,程序的執行流程中就會被插入一段用於處理異步信號的流程(這個插入的流程可能只存在於內核態,也可能延伸到用戶態),因而原有的流程就被中斷了(參見《linux異步信號handle淺析》)。
在進程對某些硬件進行操做時(好比進程調用read系統調用對某個設備文件進行讀操做,而read系統調用最終執行到對應設備驅動的代碼,並與對應的物理設備進行交互),可能須要使用TASK_UNINTERRUPTIBLE狀態對進程進行保護,以免進程與設備交互的過程被打斷,形成設備陷入不可控的狀態。(好比read系統調用觸發了一次磁盤到用戶空間的內存的DMA,若是DMA進行過程當中,進程因爲響應信號而退出了,那麼DMA正在訪問的內存可能就要被釋放了。)這種狀況下的TASK_UNINTERRUPTIBLE狀態老是很是短暫的,經過ps命令基本上不可能捕捉到。
linux系統中也存在容易捕捉的TASK_UNINTERRUPTIBLE狀態。執行vfork系統調用後,父進程將進入TASK_UNINTERRUPTIBLE狀態,直到子進程調用exit或exec。
經過下面的代碼就能獲得處於TASK_UNINTERRUPTIBLE狀態的進程:
#include <unistd.h>
void main() {
if (!vfork()) sleep(100);
}
編譯運行,而後ps一下:
kouu@kouu-one:~/test$ ps -ax | grep a\.out
4371 pts/0 D+ 0:00 ./a.out
4372 pts/0 S+ 0:00 ./a.out
4374 pts/1 S+ 0:00 grep a.out
而後咱們能夠試驗一下TASK_UNINTERRUPTIBLE狀態的威力。無論kill仍是kill -9,這個TASK_UNINTERRUPTIBLE狀態的父進程依然屹立不倒。
T(TASK_STOPPED or TASK_TRACED),暫停狀態或跟蹤狀態。
向進程發送一個SIGSTOP信號,它就會因響應該信號而進入TASK_STOPPED狀態(除非該進程自己處於TASK_UNINTERRUPTIBLE狀態而不響應信號)。(SIGSTOP與SIGKILL信號同樣,是很是強制的。不容許用戶進程經過signal系列的系統調用從新設置對應的信號處理函數。)
向進程發送一個SIGCONT信號,可讓其從TASK_STOPPED狀態恢復到TASK_RUNNING狀態。
當進程正在被跟蹤時,它處於TASK_TRACED這個特殊的狀態。「正在被跟蹤」指的是進程暫停下來,等待跟蹤它的進程對它進行操做。好比在gdb中對被跟蹤的進程下一個斷點,進程在斷點處停下來的時候就處於TASK_TRACED狀態。而在其餘時候,被跟蹤的進程仍是處於前面提到的那些狀態。
對於進程自己來講,TASK_STOPPED和TASK_TRACED狀態很相似,都是表示進程暫停下來。
而TASK_TRACED狀態至關於在TASK_STOPPED之上多了一層保護,處於TASK_TRACED狀態的進程不能響應SIGCONT信號而被喚醒。只能等到調試進程經過ptrace系統調用執行PTRACE_CONT、PTRACE_DETACH等操做(經過ptrace系統調用的參數指定操做),或調試進程退出,被調試的進程才能恢復TASK_RUNNING狀態。
Z(TASK_DEAD - EXIT_ZOMBIE),退出狀態,進程成爲殭屍進程。
進程在退出的過程當中,處於TASK_DEAD狀態。
在這個退出過程當中,進程佔有的全部資源將被回收,除了task_struct結構(以及少數資源)之外。因而進程就只剩下task_struct這麼個空殼,故稱爲殭屍。
之因此保留task_struct,是由於task_struct裏面保存了進程的退出碼、以及一些統計信息。而其父進程極可能會關心這些信息。好比在shell中,$?變量就保存了最後一個退出的前臺進程的退出碼,而這個退出碼每每被做爲if語句的判斷條件。
固然,內核也能夠將這些信息保存在別的地方,而將task_struct結構釋放掉,以節省一些空間。可是使用task_struct結構更爲方便,由於在內核中已經創建了從pid到task_struct查找關係,還有進程間的父子關係。釋放掉task_struct,則須要創建一些新的數據結構,以便讓父進程找到它的子進程的退出信息。
父進程能夠經過wait系列的系統調用(如wait四、waitid)來等待某個或某些子進程的退出,並獲取它的退出信息。而後wait系列的系統調用會順便將子進程的屍體(task_struct)也釋放掉。
子進程在退出的過程當中,內核會給其父進程發送一個信號,通知父進程來「收屍」。這個信號默認是SIGCHLD,可是在經過clone系統調用建立子進程時,能夠設置這個信號。
經過下面的代碼可以製造一個EXIT_ZOMBIE狀態的進程:
#include <unistd.h>
void main() {
if (fork())
while(1) sleep(100);
}
編譯運行,而後ps一下:
kouu@kouu-one:~/test$ ps -ax | grep a\.out
10410 pts/0 S+ 0:00 ./a.out
10411 pts/0 Z+ 0:00 [a.out] <defunct>
10413 pts/1 S+ 0:00 grep a.out
只要父進程不退出,這個殭屍狀態的子進程就一直存在。那麼若是父進程退出了呢,誰又來給子進程「收屍」?
當進程退出的時候,會將它的全部子進程都託管給別的進程(使之成爲別的進程的子進程)。託管給誰呢?多是退出進程所在進程組的下一個進程(若是存在的話),或者是1號進程。因此每一個進程、每時每刻都有父進程存在。除非它是1號進程。
1號進程,pid爲1的進程,又稱init進程。
linux系統啓動後,第一個被建立的用戶態進程就是init進程。它有兩項使命:
一、執行系統初始化腳本,建立一系列的進程(它們都是init進程的子孫);
二、在一個死循環中等待其子進程的退出事件,並調用waitid系統調用來完成「收屍」工做;
init進程不會被暫停、也不會被殺死(這是由內核來保證的)。它在等待子進程退出的過程當中處於TASK_INTERRUPTIBLE狀態,「收屍」過程當中則處於TASK_RUNNING狀態。
X(TASK_DEAD - EXIT_DEAD),退出狀態,進程即將被銷燬。
而進程在退出過程當中也可能不會保留它的task_struct。好比這個進程是多線程程序中被detach過的進程(進程?線程?參見《linux線程淺析》)。或者父進程經過設置SIGCHLD信號的handler爲SIG_IGN,顯式的忽略了SIGCHLD信號。(這是posix的規定,儘管子進程的退出信號能夠被設置爲SIGCHLD之外的其餘信號。)
此時,進程將被置於EXIT_DEAD退出狀態,這意味着接下來的代碼當即就會將該進程完全釋放。因此EXIT_DEAD狀態是很是短暫的,幾乎不可能經過ps命令捕捉到。
一些重要的雜項
調度程序的效率
「優先級」明確了哪一個進程應該被調度執行,而調度程序還必需要關心效率問題。調度程序跟內核中的不少過程同樣會頻繁被執行,若是效率不濟就會浪費不少CPU時間,致使系統性能降低。
在linux 2.4時,可執行狀態的進程被掛在一個鏈表中。每次調度,調度程序須要掃描整個鏈表,以找出最優的那個進程來運行。複雜度爲O(n);
在linux 2.6早期,可執行狀態的進程被掛在N(N=140)個鏈表中,每個鏈表表明一個優先級,系統中支持多少個優先級就有多少個鏈表。每次調度,調度程序只須要從第一個不爲空的鏈表中取出位於鏈表頭的進程便可。這樣就大大提升了調度程序的效率,複雜度爲O(1);
在linux 2.6近期的版本中,可執行狀態的進程按照優先級順序被掛在一個紅黑樹(能夠想象成平衡二叉樹)中。每次調度,調度程序須要從樹中找出優先級最高的進程。複雜度爲O(logN)。
那麼,爲何從linux 2.6早期到近期linux 2.6版本,調度程序選擇進程時的複雜度反而增長了呢?
這是由於,與此同時,調度程序對公平性的實現從上面提到的第一種思路改變爲第二種思路(經過動態調整優先級實現)。而O(1)的算法是基於一組數目不大的鏈表來實現的,按個人理解,這使得優先級的取值範圍很小(區分度很低),不能知足公平性的需求。而使用紅黑樹則對優先級的取值沒有限制(能夠用32位、64位、或更多位來表示優先級的值),而且O(logN)的複雜度也仍是很高效的。
調度觸發的時機
調度的觸發主要有以下幾種狀況:
一、當前進程(正在CPU上運行的進程)狀態變爲非可執行狀態。
進程執行系統調用主動變爲非可執行狀態。好比執行nanosleep進入睡眠、執行exit退出、等等;
進程請求的資源得不到知足而被迫進入睡眠狀態。好比執行read系統調用時,磁盤高速緩存裏沒有所須要的數據,從而睡眠等待磁盤IO;
進程響應信號而變爲非可執行狀態。好比響應SIGSTOP進入暫停狀態、響應SIGKILL退出、等等;
二、搶佔。進程運行時,非預期地被剝奪CPU的使用權。這又分兩種狀況:進程用完了時間片、或出現了優先級更高的進程。
優先級更高的進程受正在CPU上運行的進程的影響而被喚醒。如發送信號主動喚醒,或由於釋放互斥對象(如釋放鎖)而被喚醒;
內核在響應時鐘中斷的過程當中,發現當前進程的時間片用完;
內核在響應中斷的過程當中,發現優先級更高的進程所等待的外部資源的變爲可用,從而將其喚醒。好比CPU收到網卡中斷,內核處理該中斷,發現某個socket可讀,因而喚醒正在等待讀這個socket的進程;再好比內核在處理時鐘中斷的過程當中,觸發了定時器,從而喚醒對應的正在nanosleep系統調用中睡眠的進程;
內核搶佔
理想狀況下,只要知足「出現了優先級更高的進程」這個條件,當前進程就應該被馬上搶佔。可是,就像多線程程序須要用鎖來保護臨界區資源同樣,內核中也存在不少這樣的臨界區,不大可能隨時隨地都能接收搶佔。
linux 2.4時的設計就很是簡單,內核不支持搶佔。進程運行在內核態時(好比正在執行系統調用、正處於異常處理函數中),是不容許搶佔的。必須等到返回用戶態時纔會觸發調度(確切的說,是在返回用戶態以前,內核會專門檢查一下是否須要調度);
linux 2.6則實現了內核搶佔,可是在不少地方仍是爲了保護臨界區資源而須要臨時性的禁用內核搶佔。
也有一些地方是出於效率考慮而禁用搶佔,比較典型的是spin_lock。spin_lock是這樣一種鎖,若是請求加鎖得不到知足(鎖已被別的進程佔有),則當前進程在一個死循環中不斷檢測鎖的狀態,直到鎖被釋放。
爲何要這樣忙等待呢?由於臨界區很小,好比只保護「i+=j++;」這麼一句。若是由於加鎖失敗而造成「睡眠-喚醒」這麼個過程,就有些得不償失了。
那麼既然當前進程忙等待(不睡眠),誰又來釋放鎖呢?其實已獲得鎖的進程是運行在另外一個CPU上的,而且是禁用了內核搶佔的。這個進程不會被其餘進程搶佔,因此等待鎖的進程只有可能運行在別的CPU上。(若是隻有一個CPU呢?那麼就不可能存在等待鎖的進程了。)
而若是不由用內核搶佔呢?那麼獲得鎖的進程將可能被搶佔,因而可能好久都不會釋放鎖。因而,等待鎖的進程可能就不知何年何月得償所望了。
對於一些實時性要求更高的系統,則不能容忍spin_lock這樣的東西。寧肯改用更費勁的「睡眠-喚醒」過程,也不能由於禁用搶佔而讓更高優先級的進程等待。好比,嵌入式實時linux montavista就是這麼幹的。
因而可知,實時並不表明高效。不少時候爲了實現「實時」,仍是須要對性能作必定讓步的。
多處理器下的負載均衡
前面咱們並無專門討論多處理器對調度程序的影響,其實也沒有什麼特別的,就是在同一時刻能有多個進程並行地運行而已。那麼,爲何會有「多處理器負載均衡」這個事情呢?
若是系統中只有一個可執行隊列,哪一個CPU空閒了就去隊列中找一個最合適的進程來執行。這樣不是很好很均衡嗎?
的確如此,可是多處理器共用一個可執行隊列會有一些問題。顯然,每一個CPU在執行調度程序時都須要把隊列鎖起來,這會使得調度程序難以並行,可能致使系統性能降低。而若是每一個CPU對應一個可執行隊列則不存在這樣的問題。
另外,多個可執行隊列還有一個好處。這使得一個進程在一段時間內老是在同一個CPU上執行,那麼極可能這個CPU的各級cache中都緩存着這個進程的數據,頗有利於系統性能的提高。
因此,在linux下,每一個CPU都有着對應的可執行隊列,而一個可執行狀態的進程在同一時刻只能處於一個可執行隊列中。
因而,「多處理器負載均衡」這個麻煩事情就來了。內核須要關注各個CPU可執行隊列中的進程數目,在數目不均衡時作出適當調整。何時須要調整,以多大力度進程調整,這些都是內核須要關心的。固然,儘可能不要調整最好,畢竟調整起來又要耗CPU、又要鎖可執行隊列,代價仍是不小的。
另外,內核還得關心各個CPU的關係。兩個CPU之間,多是相互獨立的、多是共享cache的、甚至多是由同一個物理CPU經過超線程技術虛擬出來的……CPU之間的關係也是實現負載均衡的重要依據。關係越緊密,進程在它們之間遷移的代價就越小。參見《linux內核SMP負載均衡淺析》。
優先級繼承
因爲互斥,一個進程(設爲A)可能由於等待進入臨界區而睡眠。直到正在佔有相應資源的進程(設爲B)退出臨界區,進程A才被喚醒。
可能存在這樣的狀況:A的優先級很是高,B的優先級很是低。B進入了臨界區,可是卻被其餘優先級較高的進程(設爲C)搶佔了,而得不到運行,也就沒法退出臨界區。因而A也就沒法被喚醒。
A有着很高的優先級,可是如今卻淪落到跟B一塊兒,被優先級並不過高的C搶佔,致使執行被推遲。這種現象就叫作優先級反轉。
出現這種現象是很不合理的。較好的應對措施是:當A開始等待B退出臨界區時,B臨時獲得A的優先級(仍是假設A的優先級高於B),以便順利完成處理過程,退出臨界區。以後B的優先級恢復。這就是優先級繼承的方法。
中斷處理線程化
在linux下,中斷處理程序運行於一個不可調度的上下文中。從CPU響應硬件中斷自動跳轉到內核設定的中斷處理程序去執行,到中斷處理程序退出,整個過程是不能被搶佔的。
一個進程若是被搶佔了,能夠經過保存在它的進程控制塊(task_struct)中的信息,在以後的某個時間恢復它的運行。而中斷上下文則沒有task_struct,被搶佔了就無法恢復了。
中斷處理程序不能被搶佔,也就意味着中斷處理程序的「優先級」比任何進程都高(必須等中斷處理程序完成了,進程才能被執行)。可是在實際的應用場景中,可能某些實時進程應該獲得比中斷處理程序更高的優先級。
因而,一些實時性要求更高的系統就給中斷處理程序賦予了task_struct以及優先級,使得它們在必要的時候可以被高優先級的進程搶佔。可是顯然,作這些工做是會給系統形成必定開銷的,這也是爲了實現「實時」而對性能作出的一種讓步。
參考文獻:《linux內核設計與實現》
《現代操做系統》
《linux內核SMP負載均衡淺析》(本文未引用能夠作拓展參考)