01. 僵屍程序概述
程序已運作結束,但程序的占用的資源未被回收,這樣的程序稱為僵屍程序。
在每個程序退出的時候,核心釋放該程序所有的資源、包括打開的檔案、占用的記憶體等。 但是仍然為其保留一定的資訊,這些資訊主要主要指程序控制塊的資訊(包括程序号、退出狀态、運作時間等)。直到父程序通過 wait() 或 waitpid() 來擷取其狀态并釋放。這樣就會導緻一個問題,如果程序不調用wait() 或 waitpid() 的話, 那麼保留的那段資訊就不會釋放,其程序号就會一直被占用,但是系統所能使用的程序号是有限的,如果大量的産生僵屍程序,将因為沒有可用的程序号而導緻系統不能産生新的程序.此即為僵屍程序的危害,應當避免。
子程序已運作結束,父程序未調用 wait() 或 waitpid() 函數回收子程序的資源是子程序變為僵屍程序的原因。
02. 僵屍程序案例
測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
pid_t pid = -1;
pid = fork();
if (-1 == pid)
{
perror("fork");
goto err0;
}
else if (0 == pid)
{
printf("I am child process %d\n", getpid());
exit(0);
}
sleep(200);
return 0;
err0:
return 1;
}
測試結果:
我們另啟一個終端,檢視程序的狀态,有哪些是僵屍程序:
或者
ps -ef | grep defunct ,後面尖括号裡是 defunct 的都是僵屍程序。
03. 避免僵屍程序
3.1 避免僵屍程序的方法
最簡單的方法,父程序通過 wait() 和 waitpid() 等函數等待子程序結束,但是,這會導緻父程序挂起。
如果父程序要處理的事情很多,不能夠挂起,通過 signal() 函數人為處理信号 SIGCHLD , 隻要有子程序退出自動調用指定好的回調函數,因為子程序結束後, 父程序會收到該信号 SIGCHLD ,可以在其回調函數裡調用 wait() 或 waitpid() 回收。關于信号的更詳細用法,請看《信号中斷處理》。
測試代碼:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
void sig_child(int signo)
{
pid_t pid;
//處理僵屍程序, -1 代表等待任意一個子程序, WNOHANG代表不阻塞
while( (pid = waitpid(-1, NULL, WNOHANG)) > 0 ){
printf("child %d terminated.\n", pid);
}
}
int main()
{
pid_t pid;
// 建立捕捉子程序退出信号
// 隻要子程序退出,觸發SIGCHLD,自動調用sig_child()
signal(SIGCHLD, sig_child);
pid = fork(); // 建立程序
if (pid < 0){ // 出錯
perror("fork error:");
exit(1);
}else if(pid == 0){ // 子程序
printf("I am child process,pid id %d.I am exiting.\n",getpid());
exit(0);
}else if(pid > 0){ // 父程序
sleep(2); // 保證子程序先運作
printf("I am father, i am exited\n\n");
system("ps -ef | grep defunct"); // 檢視有沒有僵屍程序
}
return 0;
}
測試結果:
如果父程序不關心子程序什麼時候結束,那麼可以用signal(SIGCHLD, SIG_IGN)通知核心,自己對子程序的結束不感興趣,父程序忽略此信号,那麼子程序結束後,核心會回收, 并不再給父程序發送信号。關于信号的更詳細用法。
測試代碼如下:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
pid_t pid;
// 忽略子程序退出信号的信号
// 那麼子程序結束後,核心會回收, 并不再給父程序發送信号
signal(SIGCHLD, SIG_IGN);
pid = fork(); // 建立程序
if (pid < 0){ // 出錯
perror("fork error:");
exit(1);
}else if(pid == 0){ // 子程序
printf("I am child process,pid id %d.I am exiting.\n",getpid());
exit(0);
}else if(pid > 0){ // 父程序
sleep(2); // 保證子程序先運作
printf("I am father, i am exited\n\n");
system("ps -ef | grep defunct"); // 檢視有沒有僵屍程序
}
return 0;
}
測試結果:
還有一些技巧,就是 fork() 兩次,父程序 fork() 一個子程序,然後繼續工作,子程序 fork() 一 個孫程序後退出,那麼孫程序被 init 接管,孫程序結束後,init (1 号程序)會回收。不過子程序的回收還要自己做。《UNIX環境進階程式設計》8.6節說的非常詳細。原理是将子程序成為孤兒程序,進而其的父程序變為 init 程序(1 号程序),通過 init 程序(1 号程序)可以處理僵屍程序。
測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
pid_t pid;
//建立第一個子程序
pid = fork();
if (pid < 0){ // 出錯
perror("fork error:");
exit(1);
}else if (pid == 0){//子程序
//子程序再建立子程序
printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
pid = fork();
if (pid < 0){
perror("fork error:");
exit(1);
}else if(pid == 0){ // 子程序
//睡眠3s保證下面的父程序退出,這樣目前子程序的父親就是 init 程序
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}else if (pid >0){ //父程序退出
printf("first procee is exited.\n");
exit(0);
}
}else if(pid > 0){ // 父程序
// 父程序處理第一個子程序退出,回收其資源
if (waitpid(pid, NULL, 0) != pid){
perror("waitepid error:");
exit(1);
}
exit(0);
}
return 0;
}
測試結果: