前面的博客中我们也讲过了僵尸进程的概念,我们知道当一个父进程创建一个子进程时,最好要调用wait或者waitpid函数等待子进程,不然会产生僵尸进程造成内存泄漏的问题。
一般父进程在等待子进程时有两种方式等待,一种是阻塞式等待,这时父进程不能处理自己的工作;另一种是以非阻塞式等待,父进程处理自己工作的同时,要以定时轮询的方式,去查看有没有子进程等待清理。
wait()和waitpid()的区别
<1>wait 函数:用来等待任何一个子进程退出,由父进程调用。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值
成功返回被等待子进程的pid,失败返回-1。 status参数 : 输出型参数,拿回子进程的退出信息。
wait函数 : 阻塞式调用,等待的子进程不退出时,父进程一直不退出。
目的: 回收子进程,系统回收子进程的空间。参数status
如果不为空,则进程终止状态被保存于其中。依据传统,返回的整形状态字是由实现定义的,其中有些位表示退出状态(正常返回),其他位表示信号编号(异常返回),有一位表示是否产生了一个core文件等。终止状态是定义在 sys/wait.h中的各个宏,有四可互斥的宏可以用来取得进程终止的原因。
WIFEXITED :正常返回时为真,可以执行宏函数 WEXITSTATUS获取子进程传送给exit、_exit或_Exit的参数的低8位。
WIFSIGNALED :异常返回时为真,可以执行宏函数WTERMSIG取得子进程终止的信号编号,另外,对于一些实现,定义有宏WCOREDUMP宏,若以经昌盛终止进程的core文件,则为真。
WIFSTOPPED : 若为当前暂停子进程的返回的状态,则为真,可执行WSTOPSIG取得使子进程暂停的信号编号。
WIFCONTINUED : 若在作业控制暂停后已经继续的子进程返回了状态,则为真,仅用于waitpid。
<2>waitpid函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);
pid参数
从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。status参数
与wait()函数的基本相同,此处不再赘述。options参数
当options参数为0时,waitpid函数 与wait函数功能相同,仍是阻塞式等待,不提供额外功能,如果为下列常量按位或则提供更多功能:
WCONTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但状态尚未报告,则返回状态
WNOHANG:若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,即此时以非阻塞方式(轮询式访问的必要条件)等待子进程,并且返回0。
WUNTRACED:若实现支持作业控制,而pid指定的任一子进程已经暂停,且其状态尚未报告,则返回其状态返回值
当正常返回的时候,waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现此时没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;waitpid提供了三个wait所没有的功能:
1. waitpid可等待一个特定的进程
2. waitpid提供了一个wait的非阻塞版本
3. waitpid支持作业控制
验证子进程退出会给父进程发送SIGCHID信号
子进程终止时会给父进程发送SIGCHID信号,为了验证子进程在退出时确实向父进程发送了SIGCHID信号,我们对SIGCHID信号进行捕捉。
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void myhandler(int sig)
{
printf("father is catching,child is quit\n");
}
int main()
{
signal(SIGCHLD,myhandler);
pid_t pid = fork();
if(pid == 0)
{//child
printf("I'm child: %d\n",getpid());
sleep(3);
exit(-1); //子进程异常退出
}
else
{//father
while(1)
{
printf("father is doing something...\n");
sleep(1);
}
}
return 0;
}
如果子进程退出的时候给父进程发送了SIGCHLD信号,那么该信号会被捕捉到,并打印father is catching,child is quit
执行结果:
从结果看,符合我们的预期,子进程在退出时确实会给父进程发送SIGCHLD信号。
子进程的异步等待方式
父进程自定义SIGCHLD信号的处理函数,在处理函数中调用waitpid函数,采用非阻塞方式等待(轮询的方式等待)。这样父进程就可以专注于自己的工作了,子进程退出时,会向父进程发送信号,父进程在信号处理函数内部对子进程资源进行回收。这种就是对子进程的异步等待方式。
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void myhandler(int sig)
{
printf("father is catching,child is quit\n");
//以非阻塞方式等待所有异常退出的子进程
pid_t id;
while((id = waitpid(-1,NULL,WNOHANG)) > 0)
{
printf("wait child success:%d\n",id);
}
}
int main()
{
signal(SIGCHLD,myhandler);
pid_t pid1 = fork();
if(pid1 == 0)
{//child1
printf("child1:my pid is %d\n",getpid());
exit(-1);//子进程1异常终止
}
pid_t pid2 = fork();
if(pid2 == 0)
{//child2
printf("child2:my pid is %d\n",getpid());
exit(-1);//子进程2异常退出
}
pid_t pid3 = fork();
if(pid3 == 0)
{//child3
printf("child3:my pid is %d\n",getpid());//子进程3正常运行
}
while(1)
{
printf("father is doing something...\n");
sleep(1);
}
return 0;
}
执行结果:
由上结果我们可以看出,父进程一直在做自己的事情,子进程退出时,父进程收到信号,在信号处理函数内部对子进程资源进行了回收。
如上这种父进程对子进程的等待方式就是异步等待方式。