上篇讲到僵尸进程产生的原因及危害,并且简要的提到了避免产生僵尸进程的两种方法:
一、让父进程先退出,形成孤儿进程,最终由1号进程(init进程)来回收子进程系统资源;
二、父进程调用wait、waitpid函数,以阻塞或非阻塞轮训的方式来等待子进程退出,并回收子进程的系统资源;
本篇将具体来讲解一下,两种解决方案的具体实施过程。
一、避免僵尸进程之 ✈✈✈✈ 形成孤儿进程
《Unix 环境高级编程》8.6节
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
//创建第一个子进程
if ( (pid = fork()) < 0)
{
perror("fork error:");
exit(1);
}
//第一个子进程
else if (pid == 0)
{
printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
//在子进程内创建第二个子进程(即孙子进程)
if ( (pid = fork()) < 0)
{
perror("fork error:");
exit(1);
}
else if (pid >0)
{
//第一个子进程退出
printf("first procee is exited.\n");
exit(0);
}
//从这开始就是孙子进程的天下了
//睡眠3s保证孙子进程的父进程(第一个子进程)退出,这样孙子进程就会被1号进程所收养,形成孤儿进程
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}
//父进程处理第一个子进程退出,注意子进程的pid在父进程中返回,所以在这直接等待pid的退出;
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
exit(0);
return 0;
}
二、避免僵尸进程之 ✈✈✈✈ wait、waitpid函数
wait函数
函数原型:pid_t wait(int *status);
1.功能:
在父进程中执行,就是阻塞等待当前进程中任何一个子进程的退出,回收了子进程的资源;
2.参数:
status 用于保存退出子进程的退出状态,如果传递NULL,表示不关心子进程的退出状态;当子进程调用exit()函数退出子进程时,wait()函数status承接的就是exit()函数的退出值;
3.返回值:
成功返回:退出的子进程的pid号
失败返回:返回 -1,同时errno被置为ECHILD
waitpid函数
函数原型:pid_t waitpid(pid_t pid,int *status,int options);
1.功能:等待指定的子进程结束(当子进程的状态发生改变时的处理函数)
2.参数:
第一个参数:pid
常用:
pid > 0,等待指定的pid子进程退出(表示只会等待指定子进程退出,对于其他的子进程的退出不做处理);
pid = -1,等待任意一个子进程的退出;
不常用:
pid = 0,等待被调用进程同一组内任一子进程的退出;
pid < -1,等待组ID等于pid绝对值的任一子进程的退出;
第二个参数:status(同wait函数)
第三个参数:options
0,阻塞等待;
返回值: 成功 返回pid号;失败 返回-1,此时errno会被设置;
WNOHANG,非租塞等待;
返回值: 成功 返回pid号(成功接收到);失败 返回0(没有接收到);
out_pid = wait(&ret); <====等同====> out_pid = waitpid(pid, &ret, 0);
1.wait函数具体应用:
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid, out_pid;
if( (pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("im child process, and mypid = %d\n", getpid());
sleep(3);
}
else if(pid > 0)
{
out_pid = wait(NULL);
printf("im farther process, i wait the processpid = %d\n", out_pid);
}
return 0;
}
wait具体怎么玩儿的?
父进程调用了wait函数,将自己阻塞在wait函数。wait函数参数置为NULL,表示父进程不在意退出进程的状态值(即不在意exit()函数中传的是什么参数)。当子进程执行完毕或调用exit()后,wait会收集到这个子进程的信息,并将其销毁。wait返回退出的子进程的pid号。
为什么调用sleep(3);?
就是为了验证一下,wait函数会不会阻塞等待,因为父子进程在CPU调度时会抢占执行,看,现在父进程没有抢,而是在等子进程的退出。
刚才我们说了,wait承接的是exit()的参数(即子进程退出的状态值),现在我们来验证一下!
修改代码如下:
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid, out_pid;
if( (pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("im child process, and mypid = %d\n", getpid());
sleep(3);
exit(0);
//exit(1);
//exit(2);
}
else if(pid > 0)
{
int ret;
out_pid = wait(&ret);
printf("im farther process, i wait the processpid = %d\n", out_pid);
printf("ret = %d\n", ret);
}
return 0;
}
我们分别执行exit(0)/exit(1)/exit(2),编译执行结果如下:
exit(0):
exit(1):
exit(2):
我们可以看到,系统将我们赋值给exit()的参数全部乘以了256.(具体原因老师讲的忘了)
结合着上边这个没解释清的东东,再来讲两个宏。(如果想看一下这两个宏的原型,可以vi -t WIFEXITED,如果vi -t用不了,参见我的其他博客)
如何判断子进程是否正常退出呢?
WIFEXITED():
1.功能:判断子进程是否为正常退出;
2.参数:注意不是&status,而是status,不是指针哦;
3.返回值:
如果子进程是正常退出,返回一个非零值;
如果子进程是非正常退出,返回0;
WEXITSTATUS():
1.功能:在子进程为正常退出的前提下,读取子进程的退出状态值;
2.参数:与WIFEXITED一样哦;
3.返回值:返回读到的子进程退出状态值;
来,一起验证验证一下:
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid, out_pid;
if( (pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("im child process, and mypid = %d\n", getpid());
sleep(3);
exit(2);
//exit(1);
//exit(0);
}
else if(pid > 0)
{
int ret;
out_pid = wait(&ret);
if(WIFEXITED(ret))
{
printf("WIFEXITED(ret) = %d, so child process exit normally\n");
printf("child process exit num = %d\n", WEXITSTATUS(ret));
}
printf("im farther process, i wait the processpid = %d\n", out_pid);
printf("ret = %d\n", ret);
}
return 0;
}
2.waitpid函数具体应用:
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid, out_pid;
if( (pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("im child process, and mypid = %d\n", getpid());
sleep(3);
exit(2);
//exit(1);
//exit(0);
}
else if(pid > 0)
{
int ret;
//此时等同于wait(&ret);
//out_pid = waitpid(pid, &ret, 0);
//注意如果让waitpid是非阻塞模式下运行的话,要结合循环使用;
//注意,这里的pid就是子进程的进程号,因为进程的特性就是子进程的进程号在父进程返回;
while( (out_pid = waitpid(pid, &ret, WNOHANG)) == 0)
{
printf("no child process exited\n");
sleep(1);
}
printf("child process %d is exit\n", out_pid);
//这里可以用下面这行来验证下子进程的pid在父进程中返回这句话;
//printf("pid = %d\n", pid);
}
return 0;
}
编译执行(如下图)
子进程睡眠了3秒,每1秒轮训一次,所以打印了3次”no child process exited”后,子进程退出,父进程获取到了子进程退出,释放其系统资源;