凌云的博客

行胜于言

关于 Linux 进程结束状态

分类:linux| 发布时间:2015-11-18 08:00:00


前言

之前有人问我为什么当一个进程返回 1 时, waitpid 获得的 status 是 256 。 而在 shell 中:

echo $?

获得的是 1 这个正确的结果呢? 当时模糊的记得在 APUE 中有提到过相关的知识。 于是就说是因为进程结束 status 是分了几块的,某几个位代表了正常退出, 而另外几个位是代表异常退出。 shell 会处理结束状态将真正需要的结果存入 $? 中。

后来查了 APUE 以及相关文档(man waitpid)确认了我的想法是正确的。 同时写了一些 demo 来验证我的想法,于是便有了本文。

试验

首先复习下 waitpid 的定义:

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

pid 为要等待的进程号,status 为返回的状态,而 options 在我们的例子中都是 0。

然后写几个用于测试进程结束状态的程序:

// return1.c
int main()
{
    return 1;
}
// vim: set et ts=4 sts=4 sw=4:

这个例子非常简单,就是返回 1 。

// return2.c
int main()
{
    return 2;
}
// vim: set et ts=4 sts=4 sw=4:

这个同样是简单的例子,返回 2 。

// sigfpe.c
int main()
{
	int i = 3;
	int j = 0;
	int z = i / j;
	return 0;
}

这是一个非正常退出的例子,会引发一个 SIGFPE 的异常。

接下来是一个执行程序并输出 waitpid 返回结果的例子:

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

void pr_exit(int status)
{
    if (WIFEXITED(status)) {
        printf("normal termination, exit status = %d\n",
                WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("abnormal termination, signal number = %d%s\n",
                WTERMSIG(status),
                WCOREDUMP(status) ? " (core file generated)" : "");
    } else if (WIFSTOPPED(status)) {
        printf("child stopped, signal number = %d\n",
                WSTOPSIG(status));
    }
}

int main(int argc, char *argv[])
{
	int status;
    pid_t pid;

	if (argc < 2) {
		printf("command-line argument required\n");
        exit(0);
	}

    if ((pid = fork()) < 0) {
		printf("fork error\n");
        exit(0);
    } else if (pid == 0) {
        if (execl(argv[1], argv[1], (char*)0) < 0) {
            printf("execl error\n");
            return 0;
        }
    }

    if (waitpid(pid, &status, 0) < 0) {
        printf("waitpid error\n");
        return 0;
    }

    printf("status = %d\n", status);
	pr_exit(status);
	exit(0);
}
// vim: set et ts=4 sts=4 sw=4:

这个程序主要是通过 fork 和 execl 执行子进程,然后用 waitpid 得到其结束状态。 最后输出其结果。 需要注意的是,我们除了输出其结果外还在 pr_exit 中使用了 WIFEXITED、WIFSIGNALED 和 WIFSTOPPED 来判断子进程是正常退出还是异常退出。 如果是正常退出使用 WEXITSTATUS 来获取返回码,如果是异常退出使用 WTERMSIG 来获取信号值。

首先来看看执行在 shell 下执行上面三个测试程序的结果:

$  ./return1
$  echo $?
1
$  ./return2
$  echo $?
2
$ ./sigfpe
[1]    7191 floating point exception (core dumped)  ./sigfpe
$  echo $?
136

可以看到在正常结束的情况下 echo $? 会返回正确的值。

再看看通过我们自己写的 prexit 程序获取到的结果:

$ ./prexit ./return1
status = 256
normal termination, exit status = 1
$ ./prexit ./return2
status = 512
normal termination, exit status = 2
$ ./prexit ./sigfpe
status = 136
abnormal termination, signal number = 8 (core file generated)

可以看到正常结束的情况下,返回 1 时,status 为 256。 返回 2 时,status 为 512 。

结束状态的组成

在 sys/wait.h 和 bits/waitstatus.h (在我的 Ubuntu 系统中,这个文件位于 /usr/include/x86_64-linux-gnu/ 不同系统可能不一样) 中可以看到 WIFEXITED WEXITSTATUS 等宏的实现。

比如:

// sys/wait.h
# define WTERMSIG(status)   __WTERMSIG (__WAIT_INT (status))
# define WEXITSTATUS(status)    __WEXITSTATUS (__WAIT_INT (status))
# define WCOREFLAG      __WCOREFLAG

#   define __WAIT_INT(status)   (*(const int *) &(status))

// bits/waitstatus.h
#define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)
#define __WTERMSIG(status)  ((status) & 0x7f)
#define __WCOREFLAG     0x80

由此可以看到 status 主要有三部分组成:

  • bits 0-6 为 termination signal
  • bits 7 为 core dump flags
  • bits 8-15 为 exit status
| status | 0 0 0 0 0 0 0 0 |        0           | 0 0 0 0 0 0 0      |
| bits   |     8 - 15      |        7           |     0 - 6          |
| mean   |   exit status   | coredump generated | termination signal |

需要注意的是这是当调用 waitpid 时 第三个参数 options 为 0 时的情况。 如果此参数不为 0 ,情况会有所变化,具体请通过 man waitpid 查看。

参考

  • man waitpid
  • APUE
  • 本文的源代码可以从 github 获取