子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远也无法预测子进程到底什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
原理机制:
UNIX提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID, 退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait/waitpid 来取时才释放。
僵尸进程的危害:
这样就会出现问题,如果进程不调用wait/waitpid的话,那么保留的信息不会释放,其进程号就会一直被占用,但是系统能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。并且如果产生僵尸进程的源头父进程不断的产生僵尸进程,则会增加系统的性能负担。此为僵尸进程的危害。
孤儿进程:
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到init进程身上。当一个孤儿进程结束其生命周期的时候,init进程会处理它的一切善后工作。因此孤儿进程并不会有什么危害。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。每个子进程都会经历变为僵尸进程的阶段,只是看父进程处理的及不及时。
僵尸进程的危害场景:
有一个进程,它定期产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短。但是,父进程只管生成新的子进程,至于子进程退出之后的时候,它一概不问。这样,系统运行一段时间后,就会产生很多的僵尸进程,ps命令可以查看到很多状态为Z的进程。问题的根源在于产生僵尸进程的父进程,只要将这个父进程kill掉(也就是通过kill发送SIGTERM或者SIGKILL信号)。父进程死掉之后,这些僵尸进程就变成了孤儿进程,init进程会wait()这些孤儿进程,释放它们占用的资源,这样僵尸进程就结束了。
孤儿进程测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
if(pid == 0)
{
printf("i am the child process!");
printf("pid:%d ppid:%d\n", getpid(), getppid());
sleep(20);
printf("pid:%d ppid:%d\n", getpid(), getppid());
printf("child process is exited.");
}
else
{
printf("i am father process!\n");
sleep(10);
printf("father process is exited.\n");
}
return 0;
}
打印结果:
一开始子进程的pid为29833,父进程ppid:29832
10秒之后,父进程退出,子进程的父进程ppid:1
此时,父进程已经退出,子进程依旧在执行,子进程就会变成孤儿进程,被init()所接收。
僵尸进程测试程序:
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error!");
exit(1);
}
if(pid == 0)
{
printf("i am the child process, exit!\n");
exit(0);
}
printf("i will sleep 10s\n");
sleep(10);
printf("i am the father process, exit!\n");
return 0;
}
打印结果:
子进程先退出,父进程等待10s后退出,子进程退出后,父进程没有接收子进程的退出消息,就会生成僵尸进程,图中的[test2]<defunct>,等10s后父进程退出了,此僵尸进程被init进程处理,释放。
僵尸进程的解决方案:
1、通过信号机制
测试程序如下:
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <signal.h>
void handler_sign(int sign)
{
int pid;
int status;
while((pid = waitpid(-1, &status, WNOHANG)) > 0)
{
printf("child %d pid stop!\n", pid);
}
}
int main()
{
pid_t pid;
pid = fork();
//signal(SIGCHLD, SIG_IGN);忽略此信号
signal(SIGCHLD, handler_sign);
if(pid < 0)
{
perror("fork error!");
exit(1);
}
if(pid == 0)
{
printf("i am the child process, exit!\n");
exit(0);
}
printf("i will sleep 10s\n");
sleep(10);
printf("i am the father process, exit!\n");
return 0;
}
子进程退出时向父进程发送SIGCHLD信号,父进程处理SIGCHLD信号,在信号处理函数中调用waitpid函数处理子进程的信号。signal函数,wait函数,waitpid函数又是另一个知识点。
2、fork两次
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error!");
exit(1);
}
if(pid == 0)
{
printf("i am the first child process, exit!\n");
pid = fork();
if(pid < 0)
{
perror("fork error!");
exit(1);
}
if(pid > 0)
{
printf("first process is exit!\n");
exit(0);
}
else
{
printf("i am the second process\n");
sleep(10);
printf("i am the second process exit\n");
exit(0);
}
}
if(waitpid(pid, NULL, 0)!=pid)
{
perror("error");
exit(1);
}
sleep(5);
printf("i am the father process, exit!\n");
return 0;
}
原理是将子进程变成孤儿进程,让init进程处理它。
参考:
https://www.cnblogs.com/Anker/p/3271773.html