信号处理的整体流程

在 Linux 等操作系统中,信号(signal)的生命周期通常可以划分为三个阶段:

  1. 信号的发送(Generation / Delivery Request)
    信号由内核或用户空间触发,通过系统调用或硬件异常等方式产生,并交由操作系统发送给目标进程。
  2. 信号的保存(Pending / Queuing)
    由于信号的处理具有异步性,目标进程在接收到信号后,通常不会立即处理,而是由内核暂存。
  3. 信号的递达与处理(Delivery & Handling)
    在合适的时机(如从内核态返回用户态前),内核将信号递达进程,并执行对应的处理动作(默认处理 / 忽略 / 自定义处理函数)。

信号保存机制:Pending 位图

使用位图的原因:

  • 空间高效:1 bit 表示一个信号
  • 操作高效
    • 设置:pending |= (1 << sig)
    • 清除:pending &= ~(1 << sig)
    • 查询:pending & (1 << sig)
  • 天然支持集合运算(位运算)
    在内核中,每个进程(PCB,即 task_struct)都维护与信号相关的数据结构。其中最核心的是:

Pending 信号集合(pending set)

  • 本质:位图(bitmap)
  • 作用:记录当前进程“已收到但尚未处理”的信号
数据结构特征
  • 通常使用一个整型(如 unsigned int 或更大位宽)表示
  • 每一位(bit)对应一个信号编号(signal number)
位图语义定义
  • 位的位置(bit position)
    表示信号编号(通常从低位到高位对应 signal 1, 2, 3, …)
  • 位的值(bit value)
    • 0:该信号未到达
    • 1:该信号已到达(处于 pending 状态)
示例(简化表示)
pending bitmap: 0000 0000 0000 0101

含义:
- 第1位 = 1 → 收到 signal 1
- 第3位 = 1 → 收到 signal 3
- 其他位 = 0 → 未收到对应信号

在这里插入图片描述


数据结构

信号递达(Delivery)

信号递达条件的形式化描述
在 Linux 中,信号是否能够被递达(delivery),取决于两个核心条件:

  • 该信号已经产生(处于 pending 状态)
  • 该信号未被阻塞(不在 block mask 中)

位运算判定逻辑

对于信号编号 sig,可以通过位运算提取其在位图中的状态:

mask = 1 << (sig - 1);
  • mask 用于定位该信号在位图中的对应比特位

递达判定伪代码

