Linux编程|Linux编程入门(16)-进程(四)等待子进程

上一篇介绍了进程的创建和退出,以及相关的系统函数。
Linux编程入门(15)-进程(三)编程
这篇主要讲讲,子进程退出后,父进程如何获取其退出状态。
有的应用程序,需要父进程知道子进程何时终止或退出,以及其返回给父进程的状态值信息。
那么,父进程在创建完成子进程后,有没有办法获知子进程的退出状态?答案是肯定的。
Linux 提供了系统函数 wait(),用于检测子进程的终止情况。
系统函数 wait()
系统函数 wait() 主要做两件事:

  • 暂停调用它的进程,直到有子进程退出。
  • 获取子进程结束时传递给 exit() 的值。
Linux编程|Linux编程入门(16)-进程(四)等待子进程
文章图片

wait() 等待调用进程的任意一个子进程终止,其函数原型为:
#include #include pid_t wait(int *wstatus);

参数 wstatus 指向的整型变量用于接收子进程的终止状态。
函数执行成功,返回终止的子进程 ID。 失败,则返回 -1。
wait() 详细的执行步骤为:
  1. 如果调用进程没有子进程,那么 wai() 会出错,返回 -1。
  2. 调用此函数的进程,如果有子进程终止,wait() 立即返回。否则,调用进程一直阻塞。
  3. 如果 wstatus 非空,则关于子进程终止的信息会通过 wstatus 指向的整型变量返回。
  4. 将终止进程的 PID 作为 wait() 返回值,并返回。
注意,如果调用者阻塞并且有多个子进程,那么任意一个子进程终止时,wait() 就立即返回。
因为 wait() 的返回值是终止进程的 PID,所以父进程能够知晓是哪一个进程终止了。若某个子进程用来完成某项任务,当任务处理完毕后退出,父进程可以通过 wait() 了解到该任务完成了,可以继续其他处理了。
关于进程的结束状态
一个进程的结束,有三种方式:
  • 正常结束。成功调用 exit(0) 或者 return 0
  • 进程出错退出。例如,由于内存耗尽而提前退出程序。程序遇到问题要退出时,可以调用 exit() 函数时传递一个非零值。
  • 被一个信号 kill 掉。信号可能来自键盘、定时器、内核或者其他进程。
父进程如何知道子进程是以何种方式退出的呢?可以通过 wait() 返回的退出状态来进行判断。
在调用 wait() 函数时,给其传递一个整型变量的地址。Linux 内核会将子进程的退出状态存储在这个变量中:
  • 子进程调用 exit() 退出,内核会把 exit 的参数值存放到整数变量中。
  • 子进程被 kill 掉,内核将信号序号存放在这个变量中。
结束状态值的构成
进程退出返回的整型状态值,实际上仅用了最低的 16 位,由 3 部分构成:
  • 8 位记录退出的值(正常退出)。
  • 1 位指明是否发生错误并产生了 core 文件(core dump)
  • 7 位记录信号编号(被信号 kill 掉,异常返回)
Linux编程|Linux编程入门(16)-进程(四)等待子进程
文章图片

终止状态可以用 中的宏来检测。其中,有 4 个宏可用来取得进程终止的原因:
  • WIFEXITED(status) , 进程正常结束,则为真。
  • WIFSIGNALED(status), 子进程被信号终止,则为真。
  • WIFSTOPPED (status) , 子进程因信号而停止,则为真。
  • WIFCONTINUED (status) , 进程收到 SIGCONT 而恢复执行 , 则为真。
编程示例
编程示例-1
编程实验,实际验证一下,子进程调用 exit() 是如何触发 wait() 返回的处理流程:
#include #include #include #include #include #include #include /* 子进程执行函数 */ void child_code(int delay) { printf("child %d here. will sleep %d seconds\n", getpid(), delay); sleep(delay); printf("child done. exit\n"); exit(17); } /* 父进程执行函数 */ void parent_code(int childpid) { int wait_ret; wait_ret = wait(NULL); printf("done waiting for %d. Wait returned: %d\n", childpid, wait_ret); }int main() { int newpid; printf("before: mypid is %d\n", getpid()); /* 创建新进程,并判断返回值 */ if((newpid = fork()) == -1) { perror("fork"); } else if(newpid == 0) { /* 子进程执行感函数 */ child_code(2); } else { /* 父进程执行感函数 */ parent_code(newpid); } }

