20-wait,waitpid,waitid系列函数

1. wait函数

  前面在僵尸进程中说过子进程结束后,需要由父进程回收子进程,所以父进程肯定是不能先于子进程结束的,父进程怎么才能知道子进程结束了呢?

   当子进程结束后会通知父进程(子进程会给父进程发送SIGCHILD信号),然后父进程接收到通知就会处理子进程了,这时就需要用到wait函数了。


函数原型:

<sys/wait.h>
pid_t wait(int *status);

返回值:调用成功返回清理的子进程pid,失败则返回-1 (没有要回收的子进程)

参数status:用于保存子进程的退出状态等信息(传出参数)。


  wait函数可使用传出参数status来保存子进程退出状态,然后借助宏函数来进一步判断进程终止的具体原因,宏函数如下所示:

 1.WIFEXITED(status)    //返回非0表示进程正常结束
   WEXITSTATUS(status)  //如上面宏函数为真,则使用此宏函数获取进程退出状态 (exit的参数)

 2. WIFSIGNALED(status)     //返回非0表示进程异常终止
    WTERMSIG(status)        //如上面宏函数为真,使用此宏函数取得使进程终止的那个信号的编号

3.  WIFSTOPPED(status)        //返回非0表示进程处于暂停状态
    WSTOPSIG(status)      //如上面宏函数为真,使用此宏函数取得使进程暂停执行的那个信号的编号
    WIFCONTINUED(status)   //如上面宏函数为真,使用此宏函数让进程暂停后已经恢复运行



父进程在调用wait函数回收子进程时,wait函数将会执行以下几个动作:

  1. 阻塞等待子进程退出
  2. 回收子进程残留资源
  3. 获取子进程结束状态(退出原因)


2. 程序示例

wait_process程序获取子进程退出状态

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid, wpid;
    int status;
    pid = fork();

    if (pid == 0) {
        sleep(300);
        printf("child, pid = %d\n", getpid());
        return 19;

    } else if (pid > 0) {
        printf("parent, pid = %d\n", getpid());
        //获取子进程的状态
        //父进程一直在阻塞等待子进程结束
        wpid = wait(&status);
        printf("wpid ---- = %d\n", wpid);

        if (WIFEXITED(status)) {
            //获取进程退出状态
            printf("exit with %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            //获取使进程异常终止的信号编号
            printf("killed by %d\n", WTERMSIG(status)); 
        }
    }
}



程序执行结果:
这里写图片描述

  当执行kill -9命令使子进程异常终止退出时,调用wait函数通过参数status就可以获取到子进程异常终止退出的信息。


3. waitpid函数

  waitpid函数功能和wait一样,都是用来获取子进程状态或改变,但是waitpid函数更加强大,但可指定pid进程回收,可以不阻塞。

函数原型:

  pid_t waitpid(pid_t pid, int *status, in options);

返回值说明:成功返回清理掉的子进程pid;失败则返回-1,设置errno



参数pid:指定回收子进程pid
  pid > 0,回收指定pid的子进程
  pid = -1,回收任意子进程(相当于wait)
  pid = 0,回收和当前调用waitpid一个进程组内的所有子进程
  pid < -1,回收指定进程组内为|pid|的所有子进程

参数status:传出参数,用于保存清理子进程的状态(如果不关心子进程的退出状态可传NULL)

参数options:设置回收状态阻塞或非阻塞
  WUNTRACED:可获取子进程暂停状态,也就是可获取stopped状态

  WCONTINUED(linux2.6.10内核):可获取子进程恢复执行的状态,也就是可获取continued状态

  WNOHANG:设置非阻塞,如果参数pid指定的子进程运行正常未发生状态改变,则立即返回0,如果调用进程没有与pid匹配的子进程,waitpid则出错,设置errno为ECHILD。

  参数options是一个位掩码,可以使用|运算符操作组合以上几个标志,如果options = NULL(也就是这三个开关都不打开),调用waitpid函数会以阻塞方式回收子进程,这点需要注意。

  另外,对于waitpid的参数status值也可以使用WIFEXITED,WIFSIGNALED,WIFSTOPPED等宏函数来进一步判断进程终止的具体原因。


使用waitpid函数的时候需要注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环


4. 程序示例

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

/*
主要思路:熟悉waitpid函数原型,参数作用和返回值
*/
int main(void) {
    pid_t pid, wpid;
    int flg = 0;
    pid = fork();
    //fork进程失败
    if(pid == -1){
        perror("fork error");
        exit(1);
    //子进程
    } else if(pid == 0){        
        printf("I'm process child, pid = %d\n", getpid());
        sleep(5);               
        exit(4);

    //父进程
    } else {
        do {
            //WNOHANG非阻塞回收指定子进程
            wpid = waitpid(pid, NULL, WNOHANG);
            //wpid = wait(NULL);
            printf("---wpid = %d--------%d\n", wpid, flg++);

            //如果wpid == 0说明参数3为WNOHANG非阻塞回收,且子进程正在运行中
            if(wpid == 0){
                printf("NO child exited\n");
                sleep(1);
            }
        //每次循环前,判断子进程是否可回收
        } while (wpid == 0);    
        //如果为真,可回收指定子进程
        if(wpid == pid){
            printf("I'm parent, I catched child process, pid = %d\n", wpid);
        //回收的任意子进程
        } else {
            printf("other...\n");
        }
    }
    return 0;
}



