信号捕捉
在信号处理中,明确进程接收信号后的处理时机与方式十分重要。在不考虑信号屏蔽的情况下,进程收到信号后未必会立即处理,往往会选择在'合适的时机'进行——这通常是因为进程当前可能正在执行更关键的操作,不适合被信号打断。
要理解这一点,需先明确代码执行的两种基本模式:
- 内核态:是操作系统运行时的状态,主要用来执行内核代码
- 用户态:用 CPU 执行用户自己的代码,所处的状态
那么'合适的时机'指的是什么时候呢?具体又是如何处理的呢?
- 进程从内核态切换回用户态的时候
- OS 会检测当前进程的三张表,决定要不要处理信号
既然是从内核态切回用户态,说明我进程之前从用户态进入过内核态(实际上系统调用就会从用户态切到内核态)。下文将对信号捕捉流程进行详细解释。
信号捕捉流程
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
OS 处理默认和忽略,是很容易的事情,OS 默认就有权限!
- 如果是 ignore,修改 pending 表由 1 置 0,返回代码继续执行;
- 如果是 dfl,如果该信号动作是暂停,此时在 OS 内部,把进程 PCB 状态由 r 置为 s,将 PCB 从运行队列剥离出来,加入到等待队列里
而自定义处理方法的处理过程比较复杂,因此就拿它来进行说明:
- 用户程序注册了 SIGQUIT 信号的处理函数 sighandler
- 当前正在执行 main 函数,这时发生中断或异常切换到内核态。
- 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号 SIGQUIT 递达。
- 内核决定返回用户态后不是恢复 main 函数的上下文继续执行,而是执行 sighandler 函数,sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
- sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。
- 如果没有新的信号要递达,这次再返回用户态就是恢复 main 函数的上下文继续执行了。
细节 1:我们说执行代码只有两种模式,那么执行自定义方法的是用户还是 OS 呢?需要明确的是只能是用户,不能是 OS!因为是在执行自己的代码啊。
细节 2:执行自定义方法是如何做到从用户态再次进入内核的?将 do_signal 地址压入到栈里,再通过 sigretum 特殊系统调用放入到 eip 里,此时就可以回到内核态继续运行。(实际上是在执行一条能触发软中断的指令)
可是这也太复杂了吧,一个信号捕捉流程就涉及到这么多内容,别担心,这里画出一张表让你一秒钟记住它:

穿插 -- 操作系统如何运行
硬件中断
若让操作系统定期主动扫描键盘硬件状态,仔细想想,这显然不现实。毕竟操作系统作为系统资源的统筹者,绝不会做这种持续浪费 CPU 资源的事。
那有没有更高效的方式?当然有!我们可以让外部设备主动'告知'操作系统:当设备准备好(比如有按键输入)时,主动触发一个信号,通知操作系统来处理。
这,正是硬件中断机制诞生的初衷——用硬件层面的'主动通知',替代软件层面低效的'被动轮询',让系统资源利用更高效,也让硬件交互更及时。

- 中断向量表就是操作系统的一部分,启动就加载到内存中了
- 通过外部硬件中断,操作系统就不需要对外设进行任何周期性的检测或者轮询
- 由外部设备触发的,中断系统运行流程,叫做硬件中断
我们之前讲过当代码中有 scanf 时,键盘这个设备会等待我们的响应(此时是在设备的等待队列里),而一旦我们键盘按下时,OS 就得知我们的键盘是有数据的。由此可以猜想是键盘这个设备告诉 OS 我已经准备就绪了!










