在之前的文章信号入门详解中,我们已经了解了信号的基础知识,下来给大家介绍Linux信号处理机制的实现原理

一、信号机制

在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。

需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。

既然信号是异步的,这就涉及信号何时接收、何时处理的问题。

我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序便需要进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图:

信号处理示意图

接着为大家分析这个示意图:

我们可以将示意图分为信号接收、检测和处理这三个部分。

(1)、信号的接收

接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。我们将进程刚接收到信号并未处理的那种状态称为信号未决(Pending)。

注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

(2)、信号的检测

进程陷入内核态后,在两种场景下会对信号进行检测:

  • 进程从内核态返回到用户态前进行信号检测
  • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测

当发现有新信号时,便会进入下一步,信号的处理。

(3)、信号的处理

信号处理函数是运行在用户态的,之所以如此是由于若处于内核态程序便拥有过高的权利会对系统有潜在的危险,故调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。

接下来进程返回到用户态中,执行相应的信号处理函数。

信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。

至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。

二、信号在内核中的表示

实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

信号内核表示

每个信号都有两个标志位分别表⽰示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上示意图中

  1. SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  2.  SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  3.  SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数Handler。

如果在进程解除对某信号的阻塞之前这种信号产生过多次,普通信号在递达之前产生多次只计一次,这是由于每个信号只有一 个bit的未决标志,非0即1,无法记录该信号产生了多少次。而实时信号在递达之前产生多次可以依次放在一个队列里。这也就是普通信号称为不可靠信号的原因。

Linux中未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,sigset_t的实现与位图(bitset)的思想是相同的。这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