在父进程中,控制流始于程序的开始,在 wait() 的地方阻塞。
在子进程中,控制流始于 main 函数的中部(fork 之后),然后运行 child_code() 函数,最后调用 exit() 结束。
编译,运行结果如下:
$ gcc waitdemo.c -o waitdemo $ ./waitdemobefore: mypid is 2844 child 2845 here. will sleep 2 seconds child done. exit done waiting for 2845. Wait returned: 2845

由程序的执行结果来看,这段程序验证了 wait() 的函数的两个特征:
  • wait() 函数会阻塞调用它的程序,直到子进程结束。
  • wait() 函数会返回结束进程的 PID。
编程示例-2
通过编程来显示子进程的退出状态,代码 waitdemo2.c 如下:
#include #include #include #include #include #include #include /* 子进程执行函数 */ void child_code(int delay) { printf("child %d here. will sleep %d seconds\n", getpid(), delay); /* 子进程执行函数 */ sleep(delay); printf("child done. exit\n"); /* 子进程退出 */ exit(17); }void parent_code(int childpid) { int wait_ret; int child_status; int high_8, low_7, bit_7; wait_ret = wait(&child_status); printf("done waiting for %d. Wait returned: %d\n", childpid, wait_ret); /* 解析子进程退出状态信息 */ high_8 = child_status >> 8; low_7 = child_status & 0x7F; bit_7 = child_status & 0x80; printf("status: exit = %d, sig = %d, core %d\n", high_8, low_7, bit_7); }int main() { int newpid; printf("before: mypid is %d\n", getpid()); if((newpid = fork()) == -1) { perror("fork"); } else if(newpid == 0) { child_code(5); } else { parent_code(newpid); } }

【Linux编程|Linux编程入门(16)-进程(四)等待子进程】编译、运行。先看看正常运行情况,退出状态从子进程中获取(exit() 的参数):
$ gcc waitdemo2.c -o waitdemo2 $ ./waitdemo2before: mypid is 5506 child 5507 here. will sleep 5 seconds child done. exit done waiting for 5507. Wait returned: 5507 status: exit = 17, sig = 0, core 0

然后再看看异常结束情况。后台运行 waitdemo2,使用 kill 指令给子进程发送 SIGTERM 信号,内核将该信号编号填充到状态字中:
$ ./waitdemo2 &/* 后台运行程序 */ before: mypid is 5592 child 5593 here. will sleep 5 secondskill 5593/* 给子进程发送信号 */done waiting for 5593. Wait returned: 5593 status: exit = 0, sig = 15, core 0/* 显示状态信息 */

扩展
假如一个进程有多个子进程,只要有一个子进程退出,wait() 就返回。那么要等待一个指定的进程终止(需要直到该进程的 PID),该如何处理?
一种方式是,根据 wait() 返回的进程 ID 与期望的进程 ID 比较。如果终止进程不是所期望的,则再次调用 wait() 函数。
还有一种方式,使用专门的系统函数。Linux 系统提供了一个系统调用,可以用来等待特定的子进程。
该系统函数为 waitpid(), 其原型为:
#include #include pid_t waitpid(pid_t pid, int *wstatus, int options);

参数 pid,表示需要等待的具体子进程,具体含义如下:
  • pid > 0,表示等待进程 ID 为 pid 的子进程。
  • pid = 0,等待与调用进程(父进程)同一个进程组的所有子进程。
  • pid = -1,等待任意进程。
  • pid < -1,等待进程组标识符与 pid 绝对值相等的所有子进程。
可以得知,wait(&status) 等价于 waitpid(-1, &status, 0)
参数 options,是一个位掩码,可以包含如下标志位:
  • WUNTRACED 返回终止子进程的信息和因信号终止的子进程信息。
  • WCONTINUED 返回因收到 SIGCONT 信号而恢复执行的子进程状态信息。
  • WNOHANG 无子进程退出,立即返回,不阻塞父进程。
小结
本文主要介绍了父进程如何等待子进程退出,以及获取进程退出状态相关的内容。
  • 介绍了系统函数 wait() 以及其内部执行的流程。
  • 进程退出状态的构成,以及进程退出状态如何检测。
  • 通过实例演示如何运用 wait() 函数。
  • 扩展介绍了一个可以等待指定进程的系统函数 waitpid()
下次文章,以一个示例来综合运用前面几篇学过的内容,欢迎订阅查看。
公众号【一起学嵌入式】,干货资料首先送达
Linux编程|Linux编程入门(16)-进程(四)等待子进程
文章图片

    推荐阅读