一.進程調度
現代的操作系統是多道的,這必然涉及到進程的調度,調度需要許多的調度算法。
1.需要多種調度算法的理由:
- 不同的進程對于計算機的資源(CPU、IO等)的需求是不同的,比如有些需要頻繁的IO,有些需要一直占CPU。那么他們之間的調度成為提升計算機效率的關鍵。
- 不同進程還可以分類成批處理進程,例如編譯程序、科學計算等,這類進程特點是不需要和用戶交互,而是直接在后臺埋頭苦干,還有一點就是不需要實時。還可以分類成實時進程,例如視頻音頻、機械控制等,特點是要及時穩定的響應。還可以分類為交互式進程,例如shell、文本編輯程序、圖形應用程序等,特點就是經常和用戶交互,因此會長時間的阻塞,不過響應還是要快。
基于這兩點,linux對不同進程使用不同調度策略。調度策略就是操作系統從進程就緒隊列中選一個使之獲得CPU執行權的算法。那么操作系統具體是怎么做的呢?就是它根據某些信息由算法計算出一個值,這個值就代表了進程的優先級。
2.進程調度的時機:
一個進程調用schedule()函數會發生進程調度(理解:切換到另一個進程),該函數在內核態,而且不是系統調用,因此用戶態程序無法主動調用它,只能被調度。 - 中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule();比如sleep后,立即會調用schedule。
- 內核線程(只有內核態沒有用戶態的特殊進程,雖然不會系統調用,但是仍然可以有時鐘中斷、IO中斷等)可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度;
- 用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。
3.進程的切換
為了控制進程的執行,內核必須有能力掛起正在CPU上執行的進程,并恢復以前掛起的某個進程的執行,這叫做進程切換、任務切換、上下文切換;
掛起正在CPU上執行的進程,與中斷時保存現場是不同的,中斷前后是在同一個進程上下文中,只是由用戶態轉向內核態執行;
進程上下文包含了進程執行需要的所有信息,包括: - 用戶地址空間:?包括程序代碼,數據,用戶堆棧等
- 控制信息?:進程描述符,內核堆棧等
- 硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)
schedule()函數選擇一個新的進程來運行,并調用context_switch進行上下文的切換,這個宏調用switch_to來進行關鍵上下文切換
next = pick_next_task(rq, prev);//進程調度算法都封裝這個函數內部
context_switch(rq, prev, next);//進程上下文切換
switch_to利用了prev和next兩個參數:prev指向當前進程,next指向被調度的進程。使得當前進程的內核堆棧切換為下一個要執行進程的內核堆棧;然后切換到下一個進程的EIP。
二.簡單跟蹤 Linux 系統切換過程.
1.首先在schedule,pick_next_task,context_switch,switch_to等函數處設置斷點,如下圖.
中斷過程如下:
三.具體分析
下面具體分析一下switch_to的代碼:
簡單看一下這個宏和函數的被調用關系:
schedule() --> context_switch() --> switch_to --> __switch_to()
這里面,schedule是主調度函數,涉及到一些調度算法,這里不討論。當schedule()需要暫停A進程的執行而繼續B進程的執行時,就發生了進程之間的切換。進程切換主要有兩部分:1、切換全局頁表項;2、切換內核堆棧和硬件上下文。這個切換工作由context_switch()完成。其中switch_to和__switch_to()主要完成第二部分。更詳細的,__switch_to()主要完成硬件上下文切換,switch_to主要完成內核堆棧切換。
閱讀switch_to時請注意:這是一個宏,不是函數,它的參數prev, next, last不是值拷貝,而是它的調用者context_switch()的局部變量。局部變量是通過%ebp寄存器來索引的,也就是通過n(%ebp),n是編譯時決定的,在不同的進程的同一段代碼中,同一局部變量的n是相同的。在switch_to中,發生了堆棧的切換,即ebp發生了改變,所以要格外留意在任一時刻的局部變量屬于哪一個進程。關于__switch_to()這個函數的調用,并不是通過普通的call來實現,而是直接jmp,函數參數也并不是通過堆棧來傳遞,而是通過寄存器來傳遞。
四、實驗總結:
通過實驗可知schedule()函數用來選擇一個新的進程來運行,并調用context_switch()進行上下文的切換,這個宏調用switch_to()來進行關鍵上下文切換,其中pick_next_task()函數封裝了進程調度算法。中斷處理過程(包括時鐘中斷、I/O中斷、系統調用和異常)中,直接調用schedule(),或者返回用戶態時根據need_resched標記調用schedule();內核線程可以直接調用schedule()進行進程切換,也可以在中斷處理過程中進行調度,也就是說內核線程作為一類的特殊的進程可以主動調度,也可以被動調度;用戶態進程無法實現主動調度,僅能通過陷入內核態后的某個時機點進行調度,即在中斷處理過程中進行調度。