Linux主動調度是如何發(fā)生的?
有關進程的知識在前面的博客中已經提到了,有不懂的地方請參考我前面的博客,今天我直接從進程調度和切換開始講。
Linux一個較大的優(yōu)勢就是進程調度,因為Linux是一個多進程系統(tǒng),它怎么進行進程調度直接影響這個系統(tǒng)的性能,而Linux系統(tǒng)的一個優(yōu)勢就是它的系統(tǒng)在進程調度這里做的很好。
在講進程調度前,我們先來看下有關Linux知識(以下4張圖片摘自孟寧老師課件)。
圖1.Linux內核架構
圖2.Linux執(zhí)行過程
圖3.CPU執(zhí)行指令角度
圖4.從內存角度看待Linux系統(tǒng)執(zhí)行
首先一起來看下Linux的進程的類型,一般將進程分為三種,一種為I/O消耗型進程,另一種是處理器消耗型進程,還有一種是混合型,也就是I/O消耗型進程和處理器消耗型進程混合在一起的。從他們的名字可以看出,這是以進程消耗資源的種類來進行分類的。
在Linux系統(tǒng)中,是按照什么規(guī)則來進行調度的呢?我們所知的有優(yōu)先級調度,還有時間片調度。其中優(yōu)先級指的是進程的優(yōu)先級,而時間片則指的是進程所需要消耗的時間。
那么Linux系統(tǒng)中進程調度的過程到底是一個什么流程呢?主要是以下幾個方面
1.從schedule()函數開始,進行調度選擇
2.從CPU的值變化上,解讀switch_to宏執(zhí)行分析
3.到堆棧發(fā)生切換位置,在切換堆棧前后,current_thread_info變化
4.再到地址空間發(fā)生切換,解釋地址空間的切換不會影響后續(xù)切換代碼的執(zhí)行
5.Current宏代表的進程發(fā)生變化的源碼位置
6.任務狀態(tài)段中關于內核堆棧的信息發(fā)生變化的源碼位置
下面來詳細的講解一下各個環(huán)節(jié)
在Linux內核中,schedule()函數選擇一個新的進程來運行,并調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換。部分函數具體代碼如下,一個調度新進程,一個是進行上下文切換,還有相關堆棧信息的保存。
next= pick_next_task(rq, prev);//進程調度算法都封裝這個函數內部
context_switch(rq,prev, next);//進程上下文切換
switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程
Schedule:主要負責幫助系統(tǒng)選定下一個執(zhí)行的進程
調度時機:
1.進程狀態(tài)轉換的時刻,進程終止、進程睡眠。
2.當前進程的時間片用完時。
3.設備驅動程序調用。
4.進程從中斷、異常及系統(tǒng)調用返回到用戶態(tài)時。
進程的從睡眠狀態(tài)到喚醒狀態(tài),完成了一次進程的調度,中間有保存相應的進程信息,有相應的隊列進行保存。
進程調度中還有一個現象是搶占,就是優(yōu)先級高的進程進行搶占低優(yōu)先級的執(zhí)行機會,用戶搶占發(fā)生在兩種情況下,一個是從系統(tǒng)調用返回用戶空間,另一個是從中斷處理程序返回用戶空間。
而內核搶占發(fā)生在:
1.當從中斷處理程序正在執(zhí)行,且返回內核空間之前;
2.當內核代碼再一次具有可搶占性的時候;
3.如果內核中的任務顯式調用;
4.內核中的任務被阻塞。
上下文的切換也是進程調用中一個比較重要的問題,其中有一個context_switch()函數完成以下工作,switch_mm()——該函數負責把虛擬內存從上一個進程映射切換到新進程中。而switch_to()——負責從上一個進程的處理器狀態(tài)切換到新進程的處理器狀態(tài)。切換的過程包括保存、恢復棧信息和寄存器信息。
其中各種進程之間的調度又有很多方法,主要有先進先出、時間片輪轉等方式,這里就不具體分析,有興趣的可以自行查閱相應的調度方法。
下面來看下具體實驗過程:
圖5.相關指令操作,創(chuàng)建文件
圖6.文件內部修改處
圖7.文件內部修改處
圖8.調試啟動
圖9.設置斷點
圖10.查看context_switch處相關點代碼
圖11.中途的代碼調試過程
總結:從上面可以看出,Linux系統(tǒng)的進程切換的一般執(zhí)行過程是這樣的,從進程X轉向進程Y的過程是這樣的。
1.正在運行的用戶態(tài)進程X 。
2.發(fā)生中斷——save cs:eip/esp/eflags(current)to kernel stack,then load cs:eip(entry of a specific ISR) and
ss:esp(point tokernel stack)。
3.SAVE_ALL//保存現場。
4.中斷處理過程中或中斷返回前調用了schedule(),其中的switch_to做了關鍵的進程上下文切換。
5.標號1之后開始運行用戶態(tài)進程Y(這里Y曾經通過以上步驟被切換出去過因此可以從標號1繼續(xù)執(zhí)行)。
6.restore_all //恢復現場。
7.iret- pop cs:eip/ss:esp/eflags from kernel stack //恢復。
8.繼續(xù)運行用戶態(tài)進程Y
好了,從上面可以看到,整個Linux系統(tǒng)的進程切換的執(zhí)行流程就是這個樣子的!