怎麼實現進程切換

是什麼

進程是一個運行中的程序實體,擁有獨立的地址空間和邏輯控制流。

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。

進程的」快照「,是把進程正在使用的數據、下一條要運行的指令等信息存儲起來;然後選擇另外一個進程,從存儲設備中取出這個進程運行所需要的所有信息,最後運行這個進程。

進程正在使用的數據,全部都在寄存器中,把寄存器中的數據存起來,就是為進程建立了快照。

把數據存儲到哪裡呢?在彙編語言中,動態存儲數據,我發現只有堆棧可以使用。進程的切換就是這樣一個流程:

  1. 進程A正在運行,時鐘中斷髮生,執行中斷例程。
  2. 從TSS的esp0中獲取進程A的堆棧棧頂,把esp指向這個棧頂。
  3. 把寄存器中的值都壓入進程A的堆棧中。
  4. esp指向進程B的堆棧。調度程序就在這個步驟。
  5. 把TSS的esp0指向進程B的堆棧的最高地址處加4個位元組。下一次中斷髮生時,TSS獲取的esp0的值就是在這裡獲取的。
  6. B的堆棧出棧。
  7. 使用iretd出棧ss、esp、cs、eip,開始執行進程B的指令。

堆棧轉移

從上面的切換過程,很容易看出,需要轉移堆棧。

第1步~~~第3步,堆棧從用戶進程中的不知名堆棧轉移到存儲進程A數據的堆棧。這個堆棧,為進程建立快照使用。每個進程都有一個這樣的堆棧。

在第4步前後,都有一次堆棧切換:

  1. 前面,從A進程的堆棧切換到內核堆棧。進程調度程序很有可能會使用堆棧。如果仍然使用進程A的堆棧,會破壞A的堆棧,重新運行A時會有許多麻煩。
  2. 進程調度結束後,已經選擇了進程B,需要從堆棧中恢復B的數據,於是把esp指向B的堆棧。

進入內核後,我們切換過GDT和內核堆棧。當初我覺得沒必要切換內核堆棧,現在終於發現了內核堆棧的作用。

那個內核堆棧是這樣建立的:先用0填充一段內存空間,比如4KB,然後在下面設置一個標號,這個標號就是內核堆棧。代碼如下:

StatckStrace		1024  resp		0
TopStack: