讲师博文
孤儿进程和僵尸进程 来源 : 未知     2018-08-15

前段时间,由于研究经典面试题,把孤儿进程和僵尸进程也总结了一下。

我们有这样一个问题:孤儿进程和僵尸进程,怎么产生的?有什么危害?怎么去预防?

下面是针对此问题的总结与概括。

一.产生的原因

1) 一般进程

正常情况下:子进程由父进程创建,子进程再创建新的进程。父子进程是一个异步过程,父进程永远无法预测子进程的结束,所以,当子进程结束后,它的父进程会调用wait()或waitpid()取得子进程的终止状态,回收掉子进程的资源。

2)孤儿进程

孤儿进程:父进程结束了,而它的一个或多个子进程还在运行,那么这些子进程就成为孤儿进程(father died)。子进程的资源由init进程(进程号PID = 1)回收。

3)僵尸进程

僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。

二.问题危害

注意:unix提供了一种机制保证父进程知道子进程结束时的状态信息。

这种机制是:在每个进程退出的时候,内核会释放所有的资源,包括打开的文件,占用的内存等。但是仍保留一部分信息(进程号PID,退出状态,运行时间等)。直到父进程通过wait或waitpid来取时才释放。

但是这样就会产生问题:如果父进程不调用wait或waitpid的话,那么保留的信息就不会被释放,其进程号就会被一直占用,但是系统所能使用的进程号是有限的,如果大量产生僵死进程,将因没有可用的进程号而导致系统无法产生新的进程,这就是僵尸进程的危害

孤儿进程是没有父进程的进程,它由init进程循环的wait()回收资源,init进程充当父进程。因此孤儿进程并没有什么危害。

补充:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程去处理。如果父进程在子进程exit()之后,没有及时处理,出现僵尸进程,并可以用ps命令去查看,它的状态是“Z”。

三.解决方案

1)kill杀死元凶父进程(一般不用)

严格的说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵死进程的父进程。因此,我们可以直接除掉元凶,通过kill发送SIGTERM或者SIGKILL信号。元凶死后,僵尸进程进程变成孤儿进程,由init充当父进程,并回收资源。

或者运行:kill -9 父进程的pid值、

2)父进程用wait或waitpid去回收资源(方案不好)

父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。

3)通过信号机制,在处理函数中调用wait,回收资源

通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待。

例子1:

#include

#include

#include

#include

#include

static void sig_child(int signo);

int main()

{

pid_t pid;

//创建捕捉子进程退出信号

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);

}

printf("I am father process.I will sleep two seconds\n");

//等待子进程先退出

sleep(2);

//输出进程信息

system("ps -o pid,ppid,state,tty,command");

printf("father process is exiting.\n");

return 0;

}

static void sig_child(int signo)

{

pid_t pid;

int stat;

//处理僵尸进程

while ((pid = waitpid(-1, &stat, WNOHANG)) >0)

printf("child %d terminated.\n", pid);

}

4)fork两次

fork两次,父进程fork一个子进程,子进程在fork出一个孙子进程,然后子进程立马退出,并由父进程去wait回收,这个过程不需要等待,然后父进程可以去干其他的活。孙子进程因为子进程退出会成为孤儿进程,那它可以由init充当父进程,并回收。这样父进程和孙子进程就可以同时干活,互不影响,就实现了多进程。

例子2:

#include

#include

#include

#include

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)

{

printf("first procee is exited.\n");

exit(0);

}

//第二个子进程

//睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里

sleep(3);

printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());

exit(0);

}

//父进程处理第一个子进程退出

if (waitpid(pid, NULL, 0) != pid)

{

perror("waitepid error:");

exit(1);

}

exit(0);

return 0;

}

四.补充测试程序

1)孤儿进程测试程序

#include

#include

#include

#include

int main()

{

pid_t pid;

//创建一个进程

pid = fork();

//创建失败

if (pid < 0)

{

perror("fork error:");

exit(1);

}

//子进程

if (pid == 0)

{

printf("I am the child process.\n");

//输出进程ID和父进程ID

printf("pid: %d\tppid:%d\n",getpid(),getppid());

printf("I will sleep five seconds.\n");

//睡眠5s,保证父进程先退出

sleep(5);

printf("pid: %d\tppid:%d\n",getpid(),getppid());

printf("child process is exited.\n");

}

//父进程

else

{

printf("I am father process.\n");

//父进程睡眠1s,保证子进程输出进程id

sleep(1);

printf("father process is exited.\n");

}

return 0;

}

2)僵尸进程测试程序1

int main()

{

pid_t pid;

pid = fork();

if (pid < 0)

{

perror("fork error:");

exit(1);

}

else if (pid == 0)

{

printf("I am child process.I am exiting.\n");

exit(0);

}

printf("I am father process.I will sleep two seconds\n");

//等待子进程先退出

sleep(2);

//输出进程信息

system("ps -o pid,ppid,state,command");

printf("father process is exiting.\n");

return 0;

}

3)僵尸进程测试程序2

#include

#include

#include

#include

int main()

{

pid_t pid;

//循环创建子进程

while(1)

{

pid = fork();

if (pid < 0)

{

perror("fork error:");

exit(1);

}

else if (pid == 0)

{

printf("I am a child process.\nI am exiting.\n");

//子进程退出,成为僵尸进程

exit(0);

}

else

{

//父进程休眠20s继续创建子进程

sleep(20);

continue;

}

}

return 0;

}

4)僵尸进程测试程序2--测试效果

运行可执行程序显示:

I am a child process.

I am exiting.

I am a child process.

I am exiting.

I am a child process.

I am exiting.

I am a child process.

I am exiting.

I am a child process.

I am exiting.

I am a child process.

I am exiting.

Killed

开另外一个终端:

运行:

ps -a -o pid,ppid,state,cmd

显示:(状态Z代表僵尸进程)

S PID PPID CMD

S 3213 2529 ./pid1

Z 3214 3213 [pid1]

Z 3215 3213 [pid1]

Z 3219 3213 [pid1]

Z 3220 3213 [pid1]

Z 3221 3213 [pid1]

R 3223 3104 ps -a -o state,pid,ppid,cmd

用第一种方法,解决僵尸进程,杀死其父进程

运行:kill -9 3213

注意:僵尸进程无法用kill直接杀死,如kill -9 3214,再用上面命令去查看进程状态,发现3214进程还在。

五. 参考文献

http://www.cnblogs.com/Anker/p/3271773.html

《unix环境高级编程》第八章

扫码申领本地嵌入式教学实录全套视频及配套源码

上一篇:Linux C中对json格式数组数据的生成与解析

下一篇:传感器尺寸

400-611-6270

Copyright © 2004-2024 华清远见教育科技集团 版权所有
京ICP备16055225号-5京公海网安备11010802025203号