僵屍進程的產生:
當一個進程創建了一個子進程時,他們的運行時異步的。即父進程無法預知子進程會在什麼時候結束,那麼如果父進程很繁忙來不及wait 子進程時,那麼當子進程結束時,會不會丟失子進程的結束時的狀態信息呢?處於這種考慮unix提供了一種機制可以保證只要父進程想知道子進程結束時的信息,它就可以得到。
這種機制是:在每個進程退出的時候,內核釋放該進程所有的資源,包括打開的文件,占用的內存。但是仍然保留了一些信息(如進程號pid 退出狀態 運行時間等)。這些保留的信息直到進程通過調用wait/waitpid時才會釋放。這樣就導致了一個問題,如果沒有調用wait/waitpid的話,那麼保留的信息就不會釋放。比如進程號就會被一直占用了。但系統所能使用的進程號的有限的,如果產生大量的僵屍進程,將導致系統沒有可用的進程號而導致系統不能創建進程。所以我們應該避免僵屍進程
這裡有一個需要注意的地方。如果子進程先結束而父進程後結束,即子進程結束後,父進程還在繼續運行但是並未調用wait/waitpid那子進程就會成為僵屍進程。
但如果子進程後結束,即父進程先結束了,但沒有調用wait/waitpid來等待子進程的結束,此時子進程還在運行,父進程已經結束。那麼並不會產生僵屍進程。應為每個進程結束時,系統都會掃描當前系統中運行的所有進程,看看有沒有哪個進程時剛剛結束的這個進程的子進程,如果有,就有init來接管它,成為它的父進程。
同樣的在產生僵屍進程的那種情況下,即子進程結束了但父進程還在繼續運行(並未調用wait/waitpid)這段期間,假如父進程異常終止了,那麼該子進程就會自動被init接管。那麼它就不再是僵屍進程了。應為intit會發現並釋放它所占有的資源。(當然如果進程表越大,init發現它接管僵屍進程這個過程就會變得越慢,所以在init為發現他們之前,僵屍進程依舊消耗著系統的資源)
我們先來討論 父進程先結束的情況:
比如這段代碼。我們讓子進程循環打印5次語句 父進程循環打印3次語句。並在父進程中調用wait()等待子進程的結束//
#include#include#include#include#includeint main(){ int count; pid_t pid; char *message; printf("fork program starting\n"); pid=fork(); switch(pid) { case -1:perror("forkerror"); exit(EXIT_FAILURE); break; case 0 :message="This isthe children"; count=10; break; default:message="This isthe parent."; count=3; break; } for(;count>0;count--) { printf("%s\n",message); sleep(1); } if(pid) wait((int *)0); if(pid) printf("Father programDone.\n"); else printf("Child ProgramDnoe\n"); exit(0); }
我們讓程序在後台運行,並用ps命令查看進程狀態。
我們從輸出中看到
第一行顯示了我們運行的進程pid是2734
Ps 的輸出中我們看到了他有一個2735的子進程,
父進程循環三次後並不會結束,而是等待子進程結束後再結束。
這裡並未產生僵屍進程
如果我們不等帶子進程的結束
將if(pid)
wait((int *)0) 注釋掉
將產生如下輸出
從第一行我們看到我們運行的程序pid為2804
Ps輸出中的pid為2805 是創建的子進程。我們是在父進程結束後(未調用wait,所以父進程先結束)再用ps命令查看的。所以2805的父進程變成了1 (init 的pid),因為2804已經結束了,所以2805這個子進程被 init接管,同樣這裡並未產生僵屍進程
現在我們來分析子進程後結束的情況:
我們 給出下面這個程序
#include#include#include#include int main(){ int count; char *message; pid_t pid; pid=fork(); switch(pid) { case -1: perror("forkerror"); exit(EXIT_FAILURE); case 0:message="This isthe child."; count=5; break; default:message="This isth parent."; count=10; break; } for(;count>0;count--) { printf("%s\n",message); sleep(2); } if(pid) printf("Father programDone.\n"); else printf("Child prigramDone\n"); exit(EXIT_SUCCESS); }
並且在父進程中我們不調用wait/waitpid來釋放子進程的一些信息
在子進程結束,但父進程還未結束時我們查看進程信息
第一行我們看到 我們運行的程序pid 是2874,它的子進程我們可以從ps輸出中看到為2875
我們注意到pid為2875的進程這時候成了僵屍進程。如果主線程運行的時間足夠長,那麼該僵屍進程就會一直存在著並占用著系統的一些資源。
我們已盡知道了僵屍進程的產生原因,那麼如何避免僵屍進程呢。
如果父進程並不是很繁忙我們就可以通過直接調用wait/waitpid來等待子進程的結束。當然這會導致父進程被掛起。比如第一種情況中(父進程循環了三次,子進程循環了五次,子進程先結束,父進程調用wait等待子進程)父進程循環結束後並不會結束,而是被掛起等待子進程的結束。
但是如果父進程很忙。我們不希望父進程一直被掛起直到子進程的結束
那麼我們可以使用信號函數sigaction為SIGCHLD設置wait處理函數。這樣子進程結束後,父進程就會收到子進程結束的信號。並調用wait回收子進程的資源
這裡給出一個例程
#include#include#include#include#include#includevoid fun_act(int sig){ wait((int *)0);}int main(){ int count; char *message; pid_t pid; struct sigaction act; act.sa_handler=fun_act; sigemptyset(&act.sa_mask); act.sa_flags=0; pid=fork(); switch(pid) { case -1: perror("forkerror"); exit(EXIT_FAILURE); case 0:message="This isthe child."; count=5; break; default:message="This isth parent."; count=10; break; }if(pid) if(sigaction(SIGCHLD,&act,0)==-1) { perror("Settingsignal failed."); exit(1); } for(;count>0;count--) { printf("%s\n",message); sleep(1); } if(pid) printf("Father programDone.\n"); else printf("Child prigramDone\n"); exit(EXIT_SUCCESS); }
從輸出我們看到,pid為2949的子進程正常結束了,並未產生僵屍進程。說明子進程結束後,父進程收到了它結束的消息,並調用了wait回收了子進程的資源。從而避免了僵屍進程的產生。