我们已经知道,fork出来的子进程和父进程谁先运行是随机的,那我们如果控制呢?可以使用wait类函数。

#include <sys/wait.h>

pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);

若成功则返回进程pid,0,若出错返回-1

进程调用上面的两个函数可能会:

  • 如果其所有子进程都还在运行,则阻塞。
  • 如果有一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回。

两个函数的区别如下:

  • 在一个子进程终止前,wait会使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
  • waitpid并不等待在其调用后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。

如果一个子进程已经终止,并且是一个僵尸进程(zombie:一个已经终止,但是其父进程尚未对其进行善后处理的进程),则wait立即返回并获取该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞而且它有多个子进程,则在其任一个子进程终止时,wait就立即返回。因为wait返回终止子进程的PID,所以它总能了解到是哪个子进程终止了。

这两个函数的参数stat_loc是一个整形指针。如果不关心终止状态,则可以将该参数设置为NULL。如果stat_loc不是NULL,则终止进程的终止状态就存放在它所指向的单元内。那我们如果根据这个整形状态字获取进程的退出状态呢?POSIX.1在<sys/wait.h>中提供了几个互斥的宏来查看:

  • WIFEXITED(stat_loc):若为正常终止子进程返回的状态,则为真。对于这种情况可以执行WEXITSTATUS(stat_loc),取子进程传给exit/_exit/_Exit的低八位。
  • WIFSIGNALED(stat_loc):若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号),对于这种情况,可使用WTERMSIG(stat_loc)获取子进程终止的信号编号。另外,有些实现定义宏WCOREDUMP(stat_loc),若已产生core文件,则返回真。
  • WIFSTOPPED(stat_loc):若为当前暂停子进程返回的状态,则为真。对于这种情况,可使用WSTOPSIG(stat_loc)获取使子进程暂停的信号编号。
  • WIFCONTINUED(stat_loc):若在作业控制暂停后已经继续的子进程返回了状态,则为真。

可以看到,wait无法控制等待哪一子进程,但是waitpid却可以等待某一个特定的子进程。对于waitpid里面的pid参数有如下解释:

  • pid == -1:等待任一子进程,此时和wait等效。
  • pid >= 0:等待进程ID与pid相等的子进程。这是我们最常使用的情况。
  • pid == 0:等待其组ID等于调用进程组ID的任一子进程。
  • pid <= -1 :等待其组ID等于pid绝对值的任一子进程。

waitpid里面的options参数使我们可以更进一步的控制waitpid的操作。此参数可以为0,或者是下面的三个常量按位或的结果:

  • WCNTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态(POSIX.1的XSI扩展)。
  • WNOHANG:若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0.
  • WUNTRACED:若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。

除了wait和waitpid外,还有一些不太常用的扩展函数:

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);

这些函数这里就不介绍了,有兴趣的可以查看man文档。

最后我们看个例子:

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

void pr_exit(int status)
{
    if (WIFEXITED(status))
        printf ( "normal termination, exit status = %dn", WEXITSTATUS(status) );
    else if (WIFSIGNALED(status))
        printf ( "abnormal termination, signal number = %d(%s)n", WTERMSIG(status),strsignal(WTERMSIG(status)));
    else if (WIFSTOPPED(status))
        printf ( "child stopped, signal number = %d(%s)n", WSTOPSIG(status), strsignal(WSTOPSIG(status)) );
}

int main(void)
{
    pid_t       pid;
    int         status;

    if ((pid = fork()) < 0)
        exit(1);
    else if (pid == 0)
        exit(7);            /* child */

    if (wait(&status) != pid)
    {
        printf ( "wait errorn" );
        exit(2);
    }
    pr_exit(status);

    if ((pid = fork()) < 0)
        exit(3);
    else if (pid == 0)
        abort();        /* generate SIGABRT */

    if (wait(&status) != pid)
    {
        printf ( "wait errorn" );
        exit(2);
    }
    pr_exit(status);

    if ((pid = fork()) < 0)
        exit(4);
    else if (pid == 0)
        status /= 0;    /* generate SIGFPE */

    if (wait(&status) != pid)
    {
        printf ( "wait errorn" );
        exit(2);
    }
    pr_exit(status);

    exit(0);
}

程序执行结果:

allan@NYC:~/test$ ./wait_test1 
normal termination, exit status = 7
abnormal termination, signal number = 6(Aborted)
abnormal termination, signal number = 8(Floating point exception)