文章结构:
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()函数讲解
函数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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
(ifSignaled) {
(ifContinued) {
printf("PID=%ld continued\n", (long)w);
}
fflush(fMain);
if (ifExited || ifSignaled) {
printTime();
printf("isExited=%d isSingaled=%d\n", ifExited, ifSignaled);
fflush(fMain);
break;
}
} while (1);
printTime();
printf("Main PID %ld exit.\n", (long)getpid());
fclose(fMain);
exit(EXIT_SUCCESS);
}
}
进入到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文件,日志内容如下:
has not yet changed state -stopped by signal 17相信梦想是价值的源泉,相信眼光决定未来的一切,