【C/C++】多进程:父进程监听子进程状态 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
  pid < -1取该pid的绝对值,如果任意子进程的进程组ID等于该绝对值,则该组进程中任一子进程中的进程状态发生变化都会触发waitpid()的回调。
  pid == -1监听范围扩大到任意子进程。
  pid == 0监听限制为子进程的进程组ID与父进程相等。
  pid > 0监听限制为指定子进程进程ID值。
- status
  值可以为NULL。当不为NULL时,用于存储触发状态变化的信息号值和exit(code)中的code值。
  wait.h头文件定义了几个宏用于解析status的值,常见的有:
| 宏 | 含义 | 
|---|---|
| WIFEXITED(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时,获取该信号量的值。 | 
- options
  值可以是以下常量的任意值或任意常量与0的OR计算值。
| 常量 | 含义 | 
|---|---|
| WNOHANG | 调用 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进程了。  
  查看main.txt可以发现waitpid有了返回值其中ifSignaled=1表示子进程已经被信号量15所杀死。并最后退出了。
2015-04-16 23:31:00 pid=677 w=678 exitCode=15 status=38 ifExited=0 ifSignaled=1 ifStopped=0 ifContinued=0 
2015-04-16 23:31:00 PID=678 killed by signal 15
2015-04-16 23:31:00 isExited=0   isSingaled=1
2015-04-16 23:31:00 Main PID 677  exit.以上,就是本篇想说的,下一篇:多进程:僵尸进程