之前在学习wait和waitpid函数的时候,就对使用宏WIFEXITED来检查获取的进程终止状态产生过疑惑:一般我们在程序中是调用的exit或者_exit函数来退出的,那么wait和waitpid函数获取的终止状态直接就是我们传递给exit的参数不就OK了吗?

后来了解到是我考虑简单了,因为程序退出不仅仅只有我们显示地调用exit这么简单,还会有异常退出等情况,本文就对wait函数获取的状态做个总结!

先来对wait status做个整体总结,一般我们通过wait status可以判定子进程发生了以下事件:

(1)子进程通过传递一个整形参数给exit(或者_exit)而正常退出

(2)子进程被一个信号终止

(3)子进程被一个信号暂停(调用waitpid时指定WUNTRACED标志)

(4)暂停的子进程被信号SIGCONT恢复(调用waitpid时指定WCONTINUED标志)

平时我们说的进程termination status指的只是前两个wait status(可以通过$?来查看进程的termination status)。

那么wait status是如何表示这些事件的呢? —— 具体如何表示的,不同的平台有不同的定义,因为POSIX并没有对实现做出详细的定义,这也是为什么推荐使用宏来检查wait status了,主要是考虑到程序可移植问题。本文针对x86平台32位。

通过上图可以发现,虽然wait status是int型的,但实际上只使用了它的低2个字节。

高8位用来记录正常退出状态,这也正解释了为什么程序退出状态的范围总是0~255。

低8位用来记录信号。

好了,现在来看看这些宏具体是如何实现的吧!

在/usr/include/i386-linux-gnu/sys/wait.h中

在/usr/include/i386-linux-gnu/bits/waitstatus.h中

接下来我们简单分析下这几个宏:

WIFEXITED/WEXITSTATUS:当程序是正常退出时则WIFEXITED(status)为真,这种情况下WEXITSTATUS(status)返回子进程的退出状态。

WIFEXITED最后可以简写为:

  1. #define WIFEXITED(status) (((status) & 0x7f) == 0)

当WIFEXITED(status)为真则表示((status) & 0x7f)为0,意思是:程序退出不是信号导致的退出,那么就是正常退出了。

WEXITSTATUS(status)可以简写为:

  1. #define WEXITSTATUS(status) (((status) & 0xff00) >> 8)

先将低8位清零,然后右移8位,则取得高8位数值,即程序正常退出状态。

WIFSTOPPED/WSTOPSIG:当子进程是因为被一个信号暂停而返回时则WIFSTOPPED(status)为真,在这种情况下WSTOPSIG(status)返回这个暂停子进程信号的编号。

WIFSTOPPED(status)可以简写为:

  1. #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)

当wait status低八位数值是0x7f时,则表明子进程是被信号暂停而返回的。

WSTOPSIG(status)可以简写为:

  1. #define WSTOPSIG(status) (((status) & 0xff00) >> 8)

可以发现,在这种情况下,WSTOPSIG(status)与WEXITSTATUS(status)取值方式是一样的。

WIFCONTINUED:当一个暂停的子进程被信号SIGCONT唤醒而返回状态,则WIFCONTINUED(status)为真,否则为假。

WIFCONTINUED(status)可以简写为:

  1. #define WIFCONTINUED(status) ((status) == 0xffff)

当wait status低两个字节数值为0xfff时,表明一个暂停的子进程被SIGCONT信号唤醒。

WIFSIGNALED/WTERMSIG/WCOREDUMP:当程序异常终止时WIFSIGNALED(staus)为真,这种情况下WTERMSIG(status)返回终止进程的信号编号。并且程序异常终止时产生了core文件的话,则WCOREDUMP(status)为真,否者为假。

WIFSIGNALED(status)可以简写为:

  1. #define WIFSIGNALED(status) (((signed char)((status) & 0x7f + 1) >> 1) > 0)

这个宏的写法是这些宏当中最难理解的一个,以下是我的简单分析过程,对错还请批评指正!

(status) & 0x7f 的范围是0 ~ 127

