博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
20-wait,waitpid,waitid系列函数
阅读量:2044 次
发布时间:2019-04-28

本文共 6218 字,大约阅读时间需要 20 分钟。

1. wait函数

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

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

函数原型:

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 
#include
#include
#include
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 
#include
#include
#include
/*主要思路:熟悉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 
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 
#include
#include
#include
#include
#include
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函数

你可能感兴趣的文章
【JS】【31】读取json文件
查看>>
OpenSSL源代码学习[转]
查看>>
Spring下载地址
查看>>
wxzh001,进来看关于APACHE+PHP+MYSQL+SSL的LINUX下安装配置(转自奥索)
查看>>
google app api相关(商用)
查看>>
linux放音乐cd
查看>>
GridView+存储过程实现'真分页'
查看>>
flask_migrate
查看>>
解决activemq多消费者并发处理
查看>>
UDP连接和TCP连接的异同
查看>>
hibernate 时间段查询
查看>>
java操作cookie 实现两周内自动登录
查看>>
jstl 中获得session 里面值sessionScope
查看>>
Tomcat 7优化前及优化后的性能对比
查看>>
VisualVM 提示 tomcat 不受此jvm支持解决办法
查看>>
Java Guava中的函数式编程讲解
查看>>
Eclipse Memory Analyzer 使用技巧
查看>>
tomcat连接超时
查看>>
谈谈编程思想
查看>>
iOS MapKit导航及地理转码辅助类
查看>>