当一个进程正常或异常终止时,内核就向其父进程发送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. Chapter 5 Convert Image Set To LevelDB/LMDB

    Caffe中convert_imageset projrct将图像数据转换成Caffe能读取的数据格式leveldb/lmdb 1.添加命令参数 在main函数中添加命令参数,内容和位置如下: #if ...

  2. js运动 模仿淘宝幻灯

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  3. 转载-SQL中的where条件,在数据库中提取与应用浅析

    1        问题描述 一条SQL,在数据库中是如何执行的呢?相信很多人都会对这个问题比较感兴趣.当然,要完整描述一条SQL在数据库中的生命周期,这是一个非常巨大的问题,涵盖了SQL的词法解析.语 ...

  4. Bezier曲线的原理 及 二次Bezier曲线的实现

    原文地址:http://blog.csdn.net/jimi36/article/details/7792103 Bezier曲线的原理 Bezier曲线是应用于二维图形的曲线.曲线由顶点和控制点组成 ...

  5. ASA与PIX的区别

    很多年来,Cisco PIX一直都是Cisco确定的防火墙.但是在2005年5月,Cisco推出了一个新的产品——适应性安全产品(ASA,Adaptive Security Appliance).不过 ...

  6. Python基础 条件、循环

    1.条件语句 Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块. if if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的 ...

  7. LRESULT与wParam和lParam的问题

    在微软vc提供的头文件中有定义在winnt.h中typedef long LONG;在windef.h中typedef LONG LRESULT; 所以LRESULT就是long,也就是长整形之所以取 ...

  8. php,Allowed memory size of 8388608 bytes exhausted (tried to allocate 1298358 bytes)

    修改apache上传文件大小限制 PHP上传文件大小限制解决方法: 第一: 在php.ini里面查看如下行: upload_max_filesize = 8M    post_max_size = 1 ...

  9. My集合框架第二弹 二叉树的实现

    package com.wpr.collection; import java.util.NoSuchElementException; public class BinarySearchTree&l ...

  10. 【转】android onNewIntent()触发机制及注意事项

    一.onNewIntent() 在IntentActivity中重写下列方法:onCreate onStart onRestart  onResume  onPause onStop onDestro ...