經典面試題:孤兒進程、僵尸進程、守護進程詳解
孤兒進程、僵尸進程、守護進程,是非常常見的 Linux 面試題。本文就來詳細介紹下這三個進程的定義和區別。
孤兒進程
孤兒進程是指其父進程已經終止或不存在,但是該進程仍在繼續運行的進程。
如果父進程先退出,子進程還沒退出那么子進程將被托孤給 init 進程,這時子進程的父進程就是 init 進程(1 號進程)。init 進程完成所有孤兒進程的狀態收集工作。即每當出現一個孤兒進程的時候,內核就把孤兒進程的父進程設置為 init,而 init 進程會循環地 wait() 它的已經退出的子進程。這樣,當一個孤兒進程凄涼地結束了其生命周期的時候,init 進程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進程并不會有什么危害。
僵尸進程
僵尸進程是指一個子進程已經終止,但其父進程尚未調用 wait() 或 waitpid() 系統調用來獲取子進程的終止狀態,導致子進程的進程描述符仍然存在,此時子進程將成為一個僵尸進程。
一個進程在調用 exit 命令結束自己的生命的時候,其實它并沒有真正的被銷毀, 而是留下一個稱為僵尸進程(Zombie)的數據結構(系統調用 exit,它的作用是使進程退出,但也僅僅限于將一個正常的進程變成一個僵尸進程,并不能將其完全銷毀)
在 Linux 進程的狀態中,僵尸進程是非常特殊的一種,它已經放棄了幾乎所有內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,僵尸進程不再占有任何內存空間。它需要它的父進程來為它收尸,如果他的父進程沒安裝 SIGCHLD 信號處理函數調用 wait 或 waitpid() 等待子進程結束,又沒有顯式忽略該信號,那么它就一直保持僵尸狀態,如果這時父進程結束了,那么 init 進程自動會接手這個子進程,為它收尸,它還是能被清除的。但是如果父進程是一個循環,不會結束,那么子進程就會一直保持僵尸狀態,這就是為什么系統中有時會有很多的僵尸進程。
系統所能使用的進程號是有限的,如果大量的產生僵尸進程,將因為沒有可用的進程號而導致系統不能產生新的進程。 此即為僵尸進程的危害,應當避免。
(1) 子進程結束后為什么要進入僵尸狀態?
因為父進程可能要取得子進程的退出狀態等信息。
(2) 僵尸狀態是每個子進程必經的狀態嗎?
是的。任何一個子進程(init 除外)在 exit() 之后,并非馬上就消失掉,而是留下一個稱為僵尸進程(Zombie)的數據結構,等待父進程處理。這是每個子進程在結束時都要經過的階段。如果子進程在 exit()之后,父進程沒有來得及處理,這時用 ps 命令就能看到子進程的狀態是“Z”。如果父進程能及時 處理,可能用 ps 命令就來不及看到子進程的僵尸狀態,但這并不等于子進程不經過僵尸狀態。如果父進程在子進程結束之前退出,則子進程將由 init 接管。init 將會以父進程的身份對僵尸狀態的子進程進行處理。
(3) 如何查看僵尸進程?
ps -el 其中,有標記為 Z 的進程就是僵尸進程 S 代表休眠狀態;D 代表不可中斷的休眠狀態;R 代表運行狀態;Z 代表僵死狀態;T 代表停止或跟蹤狀態。
在 fork() / execve() 過程中,假設子進程結束時父進程仍存在,而父進程 fork() 之前既沒安裝 SIGCHLD 信號處理函數調用 waitpid() 等待子進程結束,又沒有顯式忽略該信號,則子進程成為僵尸進程,無法正常結束,此時即使是 root 身份 kill -9 也不能殺死僵尸進程。
(4) 如何解決僵尸進程?
殺死僵尸進程的父進程(僵尸進程的父進程必然存在),僵尸進程成為"孤兒進程",過繼給 1 號進程 init,init 始終會負責清理僵尸進程。
守護進程
守護進程( daemon) 是指在后臺運行,沒有控制終端與之相連的進程。它獨立于控制終端,通常周期性地執行某種任務 。 守護進程脫離于終端是為了避免進程在執行過程中的信息在任何終端上顯示并且進程也不會被任何終端所產生的終端信息所打斷。
總結
- 孤兒進程是父進程終止后仍在運行的進程,由init進程接管管理;
- 僵尸進程是子進程已經終止但父進程未處理其終止狀態的進程,會占用系統資源;
- 守護進程是在后臺運行的獨立進程,通常用于執行系統任務或服務。