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

分类: linux2013-01-14 22:11 1867人阅读 评论(0) 收藏 举报

linux进程和信号 僵尸进程unixUNIXUnix

僵尸进程的产生:

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

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

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

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

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

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

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

[cpp] view plaincopyprint?

  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. }

我们让程序在后台运行,并用ps命令查看进程状态。

我们从输出中看到

第一行显示了我们运行的进程pid是27324

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

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

这里并未产生僵尸进程

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

 if(pid)  

wait((int *)0)   注释掉

将产生如下输出

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

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

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

我们  给出下面这个程序

[cpp] view plaincopyprint?

  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. }

这里的代码改动很小,我们只是改变了父进程和子进程的 打印次数

并且在父进程中我们不调用wait/waitpid来释放子进程的一些信息

在子进程结束,但父进程还未结束时我们查看进程信息

第一行我们看到 我们运行的程序pid 是2874,它的子进程我们可以从ps输出中看到为2875

我们注意到pid为2875的进程这时候成了僵尸进程。如果主线程运行的时间足够长,那么该僵尸进程就会一直存在着并占用着系统的一些资源。

我们已尽知道了僵尸进程的产生原因,那么如何避免僵尸进程呢

如果父进程并不是很繁忙我们就可以通过直接调用wait/waitpid来等待子进程的结束。当然这会导致父进程被挂起。比如第一种情况中(父进程循环了三次,子进程循环了五次,子进程先结束,父进程调用wait等待子进程)父进程循环结束后并不会结束,而是被挂起等待子进程的结束。

但是如果父进程很忙。我们不希望父进程一直被挂起直到子进程的结束

那么我们可以使用信号函数sigaction为SIGCHLD设置wait处理函数。这样子进程结束后,父进程就会收到子进程结束的信号。并调用wait回收子进程的资源

这里给出一个例程

[cpp] view plaincopyprint?

  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. }

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

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

转至:http://blog.csdn.net/feng574912883/article/details/8502667

时间: 2024-10-13 01:40:06

僵尸进程的产生原因和避免方法的相关文章

linux僵尸进程产生的原因以及如何避免产生僵尸进程

给 进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取.这些信息包括子进程的进程ID.终止状态以及资源利用信息(CPU时间,内存 使用量等等).如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程).继承这些子进程的 init进程将清理它们(init进程将wait它们,从而去除僵尸状态). 但通常情况下,我们是不愿意留存僵尸进程的,它们占用内核中的空间,最终可能导致我们耗尽进程资源.那么为什么会产生僵尸进程以及如何避免产

僵尸进程产生的原因及如何避免

如果子进程先于父进程退出, 同时父进程又没有调用wait/waitpid,则该子进程将成为僵尸进程.通过ps命令,我们可以看到该进程的状态为Z(表示僵死). 一般,为了防止产生僵尸进程,在fork子进程之后我们都要wait它们:同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的. 正确的解决办法是调用waitpid而不是wait,这个

Linux 僵尸进程查杀

僵尸进程概念 僵尸进程(Zombie process)通俗来说指那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸. 书面形式一点:一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程.通过ps命令查看其带有defunct的标志.僵尸进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot). 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程.因为每个进程结束的时候,系统都会扫描

第十九篇:处理僵尸进程的两种经典方法

前言 如果父进程没有结束,而子进程终止了.那么在父进程调用 wait 函数回收这个子进程或者父进程终止以前,这个子进程将一直是僵尸进程. 本文将提供两种方法处理这个问题. 方法一:父进程回收法 wait函数将使其调用者阻塞,直到其某个子进程终止.故父进程可调用wait函数回收其僵尸子进程.除此之外,waitpid函数提供更为详尽的功能( 增加了非阻塞功能以及指定等待功能 ),请读者自行查阅相关资料. 代码实现 1 #include <unistd.h> 2 #include <sys/w

僵尸进程处理方法

僵尸进程:本质是进程描述符task_struct; 维护子进程的状态,包括子进程ID,终止状态以及进程的资源利用情况(cpu时间,内存) int  wait(int*stat_loc):成功之后返回终止子进程的pid,失败返回-1,并设置errno 1.wait调用堵塞进程直到有任一一个子进程终止,则立刻返回,返回值为此终止进程的pid 2.如果wait调用的时候,有多个字进程终止,wait()选择任一一个子进程,通过pid识别 3. 如果调用进程没有子进程,调用就会失败,此时wait返回-1,

python全栈脱产第34天------开启进程的两种方式、join方法、进程对象其他相关的属性和方法、僵尸进程、孤儿进程、守护进程、互斥锁

一.开启进程的两种方式 方式一: from multiprocessing import Processimport time def task(name): print('%s is running' %name) time.sleep(3) print('%s is done' %name) # 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中if __name__ == '__main__': p=Process(target=t

僵尸进程和孤儿进程----概念

这里仅给出僵尸进程和孤儿进程的概念.来源<深入了解计算机系统(原书第3版)>和网上的博客,下面会给出博客来源. 前言:回收子进程 当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除.相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped).当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程.从此时开始,该进程就不存在了. 1.基本概念 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitp

一起聊聊 Linux 系统中的僵尸进程

Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动.僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸.僵尸进程如何产生的?如果一个进程在其终止的时候,自己就回收所有分配给它的资源,系统就不会产生所谓的僵尸进程了.那么我们说一个进程终止之后,还保留哪些信息?为什么终止之后还需要保留这些信息呢?一个进程终止的方法很多,进程终止后有些信息对于父进程和内核还是很有用的,例如进程的ID号.进程的退出状态.进程运行的

UNIX高级环境编程(9)进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid

本章包含内容有: 创建新进程 程序执行(program execution) 进程终止(process termination) 进程的各种ID ? 1 进程标识符(Process Identifiers) 每个进程都有一个唯一的标识符,进程ID(process ID). 进程的ID是可重用的,如果一个进程被终止,那么它的进程ID会被系统回收,但是会延迟使用,防止该进程ID标识的新进程被误认为是以前的进程. 三个特殊ID的进程: Process ID 0:调度者进程,内核进程. Process