僵尸进程的产生原因和避免方法

  • Post author:
  • Post category:其他




僵尸进程的产生:

当一个进程创建了一个子进程时,他们的运行时异步的。即父进程无法预知子进程会在什么时候结束,那么如果父进程很繁忙来不及wait 子进程时,那么当子进程结束时,会不会丢失子进程的结束时的状态信息呢?处于这种考虑unix提供了一种机制可以保证只要父进程想知道子进程结束时的信息,它就可以得到。

这种机制是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存。但是仍然保留了一些信息(如进程号pid 退出状态 运行时间等)。这些保留的信息直到进程通过调用wait/waitpid时才会释放。这样就导致了一个问题,如果没有调用wait/waitpid的话,那么保留的信息就不会释放。比如进程号就会被一直占用了。但系统所能使用的进程号的有限的,如果产生大量的僵尸进程,将导致系统没有可用的进程号而导致系统不能创建进程。所以我们应该避免僵尸进程

这里有一个需要注意的地方。如果子进程先结束而父进程后结束,即子进程结束后,父进程还在继续运行但是并未调用wait/waitpid那子进程就会成为僵尸进程。

但如果子进程后结束,即父进程先结束了,但没有调用wait/waitpid来等待子进程的结束,此时子进程还在运行,父进程已经结束。那么并不会产生僵尸进程。应为每个进程结束时,系统都会扫描当前系统中运行的所有进程,看看有没有哪个进程时刚刚结束的这个进程的子进程,如果有,就有init来接管它,成为它的父进程。

同样的在产生僵尸进程的那种情况下,即子进程结束了但父进程还在继续运行(并未调用wait/waitpid)这段期间,假如父进程异常终止了,那么该子进程就会自动被init接管。那么它就不再是僵尸进程了。应为intit会发现并释放它所占有的资源。(当然如果进程表越大,init发现它接管僵尸进程这个过程就会变得越慢,所以在init为发现他们之前,僵尸进程依旧消耗着系统的资源)



我们先来讨论 父进程先结束的情况:

比如这段代码。我们让子进程循环打印5次语句 父进程循环打印3次语句。并在父进程中调用wait()等待子进程的结束//


  1. #include<stdio.h>

  2. #include<stdlib.h>

  3. #include<unistd.h>

  4. #include<sys/types.h>

  5. #include<sys/wait.h>

  6. int

    main()
  7. {

  8. int

    count;
  9. pid_t pid;

  10. char

    *message;
  11. printf(

    “fork program starting\n”

    );
  12. pid=fork();

  13. switch

    (pid)
  14. {

  15. case

    -1:perror(

    “forkerror”

    );
  16. exit(EXIT_FAILURE);

  17. break

    ;

  18. case

    0 :message=

    “This isthe children”

    ;
  19. count=10;

  20. break

    ;

  21. default

    :message=

    “This isthe parent.”

    ;
  22. count=3;

  23. break

    ;
  24. }

  25. for

    (;count>0;count–)
  26. {
  27. printf(

    “%s\n”

    ,message);
  28. sleep(1);
  29. }

  30. if

    (pid)
  31. wait((

    int

    *)0);

  32. if

    (pid)
  33. printf(

    “Father programDone.\n”

    );

  34. else
  35. printf(

    “Child ProgramDnoe\n”

    );
  36. exit(0);
  37. }
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int 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是27324

Ps 的输出中我们看到了他有一个2735的子进程,

父进程循环三次后并不会结束,而是等待子进程结束后再结束。

这里并未产生僵尸进程

如果我们不等带子进程的结束




if(pid)





wait((int *)0)


注释掉

将产生如下输出

从第一行我们看到我们运行的程序pid为2804

Ps输出中的pid为2805 是创建的子进程。我们是在父进程结束后(未调用wait,所以父进程先结束)再用ps命令查看的。所以2805的父进程变成了1 (init 的pid),因为2804已经结束了,所以2805这个子进程被 init接管,同样这里并未产生僵尸进程



现在我们来分析子进程后结束的情况:

我们  给出下面这个程序


  1. #include<stdio.h>

  2. #include<stdlib.h>

  3. #include<unistd.h>

  4. #include<sys/types.h>

  5. int

    main()
  6. {

  7. int

    count;

  8. char

    *message;
  9. pid_t pid;
  10. pid=fork();

  11. switch

    (pid)
  12. {

  13. case

    -1:
  14. perror(

    “forkerror”

    );
  15. exit(EXIT_FAILURE);

  16. case

    0:message=

    “This isthe child.”

    ;
  17. count=5;

  18. break

    ;

  19. default

    :message=

    “This isth parent.”

    ;
  20. count=10;

  21. break

    ;
  22. }

  23. for

    (;count>0;count–)
  24. {
  25. printf(

    “%s\n”

    ,message);
  26. sleep(2);
  27. }

  28. if

    (pid)
  29. printf(

    “Father programDone.\n”

    );

  30. else
  31. printf(

    “Child prigramDone\n”

    );
  32. exit(EXIT_SUCCESS);
  33. }
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
 
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回收子进程的资源

这里给出一个例程


  1. #include<sys/wait.h>

  2. #include<stdio.h>

  3. #include<stdlib.h>

  4. #include<unistd.h>

  5. #include<sys/types.h>

  6. #include<signal.h>

  7. void

    fun_act(

    int

    sig)
  8. {
  9. wait((

    int

    *)0);
  10. }

  11. int

    main()
  12. {

  13. int

    count;

  14. char

    *message;
  15. pid_t pid;

  16. struct

    sigaction act;
  17. act.sa_handler=fun_act;
  18. sigemptyset(&act.sa_mask);
  19. act.sa_flags=0;
  20. pid=fork();

  21. switch

    (pid)
  22. {

  23. case

    -1:
  24. perror(

    “forkerror”

    );
  25. exit(EXIT_FAILURE);

  26. case

    0:message=

    “This isthe child.”

    ;
  27. count=5;

  28. break

    ;

  29. default

    :message=

    “This isth parent.”

    ;
  30. count=10;

  31. break

    ;
  32. }

  33. if

    (pid)

  34. if

    (sigaction(SIGCHLD,&act,0)==-1)
  35. {
  36. perror(

    “Settingsignal failed.”

    );
  37. exit(1);
  38. }

  39. for

    (;count>0;count–)
  40. {
  41. printf(

    “%s\n”

    ,message);
  42. sleep(1);
  43. }

  44. if

    (pid)
  45. printf(

    “Father programDone.\n”

    );

  46. else
  47. printf(

    “Child prigramDone\n”

    );
  48. exit(EXIT_SUCCESS);
  49. }
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void 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);
 
}


我们在子进程结束前 用 ps 查看了一次结束后也查看了一次。

从输出我们看到,pid为2949的子进程正常结束了,并未产生僵尸进程。说明子进程结束后,父进程收到了它结束的消息,并调用了wait回收了子进程的资源。从而避免了僵尸进程的产生。