if(((1<<(signo-1))&pcb->block)
{
//signo信号被阻塞,不抵达
}
else
{
if(((1<<(signo-1))&pcb->pending)
{
//抵达信号
}
}

信号 sig 能够被递达,当且仅当:

pending[sig] == 1 && block[sig] == 0

handler数组

除位图外,在操作系统内核中,我们可以通过定义函数指针数组来管理信号处理函数。假设我们声明一个名为 handler_t 的类型别名,它代表一个返回类型为 void、接受信号编号作为参数的函数指针类型。具体定义如下:

typedef void (*handler_t)(int);
handler_t handlers[NUM_SIGNALS];

详细解释:

typedef void (*handler_t)(int signal);

此时,handler_t 是一个指向接收信号编号并返回 void 的函数的指针类型。进一步,如果我们定义一个 handler 数组,每个元素都是一个 handler_t 类型的指针,这个数组便用于存储各个信号的处理函数。

handler_t handler[32];

在此,handler 数组的大小为 32,每个元素都是一个信号处理函数指针。数组的下标(从 0 开始)代表了信号的编号,数组中的每个元素指向一个处理该信号的函数。具体地说,handler[0] 表示对信号编号 1 的处理函数,handler[1] 表示对信号编号 2 的处理函数,依此类推。

这种方法可以有效地为每个信号指定一个独立的处理函数,通过索引访问对应的处理函数并执行。这个结构通常用于操作系统内核中,尤其是信号处理机制中,用来响应各种不同的信号。

通过这种方式,我们可以清晰地管理和访问不同信号的处理函数,使得系统能够根据信号的编号迅速定位并执行相应的处理逻辑。

在这里插入图片描述


核心内核数据结构 (Kernel Data Structures)

在内核进程控制块(PCB,即 task_struct)中,信号的管理主要由三张表(结构)协同完成:

  • Pending 位图 (未决信号集): 对应你提到的“比特位”。它是一个位图(Bitmap),每一位代表一种信号。当信号产生但尚未处理时,对应的比特位由 0 置 1。
  • Blocked 位图 (信号屏蔽字/掩码): 同样是一个位图。如果某位被置 1,表示该信号被“阻塞”。即便 Pending 位图对应位置为 1,操作系统也不会立即处理该信号。
  • Handler 数组 (信号处理函数表): 对应你提到的“数组”。这是一个函数指针数组。数组的下标与信号编号(Signal ID)一一对应。每个元素存储的是该信号的处理动作(默认 SIG_DFL、忽略 SIG_IGN 或用户自定义的回调函数地址)。

A. 信号注册 (Signal Registration)

通过系统调用(如 signal() 或更健壮的 sigaction()),用户态程序将自定义处理函数(Callback)的地址传递给内核。内核根据信号编号作为偏移量(Offset),在 Handler 数组的对应下标处覆盖原有的函数指针。

B. 信号产生与递达 (Generation & Delivery)
  • 信号未决 (Pending): 当信号产生时,内核修改 PCB 中 Pending 位图的相应比特位。
  • 信号递达 (Delivery): 内核在从内核态返回用户态的时刻(检查点),会轮询 Pending 位图。
  • 逻辑判定: 内核执行如下逻辑:if (pending & ~blocked)。即:如果信号已产生且未被阻塞,则触发递达。
C. 信号捕捉 (Signal Catching)

当信号被递达时,内核根据信号编号在 Handler 数组中进行寻址,获取函数地址。

  • 如果地址指向用户定义的函数,执行流将从内核态切换回用户态,执行回调方法
  • 执行完毕后,通过系统调用(如 sigreturn)再次回到内核,最后返回主程序的断点处继续执行。

信号处理全周期

A. 信号的预屏蔽 (Signal Pre-masking)

进程通过调用 sigprocmask 系统调用,在信号尚未产生时修改 blocked 位图。这在术语上称为**“临界区保护”**——为了防止特定代码段执行时被信号异步中断,预先通过 掩码 (Mask) 屏蔽掉相关信号。

B. 信号的挂起与未决 (Signal Pending & Suspending)

当一个信号被发送至进程(信号产生),而该信号在 blocked 位图中对应位为 1 时,内核会将该信号记录在 pending 位图中。此时信号处于 未决状态 (Pending State),即信号被挂起 (Suspended)

C. 状态判定的逻辑运算 (Logical Predication)

内核在进行信号检查(通常在内核态切换回用户态的瞬间)时,会执行以下位运算:

V a l i d _ S i g n a l s = p e n d i n g   &   ∼ b l o c k e d Valid\_Signals = pending \ \& \ \sim blocked Valid_Signals=pending & blocked

  • 若结果为 0,说明所有已产生的信号均被屏蔽,内核不进行任何动作。
  • 若结果非 0,则说明存在可递达信号。
D. 信号递达与上下文切换 (Signal Delivery & Context Switching)

一旦 blocked 位图中的屏蔽位解除(通过 sigprocmask 重置),原先处于未决态的信号立即变为 递达态 (Delivered)。内核根据 sighand 数组中的函数指针进行查表偏移,并将执行流从主控制流切换至 Signal Handler


总结
关键特性 术语定义 技术内幕
独立性 正交性 (Orthogonality) Pending 与 Blocked 相互独立,互不干涉位操作。
原子性 原子位图操作 内核对位图的修改是原子性的,确保多核并发下的同步。
确定性 基于下标寻址 处理信号时,内核根据信号 ID 对 Handler 数组进行 O ( 1 ) O(1) O(1) 复杂度的查找。
异步性 异步通知机制 信号的产生与处理在时间上是解耦的,通过 Pending 位图实现缓冲。
信号机制是操作系统通过 Pending 位图(存状态)Blocked 位图(存策略) 以及 Handler 数组(存行为) 三者协同,实现的一套非阻塞、异步、且支持可配置过滤的进程间通信(IPC)模型。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