c - return 状态/值的适当范围

c - Proper range of return status / value

最近在看一本关于linux编程的书时,收到这样一条信息:

The status argument given to _exit() defines the termination status of the process, which is available to the parent of this process when it calls wait(). Although defined as an int, only the bottom 8 bits of status are actually made available to the parent. And only 0 ~ 127 is recommanded to use, because 128 ~ 255 could be confusing in shell due to some reason. Due to that -1 will become 255 in 2's complement.

以上是关于子进程的退出状态。

我的问题是:


更新 - 通过 wait() / waitpid() 获取状态:

我在书(TLPI)中阅读了更多的chps,发现return status & wait()/waitpid()中有更多值得一提的技巧,我应该在询问之前阅读更多的chps这个问题。无论如何,我已经自己添加了一个答案来描述它,以防将来对某人有所帮助。

Why the parent process only get the 8 bits of the child process's exit status?

因为POSIXsays so。 POSIX 这么说是因为原始 Unix 就是这样工作的,许多从它派生并模仿它的操作系统继续工作。

What about return value of normal functions?

他们没有关系。 Return 合理就好。 -1 与任何其他值一样好,实际上是在大量标准 C 和 POSIX API 中指示错误的标准方法。

来自@n.m的回答。很好

不过后来看书chps多了(TLPI),发现return status & wait()/waitpid()里面还有更多值得一提的trick,可能是子进程在退出时不能使用完整的 int 的另一个重要或根本原因。

等待状态

基本上:

  • 子进程应该以 1 个字节范围内的值退出,该值设置为 wait() / waitpid() 的状态参数的一部分,
  • 并且仅使用状态的 2 LSB 字节,

byte usage of status:

    event                   byte 1                  byte 0
    ============================================================
    * normal termination    exit status (0 ~ 255)   0
    * killed by signal      0                       termination signal (!=0)
    * stopped by signal     stop signal             0x7F
    * continued by signal               0xFFFF
    * 

剖析return状态:

header 'sys/wait.h',  defines a set of macros that help to dissect a wait status,

macros:
* WIFEXITED(status)
    return true if child process exit normally,
* 
* WIFSIGNALED(status)
    return true if child process killed by signal,
* WTERMSIG(status)
    return signal number that terminate the process,
* WCOREDUMP(status)
    returns ture if child process produced a core dump file,
    tip:
        this macro is not in SUSv3, might absent on some system,
        thus better check whether it exists first, via:
            #ifdef WCOREDUMP
                // ...
            #endif
* 
* WIFSTOPPED(status)
    return true if child process stopped by signal,
* WSTOPSIG(status)
    return signal number that stopp the process,
* 
* WIFCONTINUED(status)
    return true if child process resumed by signal SIGCONT,
    tip:
        this macro is part of SUSv3, but some old linux or some unix might didn't impl it,
        thus better check whether it exists first, via:
            #ifdef WIFCONTINUED
                // ...
            #endif
* 

示例代码

wait_status_test.c

// dissect status returned by wait()/waitpid()
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>

#define SLEEP_SEC 10 // sleep seconds of child process,

int wait_status_test() {
    pid_t cpid;

    // create child process,
    switch(cpid=fork()) {
        case -1: // failed
            printf("error while fork()\n");
            exit(errno);
        case 0: // success, child process goes here
            sleep(SLEEP_SEC);
            printf("child [%d], going to exit\n",(int)getpid());
            _exit(EXIT_SUCCESS);
            break;
        default: // success, parent process goes here
            printf("parent [%d], child created [%d]\n", (int)getpid(), (int)cpid);
            break;
    }

    // wait child to terminate
    int status;
    int wait_flag = WUNTRACED | WCONTINUED;
    while(1) {
        if((cpid = waitpid(-1, &status, wait_flag)) == -1) {
            if(errno == ECHILD) {
                printf("no more child\n");
                exit(EXIT_SUCCESS);
            } else {
                printf("error while wait()\n");
                exit(-1);
            }
        }
        // disset status
        printf("parent [%d], child [%d] ", (int)getpid(), (int)cpid);
        if(WIFEXITED(status)) { // exit normal
            printf("exit normally with [%d]\n", status);
        } else if(WIFSIGNALED(status)) { // killed by signal
            char *dumpinfo = "unknow";
            #ifdef WCOREDUMP
                dumpinfo = WCOREDUMP(status)?"true":"false";
            #endif
            printf("killed by signal [%d], has dump [%s]\n", WTERMSIG(status), dumpinfo);
        } else if(WIFSTOPPED(status)) { // stopped by signal
            printf("stopped by signal [%d]\n", WSTOPSIG(status));
        #ifdef WIFCONTINUED
        } else if(WIFCONTINUED(status)) { // continued by signal
            printf("continued by signal SIGCONT\n", WSTOPSIG(status));
        #endif
        } else { // this should never happen
            printf("unknow event\n");
        }
    }

    return 0;
}

int main(int argc, char *argv[]) {
    wait_status_test();
    return 0;
}

编译:

gcc -Wall wait_status_test.c

执行:

  • ./a.out 并等待它正常终止,在 fork(),
  • 之后打印子进程 id
  • ./a.out,然后kill -9 <child_process_id>在它完成休眠之前,
  • ./a.out,然后kill -STOP <child_process_id>在它结束睡眠之前,然后kill -CONT <child_process_id>恢复它,