(status) & 0x7f + 1 的范围是0 ~ 128

那么(signed char)((status) & 0x7f + 1) 的范围是1 ~ 127 和一个-128

由此推出:(signed char)((status) & 0x7f + 1) >> 1的范围是0 ~ 63 和一个-64

由此得出当wait status的低7位数值是0x7f时是不符合要求的,即宏WIFSIGNALED(status)的这种写法其实是排除了wait status的低7位数值是0x7f的。因为当低7位是0x7f(第8位为0)时表示的是子进程被信号暂停。其实现有的平台的信号编号也没有达到127的。

我们知道信号编号是从1开始的(kill函数对信号编号0有特殊的处理)。右移一位相当于除以2的操作,1除以2在程序中是等于0的,所有其中的加1操作其实很有技巧性,既利用了信号编号从1开始这个特性,又排除了127这个数值。

这个宏还可以如下实现:

  1. #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))

这样就好理解了,既不是信号暂停,又不是正常退出,那么肯定也不是被信号SIGCONT唤醒,那就肯定是被信号终止的情况了,哈哈。

WTERMSIG(status)可以简写为:

  1. #define WTERMSIG(status) ((status) & 0x7f)

这个就不用解释了吧

WCOREDUMP(status)可以简写为:

  1. #define WCOREDUMP(status) ((status) & 0x80)

这个也很好理解,就是检测第8位是否为1,是1,则生成core文件,否则不生成。

综上,可以发现APUE的pr_exit函数实现的不够全,下面给个全面的如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/wait.h>
  5. void pr_exit(const char *msg, int status)
  6. {
  7. if (msg)
  8. printf("%s ", msg);
  9. if (WIFEXITED(status)) {
  10. printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
  11. } else if (WIFSIGNALED(status)) {
  12. printf("abnormal termination, signal number = %d(%s)%s\n",
  13. WTERMSIG(status), strsignal(WTERMSIG(status)),
  14. #ifdef  WCOREDUMP
  15. WCOREDUMP(status) ? " (core file generated)" : "");
  16. #else
  17. "");
  18. #endif
  19. } else if (WIFSTOPPED(status)) {
  20. printf("child stopped, signal number = %d(%s)\n",
  21. WSTOPSIG(status), strsignal(WSTOPSIG(status)));
  22. }
  23. #ifdef WIFCONTINUED
  24. else if (WIFCONTINUED(status)) {
  25. printf("child continued by SIGCONT signal\n");
  26. }
  27. #endif
  28. else {             /* Should never happen */
  29. printf("what happened to this child? (status=%x)\n",
  30. (unsigned int) status);
  31. }
  32. }

PS:C标准对一个有符号数值并且是负数时的右移操作的说明是未实现的,我试了gcc,结果还是负数,对这块了解的不是很多,还请谅解。

参考链接:

http://tsecer.blog.163.com/blog/static/15018172012323975152/

http://www.cs.virginia.edu/pipermail/splint-discuss/2008-March/001136.html

参考书籍:

《The Linux Programming Interface》

