当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。对于这种信号的系统默认动作是忽略它)。

调用wait或waitpid的进程可能会发生的情况:

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

如果进程由于接收到SIGCHLD信号而调用wait,则可期望wait会立即返回。但是如果在任意时刻调用wait,则进程可能会阻塞。

#include <sys/wait.h>

pid_t wait( int *statloc );
返回值:若成功则返回已终止子进程ID,若出错则返回-1 pid_t waitpid( pid_t pid, int *statloc, int options );
返回值:若成功则返回状态改变的子进程ID,若出错则返回-1,若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0

这两个函数的区别如下:

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

如果一个子进程已经终止,并且是一个僵死进程,则wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞而且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的进程ID,所以它总能了解是哪一个子进程终止了。

这两个函数的参数statloc是一个整型指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。

依据传统,这两个函数返回的整型状态字是由实现定义的。其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了一个core文件等。POSIX.1规定状态用定义在<sys/wait.h>中的各个宏来查看。有四个互斥的宏可用来取得进程终止的原因,它们的名字都以WIF开始。基于这四个宏中哪一个值为真,就可选用其他宏(表8-1说明栏中下划线标注的宏)来取得终止状态、信号编号等。这四个互斥的宏示于表8-1中。

表8-1 检查wait和waitpid所返回的终止状态的宏

说明

         WIFEXITED(status)     若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送给exit、_exit或_Exit参数的低8位
         WIFSIGNALED(status)    若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使子进程终止的信号编号。另外,有些实现定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真
        WIFSTOPPED(status)    若为当前暂停子进程的返回状态,则为真。对于这种情况,可执行WSTOPSIG(status),取使子进程暂停的信号编号
        WIFCONTINUED(status)    若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.1的XSI扩展;仅用于waitpid。)

 

程序清单8-3 打印exit状态的说明

[root@localhost apue]# cat prog8-3.c
#include "apue.h"
#include <sys/wait.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),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if(WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}

程序清单8-4 演示不同的exit值(调用prog8-3中的pr_exit函数)

[root@localhost apue]# cat prog8-4.c
#include "apue.h"
#include <sys/wait.h> int
main(void)
{
pid_t pid;
int status; if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
exit(7); /* child */ if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */ if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
abort(); if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */ if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 generates SIGPE */ if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */ exit(0);
}

运行该程序可得:

[root@localhost apue]# ./prog8-4
normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8

不幸的是,没有一种可移植的方法将WTERMSIG得到的信号编号映射为说明性的名字。我们必须查看<signal.h>头文件才能知道SIGABRT的值是6,SIGFPE的值是8.

正如前面所述,如果一个进程有几个子进程,那么只要有一个子进程终止,wait就返回。如果要等待一个指定的进程终止(如果知道要等待进程的ID),那么该如何做呢?POSIX.1定义了waitpid函数以提供这种功能(以及其他一些功能)。

对于waitpid函数中pid参数的作用解释如下:

pid == –1   等待任一子进程。就这一方面而言,waitpid与wait等效。
pid > 0   等待其进程ID与pid相等的子进程。
pid == 0   等待其组ID等于调用进程组ID的任一子进程。
pid < –1   等待其组ID等于pid绝对值的任一子进程。

waitpid函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由status指向的存储单元中。对于wait,其唯一的出错是调用进程没有子进程(函数调用被一个信号中断时,也可能返回另一种出错)。但是对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程则都将出错。

options参数使我们能进一步控制waitpid的操作。此参数可以是0,或者是表8-2中常量按位“或”运算的结果。

表8-2 waitpid的options常量

常量

说明

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

 

waitpid函数提供了wait函数没有提供的三个功能:

(1)waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。

(2)waitpid提供了一个wait的非阻塞版本。有时用户希望取得一个子进程的状态,但不想阻塞。

(3)waitpid支持作业控制(利用WUNTRACED和WCONTINUED选项)。

如果一个进程fork一个子进程,但不要等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的技巧是调用fork两次。

程序清单8-5 调用fork两次以避免僵死进程

