在Unix/Linux中一般情况下,子进程是由父进程通过fork函数创建的。但是子进程的结束和父进程的运行时一个异步过程,所以子进程被父进程创建出来以后父进程无法预测子进程什么时候结束。所以父进程通常会通过调用wait()或者waitpid()系统调用来获得子进程的终止状态!
孤儿进程:
顾名思义,孤儿进程是由于他们的父进程结束运行,但是子进程仍在继续运行,这时候这些子进程会变成孤儿进程,并且这些孤儿进程会被init(1号)进程所收养,并由init进程来完成对他们的状态收集工作,一般来说孤儿进程对我们没有什么危害。
僵尸进程:
当一个进程使用了fork创建了子进程,当子进程结束运行退出时,其父进程没有调用wait()或waitpid()来获取子进程的状态信息时,这些子进程的进程描述符会仍然保存在系统中,我们称这种进程为僵尸进程,僵尸进程会对系统带来危害。
孤儿进程测试
代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
int n = 1;
//如果fork成功,则返回两次
pid_t pid = fork();
if(-1 == pid)
{
perror("fork:");
exit(-1);
}
if(pid == 0)
{//子进程
while(1)
{
n++;
printf("child pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
exit(0);
}
else
{//父进程
while(1)
{
printf("father pid=%d,n=%d\n",getpid(),n);
sleep(1);
}
}
printf("main ending!\n");
return 0;
}
由图可知,分别输出父进程的进程号、变量n的值和子进程的进程号与其父进程的进程号
我们这个时候开启另一个终端,然后用kill命令杀死父进程
我们这时候可以发现,子进程的父进程号变为1
并且由上图可知,子进程与父进程分别属于两块不同的内存空间,所以在子进程中对n进行++运算不会改变父进程中n的值。
僵尸进程测试
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
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
{
//父进程休眠10s继续创建子进程
sleep(10);
//输出进程信息
system("ps -o pid,ppid,state,tty,command");
continue;
}
}
return 0;
}
该代码中父进程每隔10创建一个子进程,但是父进程中并没有wait()或者waitpid(),所以这些子进程都会变为僵尸进程
另外开一个终端可以查到
当父进程退出时
僵尸进程也随之消失
所以要消灭僵尸进程只需要将父进程退出即可,但是在服务器上一个父进程一般不会退出,这时候要消灭僵尸进程就需要以下方法
僵尸进程解决方法
1.通过信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
测试代码如下:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<signal.h>
//处理SIGCHILD信号
void handlesig(int signo)
{
printf("signo=%d\n",signo);
pid_t pid;//记录终止进程的ID
int status;
//WNOHANG非阻塞等待
while((pid = waitpid(-1,&status,WNOHANG)) > 0)
{
printf("pid=%d,already exited!\n",pid);
}
}
int main()
{
pid_t pid = fork();
//挂载信号,当进程收到SIGCHLD信号时,就调用handlesig函数
signal(SIGCHLD,handlesig);
if(-1 == pid)
{
perror("fork");
exit(-1);
}
if(pid == 0)
{//子进程
printf("pid=%d,ppid=%d\n",getpid(),getppid());
exit(0);
}
else
{//父进程
sleep(10);
printf("pid=%d,ppid=%d\n",getpid(),getppid());
printf("father proccess exited!\n");
while(1)
{
sleep(1);
printf("父进程不退出,测试是否会产生僵尸进程!");
}
}
return 0;
}
如图可知,并没有退出父进程,但是也没有产生僵尸进程。
2.fork两次
原理如图:
测试代码如下:
#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)
{
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);
}
//输出进程信息
system("ps -o pid,ppid,state,tty,command");
exit(0);
return 0;
}