跳到主要内容

Linux 信号

Linux 系统提供了一种进程间异步通信的方式 —— 信号(signal)。信号可认为是一种软中断机制,许多重要的程序都需要处理信号。例如,在终端输入 Ctrl + C 快捷键可以中断程序,实际上就是给该进程发送了一个 SIGINT 信号。

信号类型

Linux 系统一共定义了 64 种信号,分为两大类:可靠信号与不可靠信号,前 32 种信号为不可靠信号,后 32 种为可靠信号。

  • 不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,发送多次相同的信号,进程只能收到一次,信号值取值区间为 1~31;
  • 可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次。信号值取值区间为 32~64。

在 Linux 系统中,可以通过 kill -l 命令查看所有 signal 信号,如下:

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

信号列表

下表列出了 1~31 的信号及其描述。

信号值信号名描述
1SIGHUP挂起
2SIGINT中断
3SIGQUIT退出
4SIGILL非法指令
5SIGTRAP断点或陷阱指令
6SIGABRTabort 发出的信号
7SIGBUS非法内存访问
8SIGFPE浮点异常
9SIGKILLkill 信号(不能被忽略、处理和阻塞)
10SIGUSR1用户信号1
11SIGSEGV无效内存访问
12SIGUSR2用户信号2
13SIGPIPE管道破损,没有读端的管道写数据
14SIGALRMalarm 发出的信号
15SIGTERM终止信号
16SIGSTKFLT栈溢出
17SIGCHLD子进程退出(默认忽略)
18SIGCONT进程继续
19SIGSTOP进程停止(不能被忽略、处理和阻塞)
20SIGTSTP进程停止
21SIGTTIN进程停止,后台进程从终端读数据时
22SIGTTOU进程停止,后台进程想终端写数据时
23SIGURGI/O 有紧急数据到达当前进程(默认忽略)
24SIGXCPU进程的 CPU 时间片到期
25SIGXFSZ文件大小的超出上限
26SIGVTALRM虚拟时钟超时
27SIGPROFprofile 时钟超时
28SIGWINCH窗口大小改变(默认忽略)
29SIGIOI/O 相关
30SIGPWR关机(默认忽略)
31SIGSYS系统调用异常

信号处理

Linux 信号的处理有三种方法,分别是:忽略、捕捉和默认动作

  • 忽略信号:大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为这两中信号向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的进程,显然是内核设计者不希望看到的场景。
  • 捕捉信号:需要告诉内核,用户希望如何处理某一种信号。也就是说用户需要写一个信号处理回调函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

具体的信号默认动作可以使用 man 7 signal 来查看系统的具体定义。

Signal NameSignal NumberDescription
SIGHUP1Hang up detected on controlling terminal or death of controlling process
SIGINT2Issued if the user sends an interrupt signal (Ctrl + C)
SIGQUIT3Issued if the user sends a quit signal (Ctrl + D)
SIGFPE8Issued if an illegal mathematical operation is attempted
SIGKILL9If a process gets this signal it must quit immediately and will not perform any clean-up operations
SIGALRM14Alarm clock signal (used for timers)
SIGTERM15Software termination signal (sent by kill by default)

示例代码

完整示例代码可从 GitHub 仓库获取。

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void sigroutine(int signo)
{
/* 信号处理例程,其中 signo 将会得到信号的值 */
switch(signo)
{
case SIGHUP:
printf("Get a signal -- SIGHUP\n"); // 1
break;
case SIGINT:
printf("Get a signal -- SIGINT\n"); // 2
break;
case SIGQUIT:
printf("Get a signal -- SIGQUIT\n"); // 3
break;
}
return;
}

int main()
{
printf("process id is %d\n", getpid());

signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法
signal(SIGINT, sigroutine);
signal(SIGQUIT, sigroutine);

for (;;)
;
}

使用 kill 命令可以发送信号,命令格式如下:

$ kill -signal pid

运行示例程序:

$ ./simple_example 
process id is 168125

打开另一个终端,给该进程发送不同的信号,如下:

$ kill -3 168125
$ kill -2 168125
$ kill -1 168125

可以看到示例程序的窗口打印如下信息:

Get a signal -- SIGQUIT
Get a signal -- SIGINT
Get a signal -- SIGHUP