天天看點

【Linux系統程式設計】特殊程序之僵屍程序

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;

}

測試結果:

【Linux系統程式設計】特殊程式之僵屍程式

我們另啟一個終端,檢視程序的狀态,有哪些是僵屍程序:

【Linux系統程式設計】特殊程式之僵屍程式

或者

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;

}

測試結果:

【Linux系統程式設計】特殊程式之僵屍程式

如果父程序不關心子程序什麼時候結束,那麼可以用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;

}

測試結果:

【Linux系統程式設計】特殊程式之僵屍程式

還有一些技巧,就是 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;

}

測試結果:

【Linux系統程式設計】特殊程式之僵屍程式

繼續閱讀