程序执行结果:
这里写图片描述

  父进程在fork完之后,调用了waitpid函数并sleep了1秒,这个时候子进程获得了CPU的执行权,打印I’m process child,pid = 1965后sleep了5秒,之后父进程一直在以非阻塞方式等待回收子进程,5秒后子进程执行结束,父进程成功回收子进程。


5. waitid函数

  与waitpid函数类似,waitid函数是用于获取一个子进程更加详细的状态或改变。waitid是源于System V下的系统调用,现在已加入到2.6.9 linux内核中。

函数原型:

#include <sys/wait.h>
int waitid( idtype_t idtype, id_t id, siginfo_t *infop, int options );

返回值:成功返回0,失败返回-1并设置errno



参数idtype和id指定需要获取那些子进程的状态:
  1.如果idtype = P_ALL,则获取任意子进程状态,并忽略参数id
  2.如果idtype = P_PID,获取参数id指定的子进程状态
  3.如果idtype = P_PGID,则获取进程组为id的所有子进程



waitid函数和waitpid函数最大的区别在于,waitid可以通过参数options更加精准详细的获取子进程的状态或改变:
  WEXITED:获取正常终止的子进程状态

  WSTOPPED:获取因信号而暂停的子进程

  WCONTINUED:获取由SIGCONT信号恢复执行的子进程

  WNOHANG:同waitpid函数中的选项意义相同

  WNOWAIT:从“可等待状态”的子进程处返回,后面的wait依然可以获取子进程状态



  调用waitid函数返回0会将子进程相关信息保存到参数infop中,infop是一个传出参数,它的数据类型是siginfo_t结构体,以下是siginfo结构体中的比较重要的成员信息:

siginfo_t {
int      si_signo;    /* Signal number */
 int      si_code;     /* Signal code */   
 pid_t    si_pid;      /* Sending process ID */
 uid_t    si_uid;      /* Real user ID of sending process */    
    int      si_status;   /* Exit value or signal */
. . . . . .
}

si_signo:表示信号,如SIGCHILD信号

si_code:一般有这几个值,CLD_EXITED表示子进程是调用_exit终止的,CLD_KILLED表示子进程因某个信号杀死的,CLD_STOPPED表示子进程因某个信号而终止,CLD_CONTINUED表示子进程收到SIGCONT信号恢复执行。

si_pid:发送信号的进程id

si_uid:发送信号的进程实际用户

si_status:子进程退出的原因,比如正常退出对应的值,异常退出对应的信号。子进程具体的退出原因可以通过si_code来进一步判断。


6. waitid函数的细节

  在使用waitid函数需要注意的细节,如果参数options指定了WNOHANG,waitid函数会在以下几种情况下返回0:子进程的状态已经改变,或者子进程的状态没有改变。

  如果子进程没有改变状态(这说明子进程还未退出),一些unix实现会包括linux会将siginfo_t结构体内容清0(但是有一些unix实现不会把siginfo_t结构体内容清空),然后我们就可以检查si_pid的值是否为0,以此来区分waitid函数返回0是哪一种情况。

  为了确保区分这两种情况,最好是在调用waitid函数之前把siginfo_t结构体清空,代码如下:

siginfo_t info;
memset(&info , 0 , sizeof(siginfo_t));
waitid(idtype , id , &info , WNOHANG);
if(info.si_pid == 0){
    /*没有任何子进程的状态发生改变*/
}else{
    /*有子进程的状态发生改变*/
}


7. 程序示例

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int main(void)
{
        pid_t pid;
        id_t id;
        int status;
        int ret;
        pid = fork();

        if(pid == -1){
                perror("fork error");
                exit(1);
        }
        //子进程
        else if(pid == 0){        
                printf("I'm child, pid = %d\n", getpid());
                exit(8);    //子进程以_exit方式退出,退出状态的值为8

        }
        //父进程
        else{
                siginfo_t info;
                //清空siginfo结构体
                memset(&info , 0 , sizeof(siginfo_t));
                ret = waitid(P_ALL , id , &info , WEXITED);
                if(ret < 0){
                        perror("waitid error:");
                }
                //判断子进程是否以_exit方式退出
                if(info.si_code == CLD_EXITED)
                {
                        printf("si_code = CLD_EXITED\n");
                }
                //打印子进程退出状态的值
                printf("si_status = %d\n" , info.si_status);
        }
        return 0;
}



程序执行结果:
这里写图片描述

  当子进程调用exit函数退出时,在exit函数内部会调用_exit系统调用,也就是说,实际上子进程会以_exit方式退出。然后父进程调用waitid函数在获取子进程状态时,其参数info就会保存子进程退出状态,打印si_code = CLD_EXITED则说明子进程是以_exit方式退出的,si_status则保存了子进程退出时对应的返回值。


8. 总结

1 . 重点掌握wait和waitpid的用法

2 . 注意wait和waitpid的区别和使用场景

3 .了解waitid函数

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值