概述
信号是Linux系统中用于进程间通信的一种机制,允许一个进程通知另一个进程发生了某些特定事件。信号可以来自硬件中断、用户输入,也可以来自其他进程或者内核本身。信号是一种异步通知机制,当某个事件发生时,操作系统会向目标进程发送一个信号。信号本质上是一个整数值,每个值代表一种不同的事件类型。
信号的类型
Linux定义了许多不同类型的信号,一些常用的信号、取值及其含义可参见下表。
信号名 | 取值 | 含义 |
SIGHUP | 1 | 终端挂起或控制终端关闭时发出,常用于重启守护进程 |
SIGINT | 2 | 用户按下Ctrl+C时产生,通常用来请求终止程序。 |
SIGKILL | 9 | 强制终止进程,无法被捕获或忽略。 |
SIGUSR1 | 10 | 用户自定义信号1,可以根据需要自由使用 |
SIGSEGV | 11 | 非法内存访问(即段错误),通常是由于程序试图访问未分配或保护的内存区域引起的。 |
SIGUSR2 | 12 | 用户自定义信号2,可以根据需要自由使用 |
SIGALRM | 14 | 定时器到期时发出,常用于实现超时功能。 |
SIGTERM | 15 | 请求正常终止进程,默认行为为优雅退出。 |
SIGCHLD | 17/18/20 | 子进程状态改变(比如:暂停、终止等)时发出,父进程可以通过这个信号得知子进程的状态变化。 |
信号的生命周期
一个信号从产生到被清除,主要会经历以下六个阶段。
1、信号的生成。当某个事件发生时,操作系统会生成相应的信号。信号可以由以下几种方式触发。
(1)硬件中断。比如:非法指令执行、除零错误等。
(2)软件事件。比如:用户按下Ctrl+C、调用kill函数发送信号给另一个进程等。
(3)定时器到期。比如:alarm或setitimer函数设置的定时器到期时,产生的SIGALRM信号。
(4)子进程状态变化。比如:子进程终止或暂停时,发出的SIGCHLD信号。
(5)内核检测到异常情况。比如:非法内存访问时,产生SIGSEGV信号。
2、信号的传递。一旦信号被生成,它会被加入到目标进程的未决信号集中。此时,信号处于“未决”状态,意味着它已经被创建,但还没有被传递给进程。传递方式分为以下两种。
(1)立即传递。如果进程当前没有阻塞该信号,则信号会被立即传递给进程。
(2)等待传递。如果进程已经设置了对该信号的阻塞,则信号会在未决信号集中等待,直到进程解除对该信号的阻塞。
3、信号的排队。对于大多数信号类型来说,Linux只保持一个实例,即使同一类型的多个信号连续到达,也只会记录最新的那个。然而,有一些特殊的信号(比如:SIGCHLD)是可以排队的,即可以同时存在多个实例。排队方式分为以下两种。
(1)非排队。比如:SIGINT,如果两个SIGINT信号几乎同时到达,只有最后一个会被记录下来。
(2)排队。比如:SIGCHLD,每个子进程状态的变化都会产生一个新的SIGCHLD信号,并且它们会被独立地排队。
4、信号的阻塞与解除阻塞。进程可以通过调用sigprocmask函数来改变其信号掩码,从而实现对特定信号的阻塞或解除阻塞。通过将信号添加到进程的信号掩码中,可以暂时阻止该信号的传递。通过从信号掩码中移除信号,可以让之前被阻止的信号重新变得可传递。
5、信号的处理。当信号成功传递给进程后,它将根据预先设定的行为进行处理。处理方式取决于信号的默认动作,或者用户自定义的信号处理函数。
(1)默认行为。每种信号都有一个默认的动作,比如:终止进程、忽略信号、生成核心转储文件等。如果程序没有为某种信号注册处理函数,那么它将按照默认行为执行。
(2)自定义处理函数。我们可以通过signal或sigaction函数为特定信号注册自定义处理函数,这样当信号到达时,程序会切换到信号上下文并调用对应的处理函数。
6、信号的清除。一旦信号被处理完毕,就被认为是已处理的状态,并从进程的未决信号集中移除。这意味着,同一个信号不会再次触发相同的处理逻辑,除非又有新的相同类型的信号生成。