文章结构:


#wait()的阻塞问题

  之前的多进程:父进程监听子进程状态 wait()的使用文章中,父进程为了获取子进程的SIGSTOP、SIGTERM等信号时,由于调用了wait而导致主进程一直阻塞。在实际的开发中,主进程在等待子进程状态变化时还会有其它的事情要去执行,所以需要一种异步回调机制,让主进程可以在执行其它任务的时候,又可以监听到子进程的进程状态变化时及时处理。

  signal()函数就可以解决以上的问题。


#signal()函数讲解

  signal()函数原型如下:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

  typedef定义了一类函数名叫sighandler_t,该函数返回类型为void,且只有一个int型参数。

  signal()函数第一个参数signum指所要监听的进程状态的变量信号,所有可监听的信号量的定义可以从sys/signal.h头文件中去查阅。本文文章的demo中要处理的信号量有:

SIGSTOP: 发送给父进程的,表示子进程被外部命令所暂停。命令可以是kill,也可以在top中操作。

SIGCHLD: 发送给父进程的,表示子进程被外部命令所暂停或已经执行完毕退出。这时需要父进程执行wait函数让子进程从僵尸进程状态彻底被系统回收。

SIGWINCH: 程序窗口大小发生变化。在终端命令行下运行可执行文件时鼠标拖动一下窗口即可获得此信号。

  signal()函数第二个参数是指定signum的处理函数。该函数的唯一参数将会被赋值为被监听到的信号量。在此函数中可以调用wait或其它处理逻辑。也可以赋值为系统的SIG_IGNSIG_DFL函数,分别表示忽略和默认处理方式。但是信号量SIGKILLSIGSTOP的处理方式是不能被忽略处理。

  当自定义了信号量处理函数后,所监听的信号被捕获,则该信号会被设置为阻塞blocked,然后再执行处理函数中的逻辑,处理函数执行完毕后,信号量恢复为未阻塞状态unblocked

  signal()函数正常执行,返回值为signum的原有处理函数;否则出错返回SIG_ERR,并且可以通过errno来查看错误原因。

  signal()函数在不同的Unix或Linux版本间存在较大的差异,所以一般推荐用sigaction()函数来替换。本文不涉及sigaction()的内容。


#示例代码

  接下来演示signal()函数的使用。代码示例中依然用到了对标准输出流的重定向freopen,将子进程的日志输出到child_signal.txt,父进程日志输出到main_signal.txt中去。

signal.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <string.h>


#ifndef getSigName
#define getSigName(sig) (((sig) == SIGCHLD)?"SIGCHLD":(((sig) == SIGCONT)?"SIGCONT":(((sig) == SIGTERM)?"SIGTERM":(((sig) == SIGWINCH)?"SIGWINCH":""))))
#endif

static void printTime() {
time_t calendar_time = time(NULL);
struct tm * tm_local = localtime(&calendar_time);
char str_f_t [50];
strftime(str_f_t, sizeof(str_f_t), "%G-%m-%d %H:%M:%S", tm_local);
printf("%s ", str_f_t);
}

static void handleSignal(int sig) {
printTime();
printf("PID=%d handleSignal %s=%d\n", getpid(), getSigName(sig), sig);
if (sig == SIGCHLD || sig == SIGTERM) {
// 子进程被暂停或退出了(包括按逻辑执行结束及被kill)
int status = 0;
int childPid = waitpid(-1, &status, WUNTRACED|WCONTINUED);
if (childPid == -1) {
printTime();
printf("Parent w=-1, error=%s \n", strerror(errno));
} else {
int ifExited, ifSignaled, ifStopped, ifContinued;

ifExited = WIFEXITED(status);
ifSignaled = WIFSIGNALED(status);
ifStopped = WIFSTOPPED(status);
ifContinued = WIFCONTINUED(status);

printTime();
printf("pid=%ld child=%d exitCode=%d status=%d ifExited=%d ifSignaled=%d ifStopped=%d ifContinued=%d \n",
(long)getpid(), childPid, status, _WSTATUS(childPid), ifExited, ifSignaled, ifStopped, ifContinued);

printTime();
if (ifExited) {
printf("PID=%ld exited, status=%d\n", (long)childPid, WEXITSTATUS(status));
} else if (ifSignaled) {
printf("PID=%ld killed by signal %d\n", (long)childPid, WTERMSIG(status));
} else if (ifStopped) {
printf("PID=%ld stopped by signal %d\n", (long)childPid, WSTOPSIG(status));
} else if (ifContinued) {
printf("PID=%ld continued\n", (long)childPid);
}
}
} else if (sig == SIGCONT) {
// sigcont在本人的mac上调用wait是无效的
// do nothing
} else if (sig == SIGWINCH) {
// do nothing
} else {
printTime();
printf("sig=%d is not valid.\n", sig);
}
}

