IPC#001#信号机制

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

信号列表

我们使用如下命令查看系统信号列表:

1
$ kill -l

命令执行结果如下图所示:

下表列出了各信号在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进程发送信号,执行过程如下图所示:

参考资料