前言
Linux 系统中,信号的保存涉及内核为每个进程所维护的 task_struct 结构体对象,确保信号在产生后、到进程处理前被正确记录和管理。本文将深入探索进程对信号的保存与处理。
一、信号的保存
1.1 信号保存概念引入
为什么要进行信号的保存?
进程在接收到信号时,可能不会立即处理,会在合适的位置进行处理。这就要求进程在接收到信号后、处理信号前,将收到的信号保存下来。
进程以什么形式保存信号?
在进程的 task_struct 结构体中,存在一个专门的位图结构。当进程收到一个信号时,就会将 task_struct 中的 signal 对应的比特位从 0 变为 1。这里的 0、1 表示信号的有、无,比特位的位置(第几个)表示信号的编号。操作系统向进程发送信号本质就是修改 task_struct 结构体对象中位图对应的比特位。操作系统是进程的管理者,只有它才有资格修改 task_struct 的内容属性。
1.2 信号的阻塞与保存
1.2.1 信号其他相关常见概念
- 实际执行信号的处理动作称为信号递达 (Delivery)
- 信号从产生到递达之间的状态,称为信号未决 (Pending)
- 进程可以选择阻塞 (Block) 某个信号
- 被阻塞的信号产生时将保持在未决状态(此时信号仍会被保存),直到进程解除对此信号的阻塞,才执行递达的动作。阻塞和忽略是不同的:只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
1.2.2 信号在内核中的表示
信号在内核中存在两个位图和一个方法(函数指针)向量表,每个信号都有两个标志位分别表示阻塞 (block) 和未决 (pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
在上图的例子中,SIGHUP 信号未阻塞也未产生过,当它递达时执行默认处理动作(SIG_DFL 表示该信号执行默认处理动作)。
SIGINT 信号产生过,但正在被阻塞,所以暂时不能递达。方法向量表中:SIG_IGN 表示该信号忽略处理,但是在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作(将处理动作设为默认、自定义)之后再解除阻塞。
SIGQUIT 信号未产生过,一旦产生 SIGQUIT 信号将被阻塞,它的处理动作是用户自定义函数 sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,Linux 是这样实现的:普通信号在递达之前产生多次只计一次(多余信号丢失),而实时信号在递达之前产生多次可以依次放在一个队列里。
对于这种内核级的结构,操作系统不允许用户直接对他们进行访问,所以系统提供了较多的接口,以便用户对信号的属性信息进行修改和获取(如:阻塞信号、修改执行方法)。
二、信号相关接口
系统给我们提供了一个 sigset_t 类型的结构体,帮助用户获取信号属性信息。
2.1 signal_t 结构体类型
从之前的介绍中,我们可以知道每个信号只有一个 bit 位的未决标志,非 0 即 1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型 sigset_t 来存储,sigset_t 称为信号集,这个类型可以表示每个信号的'有效'或'无效'状态。在阻塞信号集中'有效'和'无效'的含义是该信号是否被阻塞,而在未决信号集中'有效'和'无效'的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字 (Signal Mask),这里的'屏蔽'应该理解为阻塞而不是忽略。
2.2 信号集操作函数
sigset_t 类型对于每种信号用一个 bit 表示'有效'或'无效'状态,至于这个类型内部如何存储这些 bit 则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作 sigset_t 对象:


