UNIX环境编程学习笔记(21)——进程管理之获取进程终止状态的 wait 和 waitpid 函数...

lienhua34
2014-10-12

当一个进程正常或者异常终止时,内核就向其父进程发送 SIGCHLD信号。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用的函数(信号处理程序)。对于这种信号的系统默认动作是忽略它。

在文档“进程控制三部曲”中,我们讲的第三部曲是使用 wait 函数来获取终止子进程的终止状态。那么,有几个问题我们这里需要详细的学习一下。

1. 父进程一定能够获取到子进程的终止状态吗?如果子进程在父进程调用 wait 函数前就终止了,怎么办?

2. 如果父进程没有获取子进程的终止状态,那会发生什么?

3. 如果父进程有多个子进程,那么获取的是哪个子进程的终止状态呢?

对于第一个问题的回答是:内核为每个终止进程保存了一定量的信息,包括进程 ID、该进程的终止状态、以及该进程使用的 CPU 时间总量。所以,当终止进程的父进程调用 wait 或者 waitpid 函数,即可获取到这些信息。当父进程获取终止进程的终止信息之后,内核就可以释放终止进程所使用的所有存储区、关闭其所有打开的文件。

在 UNIX 术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的相关信息)的进程被称为僵尸进程(zombie)。如果编写一个长期运行的程序,调用 fork 产生子进程之后,需要调用 wait 来获取这些子进程的终止状态,否则这些子进程在终止之后将会变成僵尸进程。(后面会讲到用一个技巧以避开父进程调用 wait 获取所有子进程的终止状态。)

那么如果那些被 init 进程领养的进程在终止之后会不会也变成僵尸进程?答案是:不会。因为 init 进程无论何时只要有一个子进程终止,init 就会调用 wait 函数获取其终止状态。

那么关于上面的第三个问题,我们得通过详细学习 wait 和 waitpid 函数才能都做出回答了。

1 wait 函数

#include <sys/wait.h>

pid_t wait(int *statloc);

返回值:若成功则返回终止进程的ID,若出错则返回-1

参数 statloc 是一个整形指针。如果 statloc 不是一个空指针,则终止进程的终止状态将存储在该指针所指向的内存单元中。如果不关心终止状态,可以将 statloc 参数设置为空。

调用 wait 函数时,调用进程将会出现下面的情况:

• 如果其所有子进程都还在运行,则阻塞。

• 如果一个子进程已经终止,正等待父进程获取其终止状态,则获取该子进程的终止状态然后立即返回。

• 如果没有任何子进程,则立即出错返回。

wait 函数获取的终止状态是一个 int 型数值,那我们如何得到具体的终止信息呢?POSIX.1 规定终止状态用定义在 <sys/wait.h> 中的各个宏来参看。有四个互斥的宏可以用来得到进程的终止原因。这四个宏见表 1,

表 1: 检查终止状态的宏
说明
WIFEXITED(status)若正常终止子进程返回的状态,则为真。此种情况,调用 WEXITSTATUS(status) 可以获取子进程调用 exit 函数的参数的低 8位。
WIFSIGNALED(status)若为异常终止子进程返回的状态,则为真。此种情况,调用 WTERMSIG(status) 取得使子进程终止的信号编号。
WIFSTOPPED(status)若为当前暂停子进程返回的状态,则为真。
WIFCONTINUED(status)若在作业控制暂停后已经继续的子进程返回了状态,则为真。

下面我们来看一下打印终止进程状态说明的例子,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>extern void print_exit(int status);int
main(void)
{pid_t pid;int status;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(8);}if (wait(&status) != pid) {printf("wait error: %s\n", strerror(errno));exit(-1);}print_exit(status);if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {abort();}if (wait(&status) != pid) {printf("wait error: %s\n", strerror(errno));exit(-1);}print_exit(status);exit(0);
}void print_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\n",WTERMSIG(status));}
}
waitdemo.c

编译该程序,生成并运行 waitdemo 文件,

lienhua34:demo$ gcc -o waitdemo waitdemo.c
lienhua34:demo$ ./waitdemo
normal termination, exit status = 8
abnormal termination, signal number =6

下面我们再来看一个产生僵尸进程的示例,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>int
main(void)
{pid_t pid;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(0);}printf("fork child process:%d\n", pid);if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(0);}printf("fork child process:%d\n", pid);if ((pid = wait(NULL)) < 0) {printf("wait error: %s\n", strerror(errno));exit(-1);}printf("get child process(%d) termination status\n", pid);sleep(5);printf("parent process exit\n");exit(0);
}
zombiedemo.c

我们在父进程最后 sleep(5) 让父进程睡眠 5 秒钟是避免父进程太早退出,我们观察不到僵尸进程。我们编译该程序文件,生成并执行文件

