行程樹與 init/PID 1
因為每個行程都是由另一個行程建立的(透過 fork),行程自然構成一棵家族樹,而非一群平坦的散兵。每個行程恰好有一個父行程、可以有許多子行程,那些子行程又有子行程,依此類推——一條分岔的族譜。在 Linux 上執行 pstree,你會真的看到它被畫出來:頂端一個根,枝幹向每個執行中的行程散開。這個形狀就是行程樹,是系統用來組織所有運行中事物的骨幹。
在樹的最根部坐著一個特別的行程:init,它的 PID 永遠是 1。它是核心在開機時啟動的第一個使用者空間行程,其他每個行程都源自它。在現代 Linux 上這個角色通常由 systemd 擔任;經典上則是一個就叫 init 的程式。PID 1 有兩個獨有的職責。第一,它引導並監督整個系統——啟動服務、掛載檔案系統、讓機器活起來。第二,也是對這個欄位至關重要的:它是全體的收養者——當任何行程在仍有存活子行程時死去,那些子行程就成了孤兒,核心會把它們重新指派給 PID 1。init 接著在它們結束時盡責地等待、收割它們,使它們不致變成永久的殭屍。PID 1 還有一個特別之處:若它哪天死了,核心會恐慌(panic)——整個系統沒了它的根就無法繼續。
為何重要:這棵樹解釋了重新指派父行程(孤兒永遠在 PID 1 有個家)、殭屍清理(init 收割沒人要的子行程),以及信號/權限透過行程群組的傳播。這也是為什麼容器需要一個類 init 的行程:若你在容器裡當 PID 1 跑的程式不收割它收養的孤兒,殭屍就會堆積。知道這棵樹存在——以及 PID 1 接住每個墜落者——能把一堆令人困惑的行程,變成一個你能推理的有組織階層。
pstree -p 顯示這個階層:根部是 systemd(1),接著 sshd(900),再來你的登入 shell bash(1500),然後你剛啟動的 python3(1733)。殺掉 bash,python3 就成了孤兒;它的 PPID 變成 1,因為核心把它重新指派給了 init。
每個行程都源自 PID 1;當父行程死去,它的子行程被重新指派給 init。
在容器內,你執行的程式成為該命名空間的 PID 1,並繼承 init 的收割職責。若它忽略這點,被收養的孤兒就會變成不斷累積的殭屍——這正是為什麼有極簡的 init 包裝程式(tini 之類)存在。