linux 信号介绍

  |  

Linux中有许多处于不同状态的进程。这些进程属于用户应用程序或操作系统。我们需要一种机制让内核和这些进程协调它们的活动。其中一种方式是在一个进程有重大改变时通知其他进程,因此我们有了 信号 的概念。

Linux 信号的常见来源如图所示:
signal

信号基本上是一种单向通知。信号可以由内核发送给一个进程,或由一个进程发送给另一个进程,或者一个进程发送给它自己。

Linux信号的概念来源于Unix。在后来的Linux版本中,加入了实时(real-time)信号。信号是一种简单和轻量级的进程间通信形式,因此适用于嵌入式系统。

总共有 31 个标准信号,编号为 1-31。每个信号命名为“SIG”开头,后跟一个后缀(如INT、HUP、KILL等)。从 2.2 版开始,Linux 内核支持 33 种不同的实时信号,编号为 32-64,但应用程序应改为使用 SIGRTMIN + n 表示法。标准信号有特定用途,但 SIGUSR1 和 SIGUSR2 的使用可以由程序自定义。实时信号也可由程序定义。

Linux 信号的列表如下:

信号编号 信号名称 描述
1 SIGHUP 控制终端挂起或者断开连接
2 SIGINT 中断信号,通常由 Ctrl+C 发送
3 SIGQUIT 退出信号,通常由 Ctrl+\ 发送
4 SIGILL 非法指令信号
5 SIGTRAP 跟踪异常信号
6 SIGABRT 中止信号
7 SIGBUS 总线错误信号
8 SIGFPE 浮点错误信号
9 SIGKILL 强制退出信号(无法忽略或捕获)
10 SIGUSR1 用户定义信号1
11 SIGSEGV 段错误信号
12 SIGUSR2 用户定义信号2
13 SIGPIPE 管道破裂信号
14 SIGALRM 闹钟信号
15 SIGTERM 终止信号(无法忽略或捕获)
16 SIGSTKFLT 协处理器栈错误信号
17 SIGCHLD 子进程状态改变信号
18 SIGCONT 继续执行信号
19 SIGSTOP 暂停进程信号(无法忽略或捕获)
20 SIGTSTP 终端停止信号,通常由 Ctrl+Z 发送
21 SIGTTIN 后台进程尝试读取终端输入信号
22 SIGTTOU 后台进程尝试写入终端输出信号
23 SIGURG 套接字上的紧急数据可读信号
24 SIGXCPU 超时信号
25 SIGXFSZ 文件大小限制超出信号
26 SIGVTALRM 虚拟定时器信号
27 SIGPROF 分析器定时器信号
28 SIGWINCH 窗口大小变化信号
29 SIGIO 文件描述符上就绪信号
30 SIGPWR 电源失效信号
31 SIGSYS 非法系统调用信号
32 SIGRTMIN 实时信号最小编号
64 SIGRTMAX 实时信号最大编号

0号信号,即 POSIX.1 标准中所说的null信号,一般不使用,但在 kill 函数中有个特殊的用途。使用时没有信号被发送,但可以用来(相当不可靠)检查进程是否仍然存在。

Linux中的信号实现完全符合 POSIX 标准。最新的实现应该倾向于使用 sigaction 而不是传统的信号接口。

正如硬件子系统可以中断处理器一样,信号可以中断进程的执行。因此,它们被看作是软件中断。一般来说,中断处理程序(interrupt handlers)处理硬件中断,而信号处理程序(signal handlers)则处理信号导致的中断。

通常信号被映射到特定的按键输入,比如,SIGINT代表ctrl+c,SIGSTOP代表ctrl+z,SIGQUIT代表ctrl+\。

信号如何影响进程的状态?

signal affect

一些信号会终止正在接受信号的进程:SIGHUP、SIGINT、SIGTERM、SIGKILL。有一些信号不仅可以终止进程还会输出一些内核信息,以帮助程序员调试出错的地方,如SIGABRT(abort)、SIGBUS(bus error)、SIGILL(illegal instruction)、SIGSEGV(invalid memory reference无效内存引用)、SIGSYS(bad system call错误的系统调用) )。用于停止进程的信号有:SIGSTOP、SIGTSTP。 SIGCONT 是恢复已停止的进程。

一个程序可以覆盖信号的默认行为。例如,一个交互式程序可以忽略SIGINT(由ctrl+c输入产生)。不过有两个例外需要注意,SIGKILL和SIGSTOP,它们不能被忽略、阻止或用这种方式覆盖。

让我们看一个父进程和其子进程的例子。假设子进程向自己发送了SIGSTOP,子进程将被停止。这反过来又会触发SIGCHLD到父进程。然后,父进程可以使用SIGCONT向子进程发出继续运行的信号。当子进程从停止状态重新运行时,另一个SIGCHLD被发送到父进程。如果后来,子进程退出了,最后的SIGCHLD会被发送到父进程。

信号类似于异常(exception)吗?

一些编程语言能够使用诸如try-throw-catch这样的结构进行异常处理。
但信号与异常并不类似。相反,失败的系统或库调用会返回非零的退出代码。当一个进程被终止时,它的退出代码是128加信号编号。例如,一个被SIGKILL杀死的进程将返回137(128+9)。