lienhua34:demo$ ps -A -ostat,pid | grep -e '[Zz]'
Z     1725
lienhua34:demo$ gcc -o zombiedemo zombiedemo.c 
lienhua34:demo$ ./zombiedemo &
[1] 2961
lienhua34:demo$ fork child process:2962
fork child process:2963
get child process(2963) termination status
ps -A -ostat,pid | grep -e '[Zz]'
Z     1725
Z     2962
lienhua34:demo$ parent process exit
ps -A -ostat,pid | grep -e '[Zz]'
Z     1725
[1]+  完成                  ./zombiedemo

ps 命令打印的进程中,Z 表示僵尸进程。从上面的运行结果,我们看到父进程(ID:2961)fork 了两个子进程(ID:2962 和 2963),然后调用了 wait 函数获取了子进程 2963 的终止状态,于是子进程 2962 便成为了僵尸进程。但是,当父进程也退出时,生成僵尸进程的子进程 2962 也被内核释放。

2 waitpid 函数

只要有一个子进程终止,wait 函数就会返回。那么如果父进程希望等待特定的子进程终止,该怎么办?UNIX 提供了提供这样功能的 waitpid 函数。

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *statloc, int options);

返回值:若成功则返回终止进程ID或0;若出错则返回-1

其中 statloc 参数跟 wait 函数一样,获取终止子进程的状态信息。waitpid 函数通过 pid 参数来控制父进程希望获取特定进程的终止状态信息,

• pid==-1:等待任一子进程,与 wait 函数等效。

• pid>0:等待其进程 ID 与 pid 相等的子进程。

• pid==0:等待其组 ID 等于调用进程组 ID 的任一子进程。(我们这里不学习进程组)

• pid<-1:等待其组 ID 等于 pid 绝对值的任一子进程。

waitpid 函数返回终止子进程的进程 ID。如果指定的进程或进程组不存在,或者参数 pid 指定的进程不是调用进程的子进程则都将出错。waitpid 函数跟 wait 函数的另一个不同之处在于,wait 函数可能会使调用进程阻塞,而 waitpid 函数可以通过第三个参数 options 来控制调用进程是否要阻塞。options 参数可以是 0,也可以是表 2 中各常量或运算的结果。

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

关于 options 用于作业控制 的两个 常量 WCONTINUED 和 WUNTRACED,我们这里不学习,我们只关心常量 WNOHANG。我们来看一个例子。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>int
main(void)
{pid_t pid1, pid2;pid_t waitpidRes;if ((pid1 = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid1 == 0) {sleep(3);printf("child process %d exit\n", getpid());exit(0);}if ((pid2 = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid2 == 0) {printf("child process %d exit\n", getpid());exit(0);}if ((waitpidRes = waitpid(pid1, NULL, 0)) == pid1) {printf("get terminated child process %d.\n", waitpidRes);} else if (waitpidRes < 0) {printf("waitpid error: %s\n", strerror(errno));exit(-1);} else {printf("waitpid return 0\n");}printf("parent process exit\n");exit(0);
}
waitpiddemo.c

在上面的程序中,我们在第一个子进程中 sleep(3) 让该子进程睡眠 3秒,以便在父进程调用 waitpid 函数时该子进程尚未结束。编译该程序,生成并执行 waitpiddemo 文件,

lienhua34:demo$ gcc -o waitpiddemo waitpiddemo.c
lienhua34:demo$ ./waitpiddemo
child process 2972 exit
child process 2971 exit
get terminated child process 2971.
parent process exit

从上面的运行结果,我们可以看到父进程阻塞等待子进程 2971 终止。我们如果把上面程序的 waitpid 函数第三个参数 options 改为 WNOHANG,看一下其实际运行结果。

lienhua34:demo$ gcc -o waitpiddemo waitpiddemo.c
lienhua34:demo$ ./waitpiddemo
waitpid return 0
parent process exit
child process 2982 exit
lienhua34:demo$ child process 2981 exit

从上面的运行结果,我们可以看出父进程调用 waitpid 函数时,子进程2981 尚未终止,于是 waitpid 函数没有阻塞父进程,直接返回 0.

3 避免调用大量WAIT函数来防止僵尸进程的技巧

前面讲到僵尸进程时,我们提到要编写一个长期运行的程序,要避免出现大量的僵尸情况,就需要每次 fork 出一个子进程时都需要调用 wait 函数来等待子进程的结束以便处理该子进程的终止状态信息。但是,我们每次 fork 都要调用一个 wait 函数,实在是太麻烦了。

于是,我们就希望每次调用 fork 时不需要 wait 等待子进程终止,也不希望子进程处于僵死状态直到程序结束。这里提供一个实现此要求的技巧:调用 fork 两次。我们来看下面的例子:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>int
main(void)
{pid_t pid;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid > 0) {printf("first child process: %d, parent process: %d\n", getpid(), getppid());exit(0);}sleep(2);printf("second child process: %d, parent process: %d\n", getpid(), getppid());exit(0);}if (wait(NULL) < 0) {printf("wait error: %s\n", strerror(errno));exit(-1);}printf("parent process %d exit\n", getpid());exit(0);
}
nozombiedemo.c

