Linux系统中信号的保存涉及内核为每个进程维护的数据结构,确保信号在产生后、处理前被正确记录和管理。以下是详细的解释:
1. 信号的基本概念
- 信号(Signal) :用于通知进程发生了特定事件的异步通知机制,如
SIGINT
(Ctrl+C终止进程)、SIGTERM
(终止请求)等。 - 信号处理方式:进程可为每个信号指定处理方式------默认(如终止)、忽略或自定义处理函数。
2. 信号的保存结构
- 待处理信号集(Pending Signals) :记录已产生但尚未递送给进程的信号。由内核通过以下结构管理:
- 标准信号(1-31):使用位掩码(每个信号对应一个位)记录是否发生。多次触发同一信号时,仅记录一次(不排队)。
- 实时信号(34-64):使用队列保存,确保多次触发时每个信号实例均被记录。
- 阻塞信号集(Blocked Signals):通过信号掩码(Signal Mask)标记被阻塞的信号。阻塞的信号会停留在待处理集中,直到解除阻塞。
3. 关键数据结构
- 进程描述符(
task_struct
) :内核为每个进程维护的结构,包含:struct sigpending pending
:待处理信号集合。sigset_t signal
:标准信号的位掩码。struct list_head list
:实时信号的队列。
sigset_t blocked
:阻塞信号掩码。struct sigaction sigaction[NSIG]
:信号处理函数表。
4. 信号递送流程
- 信号产生 :通过
kill()
、终端输入或硬件异常等方式触发。 - 内核检查阻塞状态 :
- 若信号未被阻塞,立即递送(调用处理函数或执行默认操作)。
- 若被阻塞,信号被添加到
pending
集合(标准信号置位,实时信号入队)。
- 解除阻塞时处理 :
- 当进程调用
sigprocmask()
解除阻塞或从系统调用返回时,内核检查pending
集合,递送未阻塞的信号。
- 当进程调用
- 处理顺序 :
- 标准信号 :按信号编号从小到大处理。
- 实时信号:按发送顺序处理(队列先进先出)。
5. 信号处理期间的阻塞
- 默认情况下,进程执行信号处理函数时,自动阻塞当前信号,防止递归调用。
- 可通过
sigaction()
的sa_mask
字段指定额外阻塞的信号,确保处理函数原子性。
6. 示例场景
- 场景1 :进程阻塞
SIGINT
(Ctrl+C):- 用户多次按下Ctrl+C,
SIGINT
被标记在pending
中(仅一次)。 - 解除阻塞后,进程处理一次
SIGINT
。
- 用户多次按下Ctrl+C,
- 场景2 :发送实时信号
SIGRTMIN
多次:- 每次发送均入队,解除阻塞后按顺序处理所有实例。
7. 注意事项
- 信号丢失:标准信号不排队,频繁触发可能导致丢失。
- 可重入性 :信号处理函数应使用异步安全函数(如
write()
),避免死锁或数据损坏。 - 系统调用中断 :信号可能中断阻塞的系统调用(如
read()
),需检查EINTR
错误并重试。
总结
Linux通过pending
集合和blocked
掩码管理信号保存,标准信号与实时信号分别采用位图和队列实现不同语义。理解这些机制有助于编写健壮的信号处理代码,避免竞态条件和信号丢失。