做者:hanover網絡
連接:Defunct進程殭屍進程異步
在測試基於 DirectFB+Gstreamer 的視頻聯播系統的一個 Demo 的時候,其中大量使用 system 調用的語句,例如在 menu 代碼中的 system("./play") ,並且屢次執行,這種狀況下,在 ps -ef 列表中出現了大量的 defunct 進程,對程序的運行時有害的。按說system的源碼中應該已經包含了wait,但也不能排除開發板上這個版本的system中可能沒有wait,總之,開發板上在調用system後添加wait以後,defunct進程不復存在了。函數
下面談談 defunct 進程,中文翻譯叫殭屍進程。下文整理於網絡以及APUE一書。性能
1、什麼是殭屍進程測試
在UNIX 系統中,一個進程結束了,可是他的父進程沒有等待(調用wait / waitpid)他,那麼他將變成一個殭屍進程。當用ps命令觀察進程的執行狀態時,看到這些進程的狀態欄爲defunct。殭屍進程是一個早已死亡的進程,但在進程表(processs table)中仍佔了一個位置(slot)。spa
可是若是該進程的父進程已經先結束了,那麼該進程就不會變成殭屍進程。由於每一個進程結束的時候,系統都會掃描當前系統中所運行的全部進程,看看有沒有哪一個進程是剛剛結束的這個進程的子進程,若是是的話,就由Init進程來接管他,成爲他的父進程,從而保證每一個進程都會有一個父進程。而Init進程會自動wait其子進程,所以被Init接管的全部進程都不會變成殭屍進程。翻譯
2、UNIX下進程的運做方式設計
每一個Unix進程在進程表裏都有一個進入點(entry),核心進程執行該進程時使用到的一切信息都存儲在進入點。當用 ps 命令察看系統中的進程信息時,看到的就是進程表中的相關數據。當以fork()系統調用創建一個新的進程後,核心進程就會在進程表中給這個新進程分配一個進入點,而後將相關信息存儲在該進入點所對應的進程表內。這些信息中有一項是其父進程的識別碼。code
子進程的結束和父進程的運行是一個異步過程,即父進程永遠沒法預測子進程到底何時結束。那麼會不會由於父進程太忙來不及 wait 子進程,或者說不知道子進程何時結束,而丟失子進程結束時的狀態信息呢?視頻
不會。由於UNIX提供了一種機制能夠保證,只要父進程想知道子進程結束時的狀態信息,就能夠獲得。這種機制就是:當子進程走完了本身的生命週期後,它會執行exit()系統調用,內核釋放該進程全部的資源,包括打開的文件,佔用的內存等。可是仍然爲其保留必定的信息(包括進程號the process ID,退出碼exit code,退出狀態the terminationstatus of the process,運行時間the amount of CPU time taken by the process等),這些數據會一直保留到系統將它傳遞給它的父進程爲止,直到父進程經過wait / waitpid來取時才釋放。
也就是說,當一個進程死亡時,它並非徹底的消失了。進程終止,它再也不運行,可是還有一些殘留的數據等待父進程收回。當父進程 fork() 一個子進程後,它必須用 wait() (或者 waitpid())等待子進程退出。正是這個 wait() 動做來讓子進程的殘留數據消失。
3、殭屍進程的危害
若是父進程不調用wait / waitpid的話,那麼保留的那段信息就不會釋放,其進程號就會一直被佔用,可是系統的進程表容量是有限的,所能使用的進程號也是有限的,若是大量的產生殭屍進程,將由於沒有可用的進程號而致使系統不能產生新的進程。
因此,defunct進程不只佔用系統的內存資源,影響系統的性能,並且若是其數目太多,還會致使系統癱瘓。並且,因爲調度程序沒法選中Defunct 進程,因此不能用kill命令刪除Defunct 進程,唯一的方法只有重啓系統。
4、殭屍進程的產生
若是子進程死亡時父進程沒有 wait(),一般用 ps 能夠看到它被顯示爲「<defunct>」,這樣就產生了殭屍進程。它將永遠保持這樣直到父進程 wait()。
當上述程序之後臺的方式執行時,第17行強迫程序睡眠20秒,讓用戶有時間輸入ps -e指令,觀察進程的狀態,咱們看到進程表中出現了defunct進程。當父進程執行終止後,再用ps -e命令觀察時,咱們會發現defunct進程也隨之消失。這是由於父進程終止後,init 進程會接管父進程留下的這些「孤兒進程」(orphan process),而這些「孤兒進程」執行完後,它在進程表中的進入點將被刪除。若是一個程序設計上有缺陷,就可能致使某個進程的父進程一直處於睡眠狀態或是陷入死循環,父進程沒有wait子進程,也沒有終止以使Init接管,該子進程執行結束後就變成了defunct進程,這個defunct 進程可能會一直留在系統中直到系統從新啓動。
在看一個產生殭屍進程的例子。
子進程要執行的程序test_prog
父進程father的代碼father.c
執行./father,當子進程退出後,因爲父進程沒有對它的退出進行關注,會出現殭屍進程
總結:子進程成爲 defunct 直到父進程 wait(),除非父進程忽略了 SIGCLD 。更進一步,父進程沒有 wait() 就消亡(仍假設父進程沒有忽略 SIGCLD )的子進程(活動的或者 defunct)成爲 init 的子進程,init 着手處理它們。
5、如何避免殭屍進程
一、父進程經過wait和waitpid等函數等待子進程結束,這會致使父進程掛起。
在上個例子中,若是咱們略做修改,在第8行sleep()系統調用前執行wait()或waitpid()系統調用,則子進程在終止後會當即把它在進程表中的數據返回給父進程,此時系統會當即刪除該進入點。在這種情形下就不會產生defunct進程。
2. 若是父進程很忙,那麼能夠用signal函數爲SIGCHLD安裝handler。在子進程結束後,父進程會收到該信號,能夠在handler中調用wait回收。
3. 若是父進程不關心子進程何時結束,那麼能夠用signal(SIGCLD, SIG_IGN)或signal(SIGCHLD, SIG_IGN)通知內核,本身對子進程的結束不感興趣,那麼子進程結束後,內核會回收,並再也不給父進程發送信號
4. fork兩次,父進程fork一個子進程,而後繼續工做,子進程fork一個孫進程後退出,那麼孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收還要本身作。 下面就是Stevens給的採用兩次folk避免殭屍進程的示例: