Linux wait/waitpid详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangxiao93/article/details/72859312

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

展开阅读全文

没有更多推荐了,返回首页