wait函数返回值总结的更多相关文章

  1. shell调用函数返回值深入分析

    编写shell脚本过程中,我们经常会自定义一些函数,并根据函数的返回值不同来执行相应的流程,那么我们如何来获取函数的返回值呢? 首先shell中调用函数有两种方式: 第一种:value=`functi ...

  2. Python从线程获取函数返回值

    Python中利用强大的threading模块可以很容易的实现多线程开发,提高运行速度.这一般是对某个进行大量计算操作的的函数进行多线程处理,然后合并各线程的结果.获取函数返回值的方法可以如下: 1) ...

  3. 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数

    [源码下载] 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数 作者:webabcd 介绍速战速决 之 PHP 函数基础 函数参数 函 ...

  4. string类find函数返回值判定

     string类find函数返回值判定 代码示例 #include<iostream> #include<cstring> using namespace std; int m ...

  5. c语言main函数返回值、参数详解(返回值是必须的,0表示正常退出)

    C语言Main函数返回值 main函数的返回值,用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出. 很多人甚至市面上的一些书籍,都 ...

  6. Python学习教程(learning Python)--2.3.4Python函数返回值

    本节讨论Python函数返回值问题. Python和C语言一样,也可以在函数结束时返回一个值.但在定义自己的Python函数时,是不需要指定返回值数据类型的,这和Python不关心变量的数据类型是一致 ...

  7. C++ const修饰函数、函数参数、函数返回值

    const修饰函数 在类中将成员函数修饰为const表明在该函数体内,不能修改对象的数据成员而且不能调用非const函数.为什么不能调用非const函数?因为非const函数可能修改数据成员,cons ...

  8. Shell函数:Shell函数返回值、删除函数、在终端调用函数

    函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高.像其他编程语言一样,Shell 也支持函数.Shell 函数必须先定义后使用. Shell 函数的定义格式如下: f ...

  9. PHP获取函数返回值的引用

    通过在函数前添加&可以获取函数返回值的引用,如:function &test(){return 10;}

  10. Shell函数返回值、删除函数、在终端调用函数

    Shell 也支持函数.Shell 函数必须先定义后使用. Shell 函数的定义格式如下: function_name () { list of commands [ return value ] ...

随机推荐

  1. DOM对象和JQuery对象的区别

    DOM对象和JQuery对象的区别 jQuery对象和DOM对象使用说明,需要的朋友可以参考下.1.jQuery对象和DOM对象第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DO ...

  2. struts2学习笔记(3)——struts2的局部类型转换

    今天又学到了一个新的东西,就是struts2的类型转换. 为什么要类型转换? 今天我就要传一个点的坐标给你,保存时用一个自定义的Point类来保存. 因为在表单里面传过去的是字符串,如“12,23”, ...

  3. [杂题]HDOJ5515 Game of Flying Circus

    嗯...这是一道水题... 鉴于还没人写这题的题解, 那我就来写一发. 题意:有个边长为300米的正方形 嗯  这样标号 有两个人A和S,开始的时候A.S都在1(左下角)那个位置. 两个人都要按照2. ...

  4. mac 安装mysql 报错“ERROR 2002 (HY000): Can not connect to local MySQL server through socket '/tmp/mysql.sock' (2)” 解决办法

    首先安装 homebrew 再 brew install mysql 之后连接 mysql 无论是登录还是修改初始密码都会报如下的错误 ERROR 2002 (HY000): Can not conn ...

  5. java获取当前操作系统的信息

    java获取当前操作系统的信息 JavaOS虚拟机UnixEXT  从网上收集的一些关于java获取操作系统信息的方法,现在总结一下: 1获取本机的IP地址: private static Strin ...

  6. Git教程之创建版本库(2)

    什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改.删除,Git都能跟踪,以便任何时刻都可以追踪历史,或 ...

  7. C++:静态成员

    3.7.1 静态数据成员对象是类的一个实例,每个对象都具有自己的数据成员.例如,学生类张三或李四都具有自己的学号,姓名和平均成绩.在实际使用时,常常还需要一些其他的数据项,比如学生人数.总成绩.平均成 ...

  8. C++:默认的构造函数

    注意:如果类中用户没有定义构造函数,系统会自动提供一个函数体为空的默认构造函数. 但是,只要类中定义了一个构造函数(不一定无参构造函数),系统将不再给它提供 默认的构造函数.因为,默认的构造函数被类中 ...

  9. 简化PHP开发的10个工具

    本文介绍了可以帮助简化 PHP 开发的11个项目,包括框架,类库,工具,代码. 1. CakePHP Development Framework CakePHP 是一个 PHP 的快速开发框架.它提供 ...

  10. php扩展函数调用扩展中的标准函数

    这几天在写php的扩展函数,在网上学习步骤什么的都有,一般问题也都能查到,所以就不再此啰嗦,写这篇博客的原因是因为遇到的一个问题,百度谷歌都没找到,对于初学者,这个或许有用,对于过来人,我想他们肯定也 ...