[root@localhost apue]# cat prog8-5.c
#include "apue.h"
#include <sys/wait.h> int
main(void)
{
pid_t pid; if ((pid = fork()) < 0)
{
err_sys("fork error");
}
else if (pid == 0) /* first child */
{
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */ /*
* We're the second child; our parent become init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* we're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error"); /*
* We're the parent ( the original process ); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}

第二个子进程调用sleep以保证在打印父进程ID时第一个子进程已终止。在fork之后,父、子进程都可继续执行,并且我们无法预知哪一个会先执行。在fork之后,如果不使第二个子进程休眠,那么它可能比其父进程先执行,于是它打印的父进程ID将是创建它的父进程,而不是init进程(进程ID 1)。

执行结果:

[root@localhost apue]# ./prog8-5
[root@localhost apue]# second child, parent pid = 1

注意,当原先的进程(也就是exec本程序的进程)终止时,shell打印其提示符,这在第二个子进程打印其父进程ID之前。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

进程控制之wait和waitpid函数的更多相关文章

  1. linux 中的进程wait()和waitpid函数,僵尸进程详解,以及利用这两个函数解决进程同步问题

    转载自:http://blog.sina.com.cn/s/blog_7776b9d3010144f9.html 在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / wait ...

  2. 进程控制之wait3和wait4函数

    大多数UNIX系统实现提供了另外两个函数wait3和wait4.它们提供的功能比POSIX.1函数wait.waitpid和waitid所提供的功能要多一个,这与附加参数rusage有关.该参数要求内 ...

  3. wait/waitpid函数与僵尸进程、fork 2 times

    一.僵尸进程 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程, ...

  4. linux进程及进程控制

    Linux进程控制   程序是一组可执行的静态指令集,而进程(process)是一个执行中的程序实例.利用分时技术,在Linux操作系统上同时可以运行多个进程.分时技术的基本原理是把CPU的运行时间划 ...

  5. Linux C 程序 进程控制(17)

    进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...

  6. UNIX环境高级编程——进程控制

    一.进程标识符 ID为0的进程是调度进程,常常被称为交换进程.该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程.进程ID 1通常是init进程,在自举过程结束时由内核调用.ini ...

  7. linux_api之进程控制

    本篇索引: 1.引言 2.进程标识 3.多进程 4.fork函数 5.vfork函数 6.exit函数 7.wait和waitpid函数 8.竞态 9.exec函数族 10.进程状态 11.syste ...

  8. 【转载】linux进程及进程控制

    Linux进程控制   程序是一组可执行的静态指令集,而进程(process)是一个执行中的程序实例.利用分时技术,在Linux操作系统上同时可以运行多个进程.分时技术的基本原理是把CPU的运行时间划 ...

  9. 《UNIX环境高级编程》(APUE) 笔记第八章 - 进程控制

    8 - 进程控制 Github 地址 1. 进程标识 每个进程都有一个非负整型表示的 唯一进程 ID .进程 ID 是可复用的(延迟复用算法). ID 为 \(0\) 的进程通常是调度进程,常常被称为 ...

随机推荐

  1. Yii 1.1 DAO绑定参数实例

    <?php $sql = "SELECT * FROM admin_user WHERE user_name=:uname AND password LIKE :c"; $c ...

  2. rm 注意

    软连接ln -s lnfile file rm -rf lnfile只是删除lnfile ln -s lndir dir rm -rf lndir 删除链接 rm -rf lndir/删除目录下文件

  3. Hadoop概述

    本章内容 什么是Hadoop Hadoop项目及其结构 Hadoop的体系结构 Hadoop与分布式开发 Hadoop计算模型—MapReduce Hadoop的数据管理 小结 1.1 什么是Hado ...

  4. jquery cookie用法(获取cookie值,删除cookie)

    1.引入文件 2.具体操作 $.cookie('the_cookie'); // 读取 cookie $.cookie('the_cookie', 'the_value'); // 存储 cookie ...

  5. S3

    S3是Amazon EMR的一部分,它提供了一些Wikipedia的浏览统计数据,这些浏览数据的格式便于Spark测试.

  6. ntpdate server时出错原因及解决

    错误1.Server dropped: Strata too high 在ntp客户端运行ntpdate serverIP,出现no server suitable for synchronizati ...

  7. AndroidStudio debug

    1. view as text

  8. Struts ForwardAction Example

    In Struts MVC model, you have to go thought the Action Controller to get a new view page. In some ca ...

  9. zoj3591 Nim(Nim博弈)

    ZOJ 3591 Nim(Nim博弈) 题目意思是说有n堆石子,Alice只能从中选出连续的几堆来玩Nim博弈,现在问Alice想要获胜有多少种方法(即有多少种选择方式). 方法是这样的,由于Nim博 ...

  10. 结构类模式(六):享元(Flyweight)

    定义 运用共享技术有效的支持大量细粒度的对象. 两个状态 内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的. 外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保 ...