signal

信号机制是UNIX系统最古老的机制之一,它不仅是内核处理程序在运行时发生错误的方式,还是终端管理进程的方式,并且还是一种进程间通信机制。它普遍到当你像往常一样使用 ctrl+c 终止了一个程序, 你可能甚至都没有意识到发生了一次进程间通信。有时候进程并不需要发送/接收,或者共享大量数据即可进行通信,进程A只需要发出一个提示信号来告知另一个进程B发生了一些事情,以便进程B可以在检测到信号时立即触发动作。

关于不同进程间通信方式的差异和使用场景见 ipc

信号机制由三部分构成

  1. 信号是怎么产生的,或者说是谁发送的?
  1. 信号是怎么投递到进程或者线程的?
  1. 信号是怎么处理的?

本文我们从这三方面入手详细介绍一下信号 signal 的机制

信号的产生

某一天我们在路口等待红绿灯,此时我们发现红灯变成了绿灯,但是前车依然没有动。此时我们并不会给他发短信或是下车走到车前敲敲玻璃告诉他已经绿灯了,而是会按下喇叭。前车司机听到喇叭声会猛然惊醒,然后检查发现已经变成绿灯,赶紧启动了车子继续行驶了

Clipboard_Screenshot_1755956059

这是一个相当形象的例子,事实上这也暗示了信号作为进程间通信时的使用场景,即利用内核的信号机制告知其他进程来完成异步通信。不需要双方协商好数据格式以及收发处理,而是用信号机制(喇叭声)心照不宣的完成信息的传递(信号灯变绿)和处理(可以行驶)。

信号类型

信号的产生方式也就是发送方有三种,分别是

终端发送属于进程发送的一种特例,因为shell本身是一个较为特殊的进程,因此单独做为一类

进程收到的信号可以来自于其他进程。但不是所有的进程都可以向其他任意一个进程发送信号,只有具有root权限的super user才可以这么做,对于普通user的进程,只能向属于同一user的进程发送信号,而用户对于内核线程发送的信号内核线程是不会响应的

在终端输入 kill -l 即可查看到所有的信号类型,一共 64 个。不难发现前 1-31 号信号有各自的缩写名字,34-64 号则使用 SIGRTMIN+x 和 SIGRTMAX-x 为代号。

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

最初设计时 UNIX系统只有1-31总共31个信号,这些信号每个都有特殊的含义和特定的用法,是 UNIX 最早期规定的标准信号。这些标准信号的实现有一个特点,它们是用bit flag实现的。这就会导致当一个信号还在待决的时候,又来了一个同样的信号,再次设置bit位是没有意义的,所以就会丢失再次收到的信号,因此 1-31 号信号也被称为不可靠信号

为了解决这个问题,后来POSIX规定增加32-64这33个信号作为实时信号(RT的由来),并规定实时信号不能丢失,要用队列来实现。这样后面再次到来的信号不会被丢弃而是直接放入队列等待依次处理。因此 32-64 的信号也被称为可靠信号

NumberNameDefault ActionCorresponding event (in Chinese)
0 测试进程/线程是否存在
1 SIGHUP Terminate 终端线路挂起,通常由终端或内核发送
2 SIGINT Terminate 键盘中断,通常由用户按下Ctrl+C触发
3 SIGQUIT Terminate 键盘退出,通常由用户按下Ctrl+\触发
4 SIGILL Terminate and dump core 非法指令,通常由CPU异常触发
5 SIGTRAP Terminate and dump core 跟踪陷阱,通常由调试器或程序触发
6 SIGABRT Terminate and dump core 程序调用abort函数,通常由进程自身触发
7 SIGBUS Terminate and dump core 总线错误,通常由硬件或内存访问错误触发
8 SIGFPE Terminate and dump core 浮点异常,通常由程序中的浮点错误触发
9 SIGKILL Terminate 强制终止程序,通常由进程或系统管理员发送
10 SIGUSR1 Terminate 用户定义信号1,通常由用户或进程发送
11 SIGSEGV Terminate and dump core 无效内存引用,通常由进程访问无效内存触发
12 SIGUSR2 Terminate 用户定义信号2,通常由用户或进程发送
13 SIGPIPE Terminate 向没有读取者的管道写入数据,通常由进程发送
14 SIGALRM Terminate 定时器信号,通常由alarm函数触发
15 SIGTERM Terminate 软件终止信号,通常由程序或系统管理员发送
16 SIGSTKFLT Terminate 协处理器栈错误,通常由硬件或内核发送
17 SIGCHLD Ignore 子进程停止或终止,通常由内核发送
18 SIGCONT Ignore 恢复停止的进程,通常由进程或终端发送
19 SIGSTOP Stop until SIGCONT 停止进程,通常由进程或终端发送
20 SIGTSTP Stop until SIGCONT 终端发出的停止信号,通常由用户按下Ctrl+Z触发
21 SIGTTIN Stop until SIGCONT 背景进程从终端读取数据时,通常由终端发送
22 SIGTTOU Stop until SIGCONT 背景进程向终端写入数据时,通常由终端发送
23 SIGURG Ignore 套接字上的紧急情况,通常由内核发送
24 SIGXCPU Terminate 超过CPU时间限制,通常由内核发送
25 SIGXFSZ Terminate 文件大小超过限制,通常由内核发送
26 SIGVTALRM Terminate 虚拟定时器到期,通常由程序或内核发送
27 SIGPROF Terminate 性能分析定时器到期,通常由程序或内核发送
28 SIGWINCH Ignore 窗口大小改变,通常由终端发送
29 SIGIO Terminate I/O操作变得可能,通常由内核发送
30 SIGPWR Terminate 电源故障,通常由内核发送
31 SIGSYS Terminate and Coredump 系统调用错误,通常由内核发送

上表记录了所有标准信号的含义。其中部分信号比较常见,比如中断程序的 SIGINT,强制杀死程序的 SIGKILL,暂停进程的 SIGTSTP 和继续进程的 SIGCONT。除此之外还有一些不那么常见但是很有意思的 signal

信号属性

标准信号都有默认的处理方式,也就是说,如果目标进程不注册某个信号的处理函数,那么当它收到这个信号后,就会执行信号默认的操作。这些标准信号有三个属性,是否可阻塞,是否可忽略,是否可捕获

关于某个信号是否可阻塞/忽略/捕获可以参考下图

Clipboard_Screenshot_1756043225

CSAPP ShellLab 中会要求实现一个简单的 shell,其中就包含了对信号的处理操作,感兴趣的读者可以尝试完成一下

参考

zood