Linux系统中的信号
在Linux编程中,信号是不可避免的一个话题。信号是一种软中断机制,在应用程序运行的任意时刻都可能产生一个信号。信号产生后,进程会被打断去处理信号。每个信号都有一个当前的处理方式,不同的处理方式所产生的结果也不同。总结起来,信号的处理方式有以下几种:
- 终止进程
- 忽略信号
- 进程转储
- 停止进程
- 恢复进程
在Linux系统中,每种信号都有一个默认的处理方式。在程序中可以改变信号的默认处理方式,详细内容可查看下一节处理信号。那么怎么知道系统中支持那些信号呢?最简单的方式就是用"kill -l"命令来获取系统支持的信号列表。这个命令在UNIX、BSD、Linux等系统上都是可用的,而且在不同系统上得到的结果可能不一样。下面分别在Linux系统和macOS(基于BSD)系统上得到的信号列表。
kill -l
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
kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE
9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGINFO 30) SIGUSR1 31) SIGUSR2
在Linux系统中,对常见信号都有一个默认的处理方式。详见下表。
信号 值 处理方式 说明
──────────────────────────────────────────────────────────────────────
SIGHUP 1 终止进程 控制终端挂起或者控制进程死亡
SIGINT 2 终止进程 键盘中断,如按下ctrl+c
SIGQUIT 3 进程转储 从键盘收到退出ctrl+\ 或者 SysRq
SIGILL 4 进程转储 非法指令
SIGABRT 6 进程转储 中止信号,比如在程序中调用abort()
SIGFPE 8 进程转储 浮点异常,比如除数为0
SIGKILL 9 终止进程 Kill信号
SIGSEGV 11 进程转储 非法内存引用
SIGPIPE 13 终止进程 断裂的管道
SIGALRM 14 终止进程 警告信号,比如在程序中调用alarm()
SIGTERM 15 终止进程 终止信号
SIGUSR1 30,10,16 终止进程 用户自定义信号1
SIGUSR2 31,12,17 终止进程 用户自定义信号2
SIGCHLD 20,17,18 忽略信号 子进程停止或者退出
SIGCONT 19,18,25 恢复进程 恢复信号
SIGSTOP 17,19,23 停止进程 停止信号
SIGTSTP 18,20,24 停止进程 终端输入停止
SIGTTIN 21,21,26 停止进程 后台进程的终端输入
SIGTTOU 22,22,27 停止进程 后台进程的终端输出
SIGBUS 10,7,10 进程转储 总线错误
SIGPOLL 终止进程 可轮询时间
SIGPROF 27,27,29 终止进程 性能计数定时器到时
SIGSYS 12,31,12 进程转储 错误的系统调用
SIGTRAP 5 进程转储 调试跟踪/断点陷阱
SIGURG 16,23,21 忽略信号 socket上的紧急情况
对于大多数信号,一般情况下默认的处理方式都适用。但是有些信号在特定的程序设计中默认处理方式可能并不适用。以下是几个例子:
- SIGHUP: SIGHUP通常在控制终端退出或者断开时会产生。一个典型的例子是,你ssh登陆到一个服务器上,在后台启动一个服务程序;然后断开ssh连接,此时后台服务程序会收到SIGHUP信号。而默认的处理方式服务程序将终止,但是这也许并不是期望的行为。为了避免在收到SIGHUP信号时退出,需要自定义处理SIGHUP信号。当然,另外一个方法是采用nohup工具来启动服务程序。nohup本质上也是忽略SIGHUP信号。
- SIGPIPE:在使用socket的网络程序中,SIGPIPE信号可能会产生。一个典型的例子是,一个TCP的Client正连着一个Server发送数据,此时Server关闭socket,那么Client的send函数操作会触发一个SIGPIPE信号。而SIGPIPE的默认处理程序将结束Client程序。多数情况下,这种处理方式并不恰当,因为Client可能还想尝试重连或者连接其它Server。因此这种情况下就需要针对SIGPIPE进行自定义处理。
- SIGTERM: SIGTERM的一个典型场景时系统关机或者重启前,Linux的init进程(PID为1的进程)会先向所有进程发送SIGTERM信号,然后再发送SIGKILL信号。因此,如果一个程序想要优雅地关闭,比如一个程序或许想在关闭前存储一些状态以便在下次启动时使用,那么处理SIGTERM是一个很好的时机。
前面提到通过命令"kill -l"可以获取系统中的信号列表,下面的程序代码则演示了在C语言中获取信号列表及相关信息的方法。
/*
* sig_list.c: demostrates listing signals in system.
*/
#include <signal.h>
#include <stdio.h>
static void list_signals(void)
{
int sig;
sigset_t ss;
sigfillset(&ss);
for (sig = 0; sig < NSIG; sig++) {
#ifdef __DARWIN_C_LEVEL
printf("%-12s %-2d %-32s valid: %s\n", sys_signame[sig] ? sys_signame[sig] : " ", sig,
sys_siglist[sig] ? sys_siglist[sig] : " ", sigismember(&ss, sig) ? "yes" : "no");
#else
printf("%-2d %-32s valid: %s\n", sig, sys_siglist[sig] ? sys_siglist[sig] : " ", sigismember(&ss, sig) ? "yes" : "no");
#endif
}
}
int main(int argc, char *argv[])
{
list_signals();
return 0;
}
将源码编译生成sig_list程序。
gcc -o sig_list sig_list.c
在Linux和macOS上分别编译运行这个程序,得到的结果是不同的,如下所示。
./sig_list
0 valid: yes
1 Hangup valid: yes
2 Interrupt valid: yes
3 Quit valid: yes
4 Illegal instruction valid: yes
5 Trace/breakpoint trap valid: yes
6 Aborted valid: yes
7 Bus error valid: yes
8 Floating point exception valid: yes
9 Killed valid: yes
10 User defined signal 1 valid: yes
11 Segmentation fault valid: yes
12 User defined signal 2 valid: yes
13 Broken pipe valid: yes
14 Alarm clock valid: yes
15 Terminated valid: yes
16 Stack fault valid: yes
17 Child exited valid: yes
18 Continued valid: yes
19 Stopped (signal) valid: yes
20 Stopped valid: yes
21 Stopped (tty input) valid: yes
22 Stopped (tty output) valid: yes
23 Urgent I/O condition valid: yes
24 CPU time limit exceeded valid: yes
25 File size limit exceeded valid: yes
26 Virtual timer expired valid: yes
27 Profiling timer expired valid: yes
28 Window changed valid: yes
29 I/O possible valid: yes
30 Power failure valid: yes
31 Bad system call valid: yes
32 valid: no
33 valid: no
34 valid: yes
35 valid: yes
36 valid: yes
37 valid: yes
38 valid: yes
39 valid: yes
40 valid: yes
41 valid: yes
42 valid: yes
43 valid: yes
44 valid: yes
45 valid: yes
46 valid: yes
47 valid: yes
48 valid: yes
49 valid: yes
50 valid: yes
51 valid: yes
52 valid: yes
53 valid: yes
54 valid: yes
55 valid: yes
56 valid: yes
57 valid: yes
58 valid: yes
59 valid: yes
60 valid: yes
61 valid: yes
62 valid: yes
63 valid: yes
64 valid: yes
./sig_list
Signal 0 0 Signal 0 valid: yes
hup 1 Hangup valid: yes
int 2 Interrupt valid: yes
quit 3 Quit valid: yes
ill 4 Illegal instruction valid: yes
trap 5 Trace/BPT trap valid: yes
abrt 6 Abort trap valid: yes
emt 7 EMT trap valid: yes
fpe 8 Floating point exception valid: yes
kill 9 Killed valid: yes
bus 10 Bus error valid: yes
segv 11 Seg