信号是同步还是异步的?

信号既可以是同步,也可以是异步。

同步信号的出现是由于指令导致了一个无法恢复的错误,如非法地址访问。这些信号被发送到导致它的线程。这些信号也被称为陷阱(trap),因为它们也会导致陷阱进入内核的陷阱处理程序(trap handler)。

异步信号是对当前执行环境的外部信号。从另一个进程中发送 SIGKILL 就是这样一个例子。这些也被称为软件中断。

信号的生命周期是什么?

signal life cycle

一个信号经历三个阶段:

  • Generation(生成):信号可以由内核或任何进程生成,生成后会将其发送给特定的进程。信号由其编号表示,没有额外的数据或参数。因此,信号是轻量级的。但是,POSIX 实时信号传递额外的数据。可以生成信号的系统调用和函数包括 raise、kill、killpg、pthread_kill、tgkill 和 sigqueue。
  • Delivery(传递):信号在传递之前一直处于待处理状态。通常,内核会尽快将信号传递给进程。但是,如果对应的进程阻塞了信号,它将保持未处理状态直到解除阻塞。
  • Processing(处理):一旦信号被传递到,就会以多种方式中其中一种进行处理。每个信号都有一个默认的行为:忽略信号;或终止进程,有时使用核心转储(core dump);或停止/继续该过程。对于非默认行为,对应的处理函数会被调用。通过 sigaction 函数指定究竟采用哪一种处理方式。

什么是信号阻塞和解除阻塞?

signal block

信号打断了程序执行的正常流程。当进程正在执行一些关键代码或更新与信号处理程序共享的数据时,这是不希望看到的。阻断的引入解决了这个问题。不过代价是,信号处理被延迟了。

每个进程都可以指定它是否要阻塞一个特定的信号。如果被阻断,而信号确实发生了,操作系统将把该信号作为待处理信号。一旦进程解除阻断,该信号将被传递。当前被屏蔽的信号集合被称为信号屏蔽(signal mask)。

无限期地阻断一个信号是没有意义的。为了这个目的,进程可以在接受到信号后选择忽略它,被一个进程屏蔽的信号不会影响其他进程,他们可以正常接收信号。

信号屏蔽(Signal mask)可以用 sigprocmask(单线程)或 pthread_sigmask(多线程)来设置。 当一个进程有多个线程时,信号可以针对每个线程分别设置是否屏蔽。信号将被传递给任何一个没有阻断它的线程。从本质上讲,信号处理程序是针对某个进程的,信号掩码是针对某个线程的。

一个进程可以有多个待处理的信号吗?

是的,许多标准信号可以在进程中被挂起。然而,一个给定的信号类型只能有一个实例被挂起。这是因为信号的挂起和阻塞是作为位掩码(bitmask)实现的,每个信号类型只有一个位。例如,我们可以让 SIGALRM 和 SIGTERM 同时挂起,但我们不能有两个 SIGALRM
信号挂起。进程将只收到一个SIGALRM信号,即使是多次抛出。

通过实时信号,信号可以和数据一起排队,这样每个信号的实例都可以单独传递和处理。

POSIX没有规定标准信号的传递顺序,也没有规定如果标准信号和实时信号都在等待中会如何处理。然而在Linux中,会优先处理标准信号。对于实时信号,编号较低的信号首先被传递,如果一个信号类型有很多在排队,最早的一个会被首先传递。

信号历史发展

  1. 1990 信号在 POSIX.1-1990 标准中得到了描述。可以追溯至 IEEE标准1003.1-1988。
  2. 1993 实时扩展作为 POSIX.1b 发布。其中包含实时信号。
  3. 1999 随着内核版本 2.2 的发布,Linux 开始支持实时信号。
  4. 2001 POSIX.1-2001 标准中增加了更多信号:SIGBUS、SIGPOLL、SIGPROF、SIGSYS、SIGTRAP、SIGURG、SIGVTALRM、SIGXCPU、SIGXFSZ。
    list linux signals

简单 C 语言信号处理程序

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
// Example shows a custom handler for SIGINT
// but the handler reverts to default action for future signals.
// Thus, first ctrl+c will allow program to continue
// and second ctrl+c will terminate the program.
// 以下示例展示了对SIGINT信号的自定义处理程序
// 但是,处理程序会恢复为将来信号的默认操作。
// 因此,第一次按下Ctrl+C将允许程序继续执行
// 而第二次按下Ctrl+C将终止程序。

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

void sig_handler1(int num)
{
printf("You are here becoz of signal: %d\n", num);
signal(SIGINT, SIG_DFL);
}

int main()
{
signal(SIGINT, sig_handler1);
while(1)
{
printf("Hello\n");
sleep(2);
}
}
文章目录
  1. 1. 信号如何影响进程的状态?
  2. 2. 信号类似于异常(exception)吗?
  3. 3. 信号是同步还是异步的?
  4. 4. 信号的生命周期是什么?
  5. 5. 什么是信号阻塞和解除阻塞?
  6. 6. 一个进程可以有多个待处理的信号吗?
  7. 7. 信号历史发展
  8. 8. 简单 C 语言信号处理程序