一.進程調(diào)度
現(xiàn)代的操作系統(tǒng)是多道的,這必然涉及到進程的調(diào)度,調(diào)度需要許多的調(diào)度算法。
1.需要多種調(diào)度算法的理由:
- 不同的進程對于計算機的資源(CPU、IO等)的需求是不同的,比如有些需要頻繁的IO,有些需要一直占CPU。那么他們之間的調(diào)度成為提升計算機效率的關鍵。
- 不同進程還可以分類成批處理進程,例如編譯程序、科學計算等,這類進程特點是不需要和用戶交互,而是直接在后臺埋頭苦干,還有一點就是不需要實時。還可以分類成實時進程,例如視頻音頻、機械控制等,特點是要及時穩(wěn)定的響應。還可以分類為交互式進程,例如shell、文本編輯程序、圖形應用程序等,特點就是經(jīng)常和用戶交互,因此會長時間的阻塞,不過響應還是要快。
基于這兩點,linux對不同進程使用不同調(diào)度策略。調(diào)度策略就是操作系統(tǒng)從進程就緒隊列中選一個使之獲得CPU執(zhí)行權的算法。那么操作系統(tǒng)具體是怎么做的呢?就是它根據(jù)某些信息由算法計算出一個值,這個值就代表了進程的優(yōu)先級。
2.進程調(diào)度的時機:
一個進程調(diào)用schedule()函數(shù)會發(fā)生進程調(diào)度(理解:切換到另一個進程),該函數(shù)在內(nèi)核態(tài),而且不是系統(tǒng)調(diào)用,因此用戶態(tài)程序無法主動調(diào)用它,只能被調(diào)度。 - 中斷處理過程(包括時鐘中斷、I/O中斷、系統(tǒng)調(diào)用和異常)中,直接調(diào)用schedule(),或者返回用戶態(tài)時根據(jù)need_resched標記調(diào)用schedule();比如sleep后,立即會調(diào)用schedule。
- 內(nèi)核線程(只有內(nèi)核態(tài)沒有用戶態(tài)的特殊進程,雖然不會系統(tǒng)調(diào)用,但是仍然可以有時鐘中斷、IO中斷等)可以直接調(diào)用schedule()進行進程切換,也可以在中斷處理過程中進行調(diào)度,也就是說內(nèi)核線程作為一類的特殊的進程可以主動調(diào)度,也可以被動調(diào)度;
- 用戶態(tài)進程無法實現(xiàn)主動調(diào)度,僅能通過陷入內(nèi)核態(tài)后的某個時機點進行調(diào)度,即在中斷處理過程中進行調(diào)度。
3.進程的切換
為了控制進程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上執(zhí)行的進程,并恢復以前掛起的某個進程的執(zhí)行,這叫做進程切換、任務切換、上下文切換;
掛起正在CPU上執(zhí)行的進程,與中斷時保存現(xiàn)場是不同的,中斷前后是在同一個進程上下文中,只是由用戶態(tài)轉(zhuǎn)向內(nèi)核態(tài)執(zhí)行;
進程上下文包含了進程執(zhí)行需要的所有信息,包括: - 用戶地址空間:?包括程序代碼,數(shù)據(jù),用戶堆棧等
- 控制信息?:進程描述符,內(nèi)核堆棧等
- 硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)
schedule()函數(shù)選擇一個新的進程來運行,并調(diào)用context_switch進行上下文的切換,這個宏調(diào)用switch_to來進行關鍵上下文切換
next = pick_next_task(rq, prev);//進程調(diào)度算法都封裝這個函數(shù)內(nèi)部
context_switch(rq, prev, next);//進程上下文切換
switch_to利用了prev和next兩個參數(shù):prev指向當前進程,next指向被調(diào)度的進程。使得當前進程的內(nèi)核堆棧切換為下一個要執(zhí)行進程的內(nèi)核堆棧;然后切換到下一個進程的EIP。
二.簡單跟蹤 Linux 系統(tǒng)切換過程.
1.首先在schedule,pick_next_task,context_switch,switch_to等函數(shù)處設置斷點,如下圖.

中斷過程如下:



三.具體分析
下面具體分析一下switch_to的代碼:
簡單看一下這個宏和函數(shù)的被調(diào)用關系:
schedule() --> context_switch() --> switch_to --> __switch_to()
這里面,schedule是主調(diào)度函數(shù),涉及到一些調(diào)度算法,這里不討論。當schedule()需要暫停A進程的執(zhí)行而繼續(xù)B進程的執(zhí)行時,就發(fā)生了進程之間的切換。進程切換主要有兩部分:1、切換全局頁表項;2、切換內(nèi)核堆棧和硬件上下文。這個切換工作由context_switch()完成。其中switch_to和__switch_to()主要完成第二部分。更詳細的,__switch_to()主要完成硬件上下文切換,switch_to主要完成內(nèi)核堆棧切換。
閱讀switch_to時請注意:這是一個宏,不是函數(shù),它的參數(shù)prev, next, last不是值拷貝,而是它的調(diào)用者context_switch()的局部變量。局部變量是通過%ebp寄存器來索引的,也就是通過n(%ebp),n是編譯時決定的,在不同的進程的同一段代碼中,同一局部變量的n是相同的。在switch_to中,發(fā)生了堆棧的切換,即ebp發(fā)生了改變,所以要格外留意在任一時刻的局部變量屬于哪一個進程。關于__switch_to()這個函數(shù)的調(diào)用,并不是通過普通的call來實現(xiàn),而是直接jmp,函數(shù)參數(shù)也并不是通過堆棧來傳遞,而是通過寄存器來傳遞。
四、實驗總結:
通過實驗可知schedule()函數(shù)用來選擇一個新的進程來運行,并調(diào)用context_switch()進行上下文的切換,這個宏調(diào)用switch_to()來進行關鍵上下文切換,其中pick_next_task()函數(shù)封裝了進程調(diào)度算法。中斷處理過程(包括時鐘中斷、I/O中斷、系統(tǒng)調(diào)用和異常)中,直接調(diào)用schedule(),或者返回用戶態(tài)時根據(jù)need_resched標記調(diào)用schedule();內(nèi)核線程可以直接調(diào)用schedule()進行進程切換,也可以在中斷處理過程中進行調(diào)度,也就是說內(nèi)核線程作為一類的特殊的進程可以主動調(diào)度,也可以被動調(diào)度;用戶態(tài)進程無法實現(xiàn)主動調(diào)度,僅能通過陷入內(nèi)核態(tài)后的某個時機點進行調(diào)度,即在中斷處理過程中進行調(diào)度。