static void mainProcessDoSomething(FILE* f) {
int count = 0;
while(1) {
sleep(3);
if (f != NULL){
fflush(f);
}
if (count ++ > 50) {
break;
}
}
}

int main/*11*/ (int argc, char ** argv) {

// SIGWINCH:应用程序窗口发生变化
signal(SIGWINCH, &handleSignal);

// 子进程被暂停运行
signal(SIGSTOP, &handleSignal);
// 子进程被恢复运行(Mac上无效..)
signal(SIGCONT, &handleSignal);
// SIGCHLD:子进程被暂停或退出了(包括按逻辑执行结束及被kill)
signal(SIGCHLD, &handleSignal);
// 不建议对SIGTERM进行设置
// signal(SIGTERM, &handleSignal);

pid_t pId = fork();
if (pId == -1) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pId == 0) {
FILE* fChild = freopen("/Users/sodino/workspace/xcode/Define/Define/child_signal.txt", "w", stdout);
int myPid = getpid();
int parentPid = getppid();

printTime();
printf("Child:SelfID=%d ParentID=%d \n", myPid, parentPid);

int count = 0;
do{
count ++;
sleep(5);
printTime();
printf("Child:count=%d \n", count);
fflush(fChild);
if (count >= 20) {
break;
}
}while (1);
printTime();
printf("Child:SelfID=%d exit success.\n", myPid);
fflush(fChild);
fclose(fChild);
return EXIT_SUCCESS;
} else {
FILE * fMain = freopen("/Users/sodino/workspace/xcode/Define/Define/main_signal.txt", "w", stdout);
printTime();
printf("Parent:SelfID=%d MyChildPID=%d \n", getpid(), pId);
fflush(fMain);

// 继续往下执行其它任务,而不像原逻辑 会被wait()所阻塞
mainProcessDoSomething(fMain);

printTime();
printf("Parent:SelfID=%d exit success.\n", getpid());

fflush(fMain);
fclose(fMain);

return EXIT_SUCCESS;
}
}

  以上代码中,在main()函数一开始,就对SIGWINCHSIGSTOPSIGCHLD进行监听,统一注册其处理函数为handleSignal(int)。然后执行fork()生成子进程。

  handleSignal(int)函数中会对监听到的信号量做出打印及输出,如果是SIGSTOPSIGCHLD的话则会执行wait以获取子进程状态。

  在主进程中,以mainProcessDoSomething()函数来表示父进程的其它工作任务,不被wait所阻塞。

  编译signal.c文件,后在命令行终端下执行./a.out,然后鼠标拖动改动一个命令行终端窗口的大小,可见child_signal.txtmain_signal.txt都输出了handleSignal SIGWINCH=28的日志。

  然后kill -sigstop child_pid,再恢复kill -sigcont child_pid,然后一直等待子进程运行完毕,可完整看到如下两份日志。   

child_signal.txt

2015-04-19 22:31:31 Child:SelfID=4352 ParentID=4351 
2015-04-19 22:31:36 Child:count=1 
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=2 
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=3 
2015-04-19 22:31:39 PID=4352 handleSignal SIGWINCH=28
2015-04-19 22:31:39 Child:count=4 
2015-04-19 22:31:54 Child:count=5 
2015-04-19 22:31:59 Child:count=6  // 这里,对子进程执行了kill -sigstop命令
2015-04-19 22:32:21 PID=4352 handleSignal SIGCONT=19
2015-04-19 22:32:21 Child:count=7 
2015-04-19 22:32:26 Child:count=8
... ...
... ...
2015-04-19 22:33:16 Child:count=20 
2015-04-19 22:33:16 Child:SelfID=4352 exit success.

main_signal.txt

2015-04-19 22:31:31 Parent:SelfID=4351 MyChildPID=4352 
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:31:39 PID=4351 handleSignal SIGWINCH=28
2015-04-19 22:32:04 PID=4351 handleSignal SIGCHLD=20
2015-04-19 22:32:04 pid=4351 child=4352 exitCode=4479 status=0 ifExited=0 ifSignaled=0 ifStopped=1 ifContinued=0 
2015-04-19 22:32:04 PID=4352 stopped by signal 17
2015-04-19 22:33:16 PID=4351 handleSignal SIGCHLD=20
2015-04-19 22:33:16 pid=4351 child=4352 exitCode=0 status=0 ifExited=1 ifSignaled=0 ifStopped=0 ifContinued=0 
2015-04-19 22:33:16 PID=4352 exited, status=0  // 父进程监听到子进程执行完毕
2015-04-19 22:33:55 Parent:SelfID=4351 exit success. // 父进程WHILE循环执行完毕

  可以发现发送给子进程的SIGSTOP和运行退出对父进程来说都是SIGCHLD。而子进程可以接收到父进程wait方法中不支持的SIGCONT信号。