wait和waitpid
在之前学习UNP卷一的时候,曾经学习过:在one connection per process这种并发模式下,
每有一个连接就fork一个进程去handle,在父进程中必须注册信号处理函数去回收子进程,否则,
在while true
一直存在的父进程情况下,将产生大量的僵尸进程(Z)
而在信号处理函数中,又必须使用可以设置非阻塞flag的waitpid,来处理并发连接结束后的子进程回收。
参见:http://blog.csdn.net/zhangxiao93/article/details/51579649
简介
当一个进程正常或者异常终止时,父进程将会收到内核发送的SIGCHLD
信号,父进程可以选择忽略该信号,也可以注册一个信号处理函数。
现在需要用wait或waitpid的进程可能会发生什么:
1.所有子进程在运行,阻塞
2.获得子进程终止状态后,立即返回
3.没有子进程终止,出错返回(非阻塞)
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
//statloc指向终止进程的终止状态,如果不关心终止状态可指定为空指针
//pid有四种情况:
//1.pid==-1 等待任意子进程
//2.pid>0 等待进程ID与pid相等的子进程
//3.pid==0 等待组ID等于调用进程组ID的任意子进程
//4.pid<-1 等待组ID等于pid绝对值的任意子进程
//options控制waitpid的操作:
//1,2是支持作业控制
//1.WCONTINUED
//2.WUNTRACED
//3.WNOHANG waitpid不阻塞
这两个函数区别如下:
//在子进程终止前,wait使其调用者阻塞,waitpid有一个选项可使调用者不阻塞。
//waitpid并不等待在其调用之后第一个终止的子进程,它有若干选项。换言之可以不阻塞。
//事实上:
pid_t wait(int *statloc)
{
return waitpid(-1, statloc, 0);
}
避免僵尸进程
当我们只fork()一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:
1.父进程调用waitpid()等函数来接收子进程退出状态。
2.父进程先结束,子进程则自动托管到Init进程(pid = 1)。
第一种情况使用wait/waitpid回收
子进程先于父进程结束,当父进程回收之前,子进程将处于僵尸状态
子进程结束前,父进程阻塞在wait调用
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t pid;
if((pid=fork())<0)//error
{
printf("fork error!");
return -1;
}
else if(pid==0)//child
{
printf("in child\r\n");
sleep(2);
}
else
{
int stat;
pid_t pid=wait(&stat);
printf("child terminate pid = %ld\r\n",(long)pid);
}
return 0;
}
//2 秒后,父进程打印
在TCP server中,通常父进程都是一直存在,因此需要调用wait/waitpid来回收子进程:
由于子进程结束时会发送SIGCHLD
的信号,在信号处理函数中对子进程进行回收
void sig_child(int signo)
{
while ( (pid = waitpid(-1,&stat,WNOHANG)) > 0)
printf("child terminated\r\n");
}
typedef void (*signal_handler_t)(int);
int main()
{
//...
//listen
signal_handler_t sig_handler1=sig_child;
signal(SIGCHLD,sig_handler1);
//...
}
解释一下,为什么要用while
循环,且为什么要用waitpid
的非阻塞模式
1.用while循环
如果在信号处理函数中,有子进程终止,通过while循环一次性回收
2.非阻塞模式
保证在回收最后一个终止的子进程后,没有子进程退出时,waitpid出错返回,主进程从信号处理函数中跳出而不是阻塞在信号处理函数中
第二种情况init进程负责回收
父进程先结束,这样子进程讲托管给init进程,由init进程回收子进程。
因为在父进程调用wait/waitpid之前,子进程都有机会成为僵尸进程
如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求需要用两个fork
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) //error
{
fprintf(stderr,"Fork error!\r\n");
exit(-1);
}
else if (pid == 0) //第一个子进程
{
if ((pid = fork()) < 0) //在第一个子进程中fork
{
fprintf(stderr,"Fork error!/n");
exit(-1);
}
else if (pid > 0) //第一个子进程,也就是第二个子进程的父进程
exit(0); /* parent from second fork == first child */
//直接退出
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("Second child, parent pid = %d\r\n", getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
{
fprintf(stderr,"Waitpid error!\r\n");
exit(-1);
}
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}
参考
1.APUE
2.http://blog.csdn.net/dlutbrucezhang/article/details/8883339
3.http://blog.csdn.net/zhangxiao93/article/details/51579649