「深入理解PHP内核」- 信号处理

前言

在PHP7生命周期中会涉及信号的处理在第7.1基础知识中,作者提到了信号处理,并得出结论:

  • 可靠信号(>=34)不会丢失,N个可靠信号经过排队,在信号处理的时候仍然是N个。
  • 非可靠信号(<34)会丢失,N个非可靠信号经过排队,在信号处理的时候是1个
  • sigprocmask系统调用是设置进程的信号掩码(掩码中的信号会进入队列排队处理)的。
  • 对于3中进入队列的信号,进程可以通过sigsuspend(&newMask)从队列中取出阻塞信号

在这一小节的学习过程,大家对信号处理都不是很熟悉,理解起来比较费劲,故在这里对该小节中的示例代码进行剖析

知识拓展

在讲解代码之前,先拓展一下信号的相关知识

UNIX信号有1~63个

  • 1~31的信号为传统UNIX支持的信号,是不可靠信号(非实时信号)
  • 34~63信号是后来扩容的可靠信号(实时信号)

注意:32和33信号是不存在,应该是想区分开这两类信号吧

不可靠信号不支持排队,可能会造成信号丢失

可靠信号支持排队,不会造成信号丢失

可以采用多次发送来进行排队测试

发送信号到进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kill [-s signal|-p] [--] pid… 向某一进程发送信号
kill -2 6389
kill -l [signal] 信号列表

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

需要理解的三个函数

  • sigaction():用来查询或设置信号处理方式
  • sigprocmask():用于改变进程的当前阻塞信号集,也可以用来检测当前进程的信号掩码
  • sigsuspend():在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止

实战

我们要验证的就是可靠信号(>=34)不丢失,非可靠信号会丢失这一结论,并理解上述三个函数
我们需要检测到当前进程接收到的信号,并且能根据不同的信号设定不同的响应

首先自定义一个信号处理函数

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
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>

/*自定义信号处理函数*/
void signal_handler(int signo);

void signal_handler(int signo) {
if (signo == SIGINT) {
printf("catch signal SIGINT:%d\n", signo);
} else if (signo == SIGRTMIN) {
printf("catch signal SIGRTMIN:%d\n", signo);
} else if (signo == SIGQUIT) {
printf("catch signal SIGQUIT:%d\n", signo);
exit(0);
} else {
printf("catch signal: %d\n", signo);
}
}

/*程序主函数*/
int main(void) {
// 声明一个信号集
sigset_t set;
// 清空信号集
sigemptyset(&set);
// 向信号集添加信号
sigaddset(&set, SIGINT);// 添加一个不可靠信号 2
sigaddset(&set, SIGRTMIN);// 添加一个可靠信号 34
// 屏蔽信号集中的信号
sigprocmask(SIG_BLOCK, &set, NULL);

// 声明一个sigaction结构体
struct sigaction sa;
// 为结构体申请内存空间
memset(&sa, 0, sizeof(struct sigaction));

// 安装信号处理器
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
// 设置信号处理方式
sigaction(SIGINT, &sa, NULL);
sigaction(SIGRTMIN, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);

int count = 0;
while(1) {
if (count >= 100) {
break;
}
printf("sleep...\n");
sleep(1);
if (count > 0 && count % 10 == 0) {
// 每10s接收并处理处理一次信号,处理完后继续屏蔽信号SIGINT, SIGRTMIN
printf("等待信号..\n");
sigemptyset(&set);
sigsuspend(&set);
}
count ++;
}
}

上面程序中每10s才去处理一次信号,可以通过10s内连续发送多个不可靠信号2和10s内连续发送多个可靠信号34

来看程序运行结果

1
2
gcc signal.c -o signal
./signal

以下是两种信号的执行结果

  • 发送可靠信号
    发送可靠信号
  • 发送不可靠信号
    发送不可靠信号