### 進程切換:
為了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,并恢復以前掛起的某個進程的執行,這種行為被稱為進程切換、任務切換或上下文切換。
### 硬件上下文:
盡管每個進程可以擁有屬于自己的地址空間,但所有進程必須共享CPU寄存器。因此,在恢復一個進程的執行之前,內核必須確保每個寄存器裝入了掛起進程時的值。
進程恢復執行前必須裝入寄存器的一組數據稱為硬件上下文。硬件上下文是進程可執行上下文的一個子集,因為可執行上下文包含進程執行時需要的所有信息。在Linux中,進程硬件上下文的一部分存放在任務狀態(TSS)段,而剩余部分存放在內核態堆棧中。進程切換只發生在內核態。
### 執行進程切換:
從本質上說,每個進程切換由兩步組成:
1、切換頁全局目錄以安裝一個新的地址空間
2、切換內核態堆棧和硬件上下文
### 創建進程:
Unix操作系統依賴進程創建來滿足用戶的需求。
現代Unix內核引入了三種不同機制解決進程創建問題:
1、寫時復制技術,允許子進程讀相同的物理頁。只要兩者中有一個試圖寫一個物理頁,內核就把這個頁的內容拷貝到一個新的物理頁,并把這個新的物理頁分配給正在寫的進程。
2、輕量級進程允許父子進程共享每個進程在內核的很多數據結構
3、vfork()系統調用創建的進程能共享其父進程的內存地址空間。為了防止父進程重寫子進程需要的數據,阻塞父進程的執行,一直到子進程退出或執行一個新的程序為止。
### Clone()、fork()及vfork()系統調用:
在Linux中,輕量級進程是由名為clone()的函數創建的。clone()是在C語言庫中定義的一個封裝函數,它負責建立新的輕量級進程的堆棧,并調用對編程者隱藏的clone()系統調用。
傳統的fork()以及vfork()系統調用在Linux中也是用clone()實現的。
do_fork()函數負責處理clone()、fork()和vfork()系統調用。
### 內核線程:
現代操作系統將一些重要的任務,如刷新磁盤高速緩存,交換出不用的頁框,維護網絡連接等,委托給內核線程,內核線程不受不必要的用戶態上下文拖累。
在Linux中,內核線程在以下幾方面不同于普通進程:
1、內核線程只運行在內核態,而普通進程既可以運行在內核態,也可以運行在用戶態。
2、因為內核線程只運行在內核態,它只使用大于PAGE_OFFSET的線性地址空間,而不管在用戶態還是內核態,普通進程可以使用4GB的線性地址空間。
kernel_thread()函數創建一個新的內核線程,它接受的參數有:所要執行的內核函數的地址、要傳遞給函數的參數、一組clone標志。該函數本質上調用do_fork()。
所有的進程的祖先叫做進程0,idle進程,或者因為歷史原因叫做swapper進程,它是在Linux初始化階段從無到有創建的一個內核線程。stark_kernel()函數初始化內核需要的所有數據結構,激活中斷,創建另一個叫進程1的內核線程(init進程)。新創建內核線程的PID為1,并與進程0共享進程所有的內核數據結構。
創建init進程后,進程0執行cpu_idle()函數,該函數本質上是在開中斷的情況下重復執行hlt匯編語言指令。只有當沒有其它進程處于TASK_RUNNING狀態時,調度程序才選擇進程0。
在多處理器系統中,每個CPU都有一個進程0.只要打開電源,計算機的BIOS就會啟動某一個CPU,同時禁用其它CPU。運行在CPU0上的swapper進程初始化內核數據結構,然后激活其它CPU,并通過copy_process()函數創建另外的swapper進程,把0傳遞給新創建的swapper進程作為它們的新PID。此外,內核把適當的CPU索引賦給內核所創建的每個進程的thread_info描述符的cpu字段。
由進程0創建的內核線程執行init()函數,init()依次完成內核初始化。init()調用execve()系統調用裝入可執行程序init,結果,init內核線程變為一個普通進程,且擁有自己的每進程內核數據結構。在系統關閉之前,init進程一直存活,因為它創建和監控在操作系統外層執行的所有進程的活動。
### 撤消進程:
進程終止的一般方式是調用exit()庫函數,該函數釋放C函數庫所分配的資源,執行編程者所注冊的每個函數,并結束從系統回收進程的那個系統調用。
### 進程終止:
在Linux2.6中有兩個終止用戶態應用的系統調用:
1、exit_group()系統調用,它終止整個線程組,即整個基于多線程的應用。do_group_exit()是實現這個系統調用的主要內核函數。
2、exit()系統調用,它終止某一個線程,而不管該線程所屬線程組中的所有其它進程。do_exit()是實現這個系統調用的主要內核函數。
### 進程刪除:
Unix允許進程查詢內核以獲得其父進程的PID,或者其任何子進程的執行狀態,包括終止狀態。因此,不允許Unix內核在進程一終止后就丟棄包含在進程描述符字段中的數據,只有父進程發出了與被終止進程相關的wait()類系統調用之后,才允許這樣做。這就是引入僵死狀態的原因:盡管從技術上來說進程已死,但必須保存它的描述符,直到父進程得到通知。
如果發生父進程在子進程結束之前結束的情況,系統中會到處是僵死的進程,而且它們的進程描述符會永久占據著RAM,因此,必須強迫所有的孤兒進程成為init進程的子進程。這樣,當init進程用wait()類系統調用檢查其合法的子進程終止時,就會撤消僵死進程。