【C/C++】多进程:父进程监听子进程状态 wait()的使用
文章结构:
# wait能力介绍
在上一篇 【C/C++】多进程:子进程的创建fork() 中演示了子进程的创建。
创建子进程后,父进程具有监听子进程的运行状态的能力,用到的函数为:
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
以上函数用于等待子进程子进程的状态变化回调并且获取状态变化信息。所能获取到的状态变化包括:子进程运行结束、子进程被信号量暂停、子进程被信号量恢复运行。
父进程执行了
wait
函数后,如果子进程已经发生了状态变化,则
wait
函数立即就会有返回结果;否则
wait
函数会一直阻塞直至子进程状态发生变化。
通常意义上,如果子进程已经发生了状态变化,但还未被父进程或其它系统回调执行
wait
,则把此时的子进程称为是可等待的(waitable)。
子进程运行结束后,父进行执行
wait
函数可以推动系统释放与子进程相关的资源;否则子进程将会被维持在
僵尸进程
的状态下一直存在。
函数
wait(int * status)
是对
waitpid()
的封装,限定了只有在任一子进程运行结束时才会有返回,否则调用进程会一起处于阻塞状态暂停执行。
wait(int * status)
等同于如下代码:
waitpid(-1, &status, 0);
waitpid()
会阻塞调用进程直至任一子进程的运行状态发生变化。接下来对
waitpid()
的三个参数进行讲解:
pid < -1
取该pid的绝对值,如果任意子进程的进程组ID等于该绝对值,则该组进程中任一子进程中的进程状态发生变化都会触发
waitpid()
的回调。
pid == -1
监听范围扩大到任意子进程。
pid == 0
监听限制为子进程的进程组ID与父进程相等。
pid > 0
监听限制为指定子进程进程ID值。
值可以为
NULL
。当不为
NULL
时,用于存储触发状态变化的信息号值和
exit(code)
中的
code
值。
wait.h
头文件定义了几个宏用于解析
status
的值,常见的有:
WEXITSTATUS(status)
当子进程调用
exit(code)
或
_exit(code)
或正常运行到
main()
函数结尾时正常结束运行,则返回
true
。
当
WIFEXITED(status)
为
true
时,获取
exit(code)
或
_exit(code)
的
code
值。
其中
code
只能为0或正数,不支持负数。
WIFSIGNALED(status)
WTERMSIG(status) 当子进程被信号量杀死时则返回
true
。
当
WIFSIGNALED(status)
为
true
时,获取该信号量的值。
WIFSTOPPED(status)
WSTOPSIG(status) 当子进程被信号量暂停执行时则返回
true
。
当
WIFSTOPPED(status)
为
true
时,获取该信号量的值。
值可以是以下常量的任意值或任意常量与0的
OR
计算值。
wait
时指定的
pid
仍未结束运行,则
wait
立即返回0。
WUNTRACED
当子进程被暂停时,则
wait
立即返回子进程的
pid
。
WCONTINUED
当被暂停的子进程又被信号量恢复后,则
wait
立即返回子进程的
pid
。
Linux 2.6.10及以后生效。在Mac 0S X 10.9.5上未生效。
wait()
函数在正常执行时会返回被终止进程的
pid
值,当执行发生错误后会返回-1。
waitpid()
函数在正常执行时会返回进程状态发生变化的进程
pid
值;如果函数options中包含了
WNOHANG
常量,则会在指定
pid
的子进程未退出且进程状态也未发生变化时直接返回0,如果子进程已经退出了,则返回子进程的
pid
;否则当执行发生错误后会返回-1。
由于涉及到两个进程,在终端命令行下的日志打印会出现混乱,所以通过重定向标准输入输出流将两个进程的日志分别输出到两个文件中去,父进程的日志输出到
main.txt
中去,子进程的日志输出到
child.txt
中去。涉及到重定向标准输入输出流,具体细节见
【C/C++】文件创建、打开、读、写、复制、关闭、删除等操作汇总
。
涉及到的
ps
、
kill
命令可以通过
man ps
及
man kill
去查找细节。
接下来的代码将演示
fork()
一个子进程后,会通过
ps
命令查询进程状态,及
kill
命令向子进程发送信号量改变进程状态;父进程通过
wait
监听子进程状态。
wait.c
源码如下:
1 |
|
进入到
wait.c
的目录下,执行如下命令编译并运行:
gcc wait.c // 生成可执行文件a.out
./a.out // 运行可执行文件
使用
ps -j
命令,查看进程可以看到
fork()
执行后,进程列表中有两个名为
a.out
的进程。如下图:
可以看到PID=678的进程其父进程是PID=677。两个进程的进程组ID(PGID)都为677。
再看上图中的
STAT
列,两个进程都是
S+
,其中
S
表示进程处于
sleeping
状态,原因是父进程在
wait
,而子进程除了打印日志外大部分时间都是在执行
sleep()
;另一个
+
表示这两个进程都是在当前控制台的前台进程。
接使用
kill -SIGSTOP 678
命令向子进程发送暂停信号,再
ps -j
查询一下进程状态,发现子进程678已经从
S+
变为
T+
,即已经进入STOP状态了。
而另一方面,查看
child.txt
,发现该文件已经不会继续生成日志了。查看
main.txt
文件,日志内容如下:
2015-04-16 22:53:15 Child PID=678 has not yet changed state
2015-04-16 23:11:48 pid=677 w=678 exitCode=4479 status=38 ifExited=0 ifSignaled=0 ifStopped=1 ifContinued=0
2015-04-16 23:11:48 PID=678 stopped by signal 17
第一句
Child PID=678 has not yet changed state
是父进程执行
waitpid:WNOHANG
的返回结果,表示当时子进程仍未退出,正在运行。第二句则输出了
ifStopped=1
表示
waitpid:WUNTRACED
已经监听到子进程被外部发送的信号量导致进程状态发生变化了。第三句
PID=678 stopped by signal 17
表示信号量值是17,通过以下命令可能验证SIGSTOP信号量值为17:
sodino:Define sodino$ kill -l SIGSTOP
17
接下来,要继续使用
kill -SIGCONT 678
命令来恢复子进程的运行,操作见下图:
由上图可见子进程从暂停状态又恢复运行了。查看
child.txt
日志则发现日志又恢复输出了:
... ...
2015-04-16 23:11:46 sleep count=1110
2015-04-16 23:11:47 sleep count=1111
2015-04-16 23:23:20 sleep count=1112
2015-04-16 23:23:21 sleep count=1113
... ...
注意看时间,从23:11分到23:23分这个时间段内进程状态是停止的所以没有日志输出(这段时间在写博客给你们看呀..)。查看
main.txt
则没有发现
waitpid
有回调,个人认为这是在我的mac上C版本及运行环境问题吧。
最后再使用命令
kill -SIGTERM 678
后,可以发现
ps
命令已经查询不到刚才的
a.out
进程了。