信号是一种软件中断。常驻程序尤其需要注意处理这些信号,如果没有处理,同时也没有了解信号的默认动作,进程可能会莫名其妙的退出或者core。信号早已有之,但在老的操作系统中,可能会出现信号丢失。4.3BSD和SVR3之后增加了可靠信号机制,我们可以放心使用信号机制。本文根据阅读Nginx代码,参考其信号处理机制,总结了信号使用方法和注意事项。
信号列表
我们使用如下命令查看系统信号列表:
命令执行结果如下图所示:

下表列出了各信号在Linux环境中的含义,以及默认动作:
| 值 | 名字 | 说明 | ISO C | SUS | 默认动作 |
|---|
| 1 | SIGHUP | 连接断开 | | ✔ | 终止 |
| 2 | SIGINT | 终端中断符 | ✔ | ✔ | 终止 |
| 3 | SIGQUIT | 终端退出符 | | ✔ | 终止+core |
| 4 | SIGILL | 非法硬件指令 | ✔ | ✔ | 终止+core |
| 5 | SIGTRAP | 硬件故障 | | XSI | 终止+core |
| 6 | SIGABRT | 异常终止(abort) | ✔ | ✔ | 终止+core |
| 7 | SIGBUS | 硬件故障 | | ✔ | 终止+core |
| 8 | SIGFPE | 算术异常 | ✔ | ✔ | 终止+core |
| 9 | SIGKILL | 终止 | | ✔ | 终止 |
| 10 | SIGUSR1 | 用户自定义的信号 | | ✔ | 终止 |
| 11 | SIGSEGV | 无效内存引用 | ✔ | ✔ | 终止+core |
| 12 | SIGUSR2 | 用户自定义的信号 | | ✔ | 终止 |
| 13 | SIGPIPE | 写至无读进程的管道 | | ✔ | 终止 |
| 14 | SIGALRM | 超时(abort) | | ✔ | 终止 |
| 15 | SIGTERM | 终止 | ✔ | ✔ | 终止+core |
| 16 | SIGSTKFLT | 协处理器故障 | | | 终止 |
| 17 | SIGCHLD | 子进程状态改变 | | ✔ | 忽略 |
| 18 | SIGCONT | 使暂停进程继续 | | ✔ | 继续/忽略 |
| 19 | SIGSTOP | 停止 | | ✔ | 暂停进程 |
| 20 | SIGTSTP | 终端停止符 | | ✔ | 暂停进程 |
| 21 | SIGTTIN | 后台读控制tty | | ✔ | 暂停进程 |
| 22 | SIGTTOU | 后台写至控制tty | | ✔ | 暂停进程 |
| 23 | SIGURG | 紧急情况(套接字,带外数据) | | ✔ | 忽略 |
| 24 | SIGXCPU | 超过CPU限制(setrlimit) | | XSI | 终止+core/忽略 |
| 25 | SIGXFSZ | 超过CPU限制(setrlimit) | | XSI | 终止+core/忽略 |
| 26 | SIGVTALRM | 虚拟时间闹钟(setitimer) | | XSI | 终止 |
| 27 | SIGPROF | 梗概事件超时(setitimer) | | XSI | 终止 |
| 28 | SIGWINCH | 终端窗口大小改变 | | | 忽略 |
| 29 | SIGIO | 异步I/O | | | 终止/忽略 |
| 30 | SIGPWR | 电源失效/重启动 | | | 终止/忽略 |
| 31 | SIGSYS | 无效系统调用 | | XSI | 终止+core |
注释
- POSIX.1包含了ISO C标准函数库,同时结构分类两部分:必须部分和可选部分(X/Open系统接口 X/Open System Interface,XSI)。
- SUS 即 单一UNIX规范(Single UNIX Specification,缩写为SUS),它是UNIX系统的统一规格书。SUS是POSIX的扩展,扩充了POSIX标准,定义了标准UNIX操作系统。只有遵循XSI的实现才能称为UNIX系统。
- SUS、POSIX、ISO C的关系,可以总结为 SUS > POSIX > ISO C。
信号编程
下表列出C头文件 /usr/include/x86_64-linux-gnu/asm/signal.h 中关于信号的定义:
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
| #define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
#define SIGPWR 30
#define SIGSYS 31
|
信号函数 signal
1
2
3
4
5
| #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
|
在实际开发环境中,通常使用上述简单信号函数,来注册信号的对应处理函数。我们使用如下的
测试代码文件
,来演示该函数的使用方法:
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
| #include<unistd.h>
#include<stdio.h>
#include<signal.h>
void sig_handle(int sig)
{
switch (sig)
{
case SIGUSR1 : printf("sig_handle::get SIGUSR1:%d\n", sig); break;
case SIGUSR2 : printf("sig_handle::get SIGUSR2:%d\n", sig); break;
default : printf("sig_handle::get signal :%d\n", sig); break;
}
}
int main(int argc, char ** argv)
{
printf("sgtest::getpid()::%d\n", (int)getpid());
if (signal(SIGUSR1, sig_handle) == SIG_ERR)
{
perror("signal SIGUSR1 register error");
return 1;
}
if (signal(SIGUSR2, sig_handle) == SIG_ERR)
{
perror("signal SIGUSR2 register error");
return 1;
}
while(1)
{
// 我们将sleep放置到while循环中,
// 是因为当信号来临时,sleep被信号中断,然后sleep会马上返回
// 如果sleep不在while循环中,那么该程序执行完信号响应函数之后,会马上执行到底,从而整体退出
int ileft = sleep(10); // ileft表示被中断后,sleep执行中的剩余时间
// man 3 sleep : Zero if the requested time has elapsed,
// or the number of seconds left to sleep, if the call was interrupted by a signal handler.
printf("ileft:%d\n", ileft);
}
return 0;
}
|
使用命令编译该测试代码:
1
| $ cc -o sigtest sigtest.c
|
我们先启动一个终端,开始运行sigtest程序,然后在另外一个终端,向sigtest进程发送信号,执行过程如下图所示:

参考资料