在上面程序中,在第一个子进程中 fork 处第二个子进程之后并终止第一个子进程。编译该程序,生成并执行文件 nozombiedemo,

lienhua34:demo$ gcc -o nozombiedemo nozombiedemo.c
lienhua34:demo$ ./nozombiedemo
first child process: 2471, parent process: 2470
parent process 2470 exit
lienhua34:demo$ second child process: 2472, parent process: 1

从上面的运行结果,我们看到第一个子进程 2471 终止后,其子进程2472 的父进程 ID 变成了 1(即 init 进程)。前面我们提到过,父进程为 init进程的所有进程在终止时都会被 init 进程获取其终止状态,从而不会变成僵尸进程。于是,通过上面的 fork 两次的技巧,我们就可以实现创建一个新进程,不需要等待该新进程终止,也不担心该新进程会变成僵尸进程。

(done)

转载于:https://www.cnblogs.com/lienhua34/p/4021272.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/460034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android代码导入有错误,android新项目导入后进行编译出现cmake错误

问题描述使用as加载并且gradle sync 提示&#xff1a;SIMPLE:error configure同时使用make project提示问题出现的环境背景及自己尝试过哪些方法已经尝试更换过ndk 调整target brinary 然后完全clean project等操作相关代码// 请把代码文本粘贴到下方(请勿用图片代替代码)使用g…

BZOJ1652 [Usaco2006 Feb]Treats for the Cows

蒟蒻许久没做题了&#xff0c;然后连动规方程都写不出了。 参照iwtwiioi大神&#xff0c;这样表示区间貌似更方便。 令f[i, j]表示i到j还没卖出去&#xff0c;则 f[i, j] max(f[i 1, j] v[i] * T, f[i, j - 1] v[j] * T) &#xff08;←这样用推的方式更好想一点。。&#…

git+jekyll部署备忘

github&#xff0c;会自动帮忙编译jekyll编写的文件&#xff0c;只要将文件放到gh-pages分支 (或者使用官方教程的二级域名方式&#xff0c;项目名字&#xff0c;可以随便起&#xff0c;官网的例子是 用户名.github.com 作为项目名字&#xff0c;可以使用 用户名.github.com …

搜索 由浅入深 之一 水题

搜索很重要&#xff0c;是很难学的算法&#xff0c;能看懂很简单&#xff0c;但是要想真正做出题来就比较困难了&#xff0c;那么&#xff0c;我们现在就水题开始研究搜索。 水题之&#xff1a; 1024: [SCOI2009]生日快乐 Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 830 …

android studio创建文件,如何在Android Studio中创建File Templates

标签&#xff1a; File Template Android Studio我发现一个可以让写程序变得简单的方法&#xff0c;那就是自定义文件模板(Custom File Templates).那么什么是File Templates呢&#xff1f;说白了&#xff0c;就是一个已经包含一部分代码的源文件如何创建File Templates首先&am…

odoo 中多币种处理(外币处理)

2019独角兽企业重金招聘Python工程师标准>>> 1. odoo多币种处理 http://www.cnblogs.com/godzone/archive/2012/11/05/2754436.html 2. OpenERP的价格表&#xff08;Pricelist&#xff09;机制: http://www.chinamaker.net/html/2011/study_1123/78.html 3. odoo …

h5 android数字键盘,【笔记】移动端H5数字键盘input type=number的处理(IOS和Android)...

在Vue中的项目&#xff0c;基于VUX-UI开发&#xff0c;一个常见的需求&#xff1a;1、金额输入框2、弹出数字键盘3、仅支持输入两位小数&#xff0c;限制最大11位数&#xff0c;不允许0开头第一&#xff0c;首先想到额就是在VUX-UI中制定typenumber。--不可行VUX中的文档和代码…

Sybase数据库应用系统调优的五大领域

Sybase数据库应用系统调优的五大领域 2011/3/14/13:49来源&#xff1a;慧聪it网本 文以“某大型商业银行的网上银行系统”这一很具有典型意义的企业级大型Sybase数据库应用系统为例&#xff0c;涉及了数据库应用系统调优的五大领域&#xff1a;压力测试、 应用端调优、服务器端…

android 6.0 ios9谁快,没安卓6.0流畅?iOS 9突飞猛进终于不卡

今天早些时候&#xff0c;iOS 9.1正式发布&#xff0c;改进系统Bug&#xff0c;添加新功能等&#xff0c;但还有不少反应系统流畅了。不知道是否是心里作用的关系&#xff0c;之前不少果粉吐槽iOS 9过于卡顿&#xff0c;苹果也是在一个版本一个版本的改进&#xff0c;这次的iOS…

(UML两个汇总)九种图。

最后总结UML关系&#xff0c;有明确的关系&#xff0c;现在让我们总结一下UML九图。。图往往比文字要直观&#xff0c;因此&#xff0c;当我们开发软件。文件必须是不可或缺的人物&#xff0c;。以下我将这九种图分了一下&#xff1a; 我们还能够将这九种图分为静态图和动态图&…

SqlBulkCopy 批量复制数据到数据表

使用 SqlBulkCopy 类只能向 SQL Server 表写入数据。但是&#xff0c;数据源不限于 SQL Server&#xff1b;可以使用任何数据源&#xff0c;只要数据可加载到 DataTable 实例或可使用 IDataReader 实例读取数据 使用Datatable作为数据源的方式&#xff1a;下面的代码使用到了Co…

Android Ac 控件,Android控件--MultiAutoCompleteTextView

1.功能可支持选择多个值(在多次输入的情况下)&#xff0c;分别用分隔符分开&#xff0c;并且在每个选中的时候再次输入值时会自动匹配&#xff0c;可用在发短信、发邮件时选择联系人这种类型当中。2.独特属性android:completionThreshold"3" ----设置输入多少字符时自…

BZOJ1299 [LLH邀请赛]巧克力棒

怎么又是博弈论。。。我去 Orz hzwer&#xff0c;这道题其实是可以转化成Nim游戏的&#xff01; "第一步&#xff1a; 先从n根巧克力棒中取出m(m>0)根&#xff0c;使得这m根巧克力棒的xor和为0&#xff0c;同时使得剩下的n-m根巧克力棒无论怎么取&#xff0c;xor和都不…

Fragment的保存

2019独角兽企业重金招聘Python工程师标准>>> 一、场景 在一个fragment播放语音文件&#xff0c;一旦设备发生旋转&#xff0c;播放将暂停。因为fragment将重新生成。具体的流程步骤如下&#xff1a; 二、解决的方式 在Fragment的onCreate方法中&#xff0c;设置s…

SQL Server 存储引擎-剖析Forwarded Records

我们都知道数据在存储引擎中是以页的形式组织的,但数据页在不同的组织形式中其中对应的数据行存储是不尽相同的,这里通过实例为大家介绍下堆表的中特有的一种情形Forwared Records及处理方式. 概念 堆表中,当对其中的记录进行更新时,如果当前数据页无法满足更新行的容量,此时这…

u3d游戏开发视频潭州_游戏美术行业的发展与应用人工智能学院专业介绍及未来前景系列报告会二...

为了让2020级新同学对动漫专业加深认识&#xff0c;更好的规划学习&#xff0c;学院于11月12日6点晚邀请了校企合作单位“369云遮月游戏公司”在长安校区图书馆阶梯教室进行了游戏美术行业的发展与应用的报告会&#xff0c;主题围绕“专业介绍与发展前景”展开&#xff0c;云遮…

华为谷歌互利合作曝光:或将推Nexus手表

业内传言称&#xff0c;中国的华为科技公司和韩国LG电子公司&#xff0c;今年将为谷歌公司设计两款Nexus品牌的智能手机&#xff0c;这也将是华为科技第一次参加谷歌的Nexus硬件计划。日前&#xff0c;国外权威媒体披露了华为Nexus硬件计划的更多内容。除了一款5.7英寸的智能手…

unity2d随机生成物体_2020 年最好用的一键生成设计神器,全在这里了!

对于很多新手设计师来说&#xff0c;要高效率地完成一件看上去还不错的设计作品&#xff0c;其实并不是一件容易的事。特别是在现实的工作中&#xff0c;对于临危受命的任务&#xff0c;更是很少有人会耐心地等你慢慢去摸索的.……不慌&#xff0c;今天就掏出一份私藏已久的设计…

android MPV架构快速实现,不是所有的MPV都叫GL8,一体化智能座舱体验来袭

进入车内&#xff0c;首先映入眼帘的就是双12.3吋全液晶仪表及中控联屏&#xff0c;出色的画面质感在第一时间吸引了我的注意。当然&#xff0c;全新的一体化智能座舱理念&#xff0c;多屏互联、多维交互也是它的最大亮点之一。全新一代别克GL8家族采用迭代更新的通讯解决方案&…

基于SuperSocket的IIS主动推送消息给android客户端

在上一篇文章《基于mina框架的GPS设备与服务器之间的交互》中&#xff0c;提到之前一直使用superwebsocket框架做为IIS和APP通信的媒介&#xff0c;经常出现无法通信的问题&#xff0c;必须一天几次的手动回收程序池&#xff0c;甚至重起服务器&#xff0c;通常周末接到一个陌生…