怎麼實現進程切換
- 2021 年 3 月 6 日
- 筆記
是什麼
進程是一個運行中的程序實體,擁有獨立的地址空間和邏輯控制流。
void sayHi()
{
printf("%s\n", "Hello,World");
return 0;
}
sayHi
就是一個函數,它一旦運行起來,就是進程。
獨立的邏輯控制流,是說這個進程就像獨佔一個CPU一樣。每個進程使用CPU的時間不是連續的,但它們的指令運行卻是前後銜接的,不會受到其他進程的指令對它的指令和數據大的更改。
運行起來
進程,這裡是指用戶進程(區別於內核),處在特權級3。內核在特權級0。CPU從通電運行起,就處在特權級0。要運行用戶進程,需要從特權級0轉移到特權級3。
CPU執行哪條指令,受cs:eip
控制。有一個指令,既能夠實現特權級從高向低轉移,又能更新cs:eip
。這個指令就是iretd
。
執行第一個用戶進程的方法是,把cs、eip、ss、esp
等寄存器需要的值入棧,然後用irted
出棧,用戶進程就能運行起來了。示意代碼如下。注意,下面的代碼只是說明大概思路,不是可執行的代碼。
push ss
push esp
push cs
push eip
irted
停下來
操作系統要讓一個CPU能運行多個進程。一個進程不能總是獨佔CPU,必須讓它停下來,把CPU讓給其他進程使用。
時鐘中斷每隔一段時間就會停止當前進程,轉移到執行中斷例程。
切換
在中斷例程中,我們可以為當前進程A建立一個」快照「,選擇運行進程B。
進程的」快照「,是把進程正在使用的數據、下一條要運行的指令等信息存儲起來;然後選擇另外一個進程,從存儲設備中取出這個進程運行所需要的所有信息,最後運行這個進程。
進程正在使用的數據,全部都在寄存器中,把寄存器中的數據存起來,就是為進程建立了快照。
把數據存儲到哪裡呢?在彙編語言中,動態存儲數據,我發現只有堆棧可以使用。進程的切換就是這樣一個流程:
- 進程A正在運行,時鐘中斷髮生,執行中斷例程。
- 從TSS的
esp0
中獲取進程A的堆棧棧頂,把esp
指向這個棧頂。 - 把寄存器中的值都壓入進程A的堆棧中。
- 把
esp
指向進程B的堆棧。調度程序就在這個步驟。 - 把TSS的
esp0
指向進程B的堆棧的最高地址處加4個位元組。下一次中斷髮生時,TSS獲取的esp0
的值就是在這裡獲取的。 - B的堆棧出棧。
- 使用
iretd
出棧ss、esp、cs、eip
,開始執行進程B的指令。
堆棧轉移
從上面的切換過程,很容易看出,需要轉移堆棧。
第1步~~~第3步,堆棧從用戶進程中的不知名堆棧轉移到存儲進程A數據的堆棧。這個堆棧,為進程建立快照使用。每個進程都有一個這樣的堆棧。
在第4步前後,都有一次堆棧切換:
- 前面,從A進程的堆棧切換到內核堆棧。進程調度程序很有可能會使用堆棧。如果仍然使用進程A的堆棧,會破壞A的堆棧,重新運行A時會有許多麻煩。
- 進程調度結束後,已經選擇了進程B,需要從堆棧中恢復B的數據,於是把
esp
指向B的堆棧。
進入內核後,我們切換過GDT和內核堆棧。當初我覺得沒必要切換內核堆棧,現在終於發現了內核堆棧的作用。
那個內核堆棧是這樣建立的:先用0填充一段內存空間,比如4KB,然後在下面設置一個標號,這個標號就是內核堆棧。代碼如下:
StatckStrace 1024 resp 0
TopStack: