電腦裡的 CPU 數量是有限的,但同時想被執行的process 卻有很多。當OS想要從一個process切換到另一個process時,就會發生一件非常重要的事:保存與恢復行程的狀態。而這樣的一個切換過程,我們就稱呼他為上下文切換(Context Switch)。
具體而言,當CPU要從processA切到processB時:
Context Switch這個動作是必須,也是有代價的。因為他本身不做任何實際運算工作,就是只做「搬資料、切換角色」,所以他其實是一種系統開銷(overhead)。切換越頻繁,真正執行程式的效率反而下降。哪些因素會影響 Context Switch 的速度呢?
Program本身會產生process,而process之間也會彼此互動、產生關係。接下來,我們就要來談Process之間是如何管理、建立與終止這些Process。
在OS中,Process是可以產生其他Process的。為了方便區分,我們會把原先的Process稱為父行程(Parent Process),建立出來的稱為子行程(Child Process)。無論是父行程還是子行程,每個Process都會分配到一個獨立的 PID(Process ID),而這些父子行程之間的關係會形成一顆 行程樹(Process Tree)。
這邊以UNIX系統來說,便會有兩種指令常常被提到:
第一,fork():Process用來複製自己。
當一個Process呼叫fork(),它會產生一個幾乎一模一樣的子Process。這個子Process會有自己的 PID,但內容(程式碼、記憶體)會與父Process相同。而產生的子Process就會從fork() 後面那一行繼續各自執行。
第二,exec():切換程式內容
呼叫 exec() 的Process,會把自身的程式碼內容替換掉但 PID 不會變。
下圖表示了fork() , exec() 結合使用的範例:
人類上班最後一樣任務是打卡(然後繼續加班X),而當Process程完成任務後,要做的最後一件事就是呼叫 exit()。
exit()這個函數本身涵蓋一下的目的:
這邊請留意,當子行程在執行完結束後,會呼叫exit()告知結束。但這並不等於父行程有收到通知歐。
就像是你喜歡對方,跟對方知道你喜歡是兩件事一樣。還等啥,先別看了,趕緊去告白一波吧!!!
因此,OS提供wait()系統呼叫,讓父行程可以等待子行程結束。直到:
這種設計可以讓父行程知道子行程是否順利完成工作,或是否出錯。
在 UNIX / Linux 系統中,一個子行程執行完畢後,通常會呼叫 exit() 表示「任務完成」,並將控制權交還給作業系統。理論上來說,J個子行程就應該從OS中完全消失,但實際上並不會立刻清除。(沒錯,神奇吧
當子行程呼叫 exit() 結束時,OS會:
對,所以Process還是會保留著。如果父行程有正常呼叫 wait(),那J個空殼(?就會被清除,這邊稱為「回收」子行程。但如果父行程從頭到尾都沒呼叫 wait(),那麼這個子行程就會「身體不見了,但在系統中還留有一點殘留資料」,變成所謂的 殭屍行程(Zombie Process)。
第一,在一個棒棒的父行程邏輯中,父行程需要在適當時機呼叫 wait() 或 waitpid(),以確保回收所有子行程。
第二,讓 init(或 systemd)接管孤兒行程:若父行程提早終止,子行程會被系統接管。這些「孤兒行程」由 init 接手後,會負責正確回收,避免留下殭屍。
第三,可以使用非同步處理機制:在某些情況下,可以使用 signal(SIGCHLD) 搭配處理函數,自動在子行程結束時執行回收動作。