「Linux 系统编程」信号集操作函数
1 未决信号集和阻塞信号集
1.1 未决信号集 (Pending Signal Set)
当一个信号被发送给进程时(例如,由另一个进程通过 kill()
发送,或由内核产生,如 SIGSEGV
),这个信号首先会被加入到该进程的未决信号集中。
未决信号集 是内核为每个进程维护的一个 位图数据结构,用于实时追踪哪些信号已经产生但尚未被进程消费处理。该位图中的 每一位直接映射一个特定的信号编号,位值为 1
表示对应信号处于未决状态。
处理机制:
- 置位操作:当信号产生时,内核通过原子操作将对应位设置为
1
。 - 清零时机:当信号被递送给进程处理时,内核将相应位清零。
- 队列语义:对于 标准信号(1-31),多次触发同一信号仅导致单次置位;对于 实时信号(34-64),内核通过 外部队列维护,但未决位图仍记录至少有一个该信号待处理。
特点:
-
一个信号在未决信号集中 最多只会存在一次。即使同一标准信号被发送多次,在未决集里也只会标记为有一个(这是标准信号的“不可靠”性,即可能丢失)。
-
对于实时信号(
SIGRTMIN
到SIGRTMAX
),多个相同的信号可以在未决队列中排队,不会丢失。
1 |
|
1.2 阻塞信号集 / 信号掩码 (Blocked Signal Set / Signal Mask)
阻塞信号集 是进程控制的位图掩码,用于 精确管理信号递送的时机。该位图定义了信号屏蔽策略,其中置位的信号将 被内核暂时抑制递送,实现临界区的原子性保护。
处理机制:
- 屏蔽机制:当位图中某位为
1
时,对应信号将被内核阻塞 - 延迟递送:被阻塞的信号产生时,内核 只设置未决位图,而不立即递送信号给进程
- 解除阻塞:当进程 清除阻塞位图中的对应位 时,内核会 检查未决位图并处理积压信号。处理完成后再递送信号给进程。
1 |
|
1.3 两集协同工作的位图操作
-
阻塞 决定了一个信号何时被处理(现在还是将来)。
-
未决 记录了一个信号是否已产生但尚未被处理。
2 信号集操作函数
为了设置和修改 阻塞信号集,我们需要一种方法来表示和操作信号集合。Linux 提供了一系列函数来操作一个特殊的类型 sigset_t
(信号集类型)。
2.1 数据类型 sigset_t
这是一个不透明数据类型,用于 表示一个信号集合。不能直接操作它的内部位掩码,而 必须使用提供的函数。
2.2 基本操作函数
定义在 <signal.h>
头文件中,成功返回 0
,失败返回 -1
。
-
int sigemptyset(sigset_t *set)
- 作用:初始化一个
sigset_t
集合,使其 不包含任何信号(全部清零)。 - 注意:在使用一个新的
sigset_t
变量之前,必须 先调用此函数或sigfillset
进行初始化,否则它可能包含随机垃圾数据。
- 作用:初始化一个
-
int sigfillset(sigset_t *set)
- 作用:初始化一个
sigset_t
集合,使其包含所有信号(全部置1)。
- 作用:初始化一个
-
int sigaddset(sigset_t *set, int signum)
- 作用:将特定信号
signum
添加 到集合set
中。
- 作用:将特定信号
-
int sigdelset(sigset_t *set, int signum)
- 作用:将特定信号
signum
从集合set
中 移除。
- 作用:将特定信号
-
int sigismember(const sigset_t *set, int signum)
- 作用:检查信号
signum
是否在集合set
中。 - 返回值:在集合中返回
1
,不在返回0
,错误返回-1
。
- 作用:检查信号
2.3 应用信号掩码 sigprocmask
创建好一个信号集后,我们需要用 sigprocmask
来 设置进程的阻塞信号集(信号掩码)。
sigprocmask
函数:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
- 作用:检查或修改进程的 阻塞信号集(信号掩码)。
- 参数
how
:指定如何修改当前的阻塞信号集。SIG_BLOCK
:将set
中的信号 加入 当前的阻塞信号集。新的掩码 = 当前掩码 |set
。SIG_UNBLOCK
:将set
中的信号 移除 当前的阻塞信号集。新的掩码 = 当前掩码 & ~set
。SIG_SETMASK
:直接使用set
集合 替换 当前的阻塞信号集。新的掩码 =set
。
- 参数
oldset
:如果不是NULL
,则函数会将 调用前 的阻塞信号集保存到oldset
中。时常用于恢复原有掩码。
2.4 获取未决信号集 sigpending
int sigpending(sigset_t *set)
- 作用:获取当前进程的 未决信号集。将当前处于未决状态的信号集合通过
set
参数返回。
3 信号操作函数使用示例
下面的示例中,演示了如何阻塞 SIGINT
信号(由 Ctrl+C 产生),然后检查这个信号是否处于未决状态。
1 |
|
- 程序启动后,立即阻塞了
SIGINT
。 - 在
sleep(5)
期间,如果按下Ctrl+C
,SIGINT
信号会产生。 - 但由于信号被阻塞,它不会被处理,而是被加入到 未决信号集。
sigpending()
会检测到它,并打印"SIGINT is pending..."
。- 当调用
sigprocmask(SIG_SETMASK, &old_set, NULL)
解除阻塞时,内核会立即检查未决信号集。 - 发现有一个未决的
SIGINT
,于是立刻将其从未决集中清除,并递送给进程。 - 如果进程没有为
SIGINT
设置自定义处理函数,默认行为是终止进程,所以程序会立即退出,最后的printf
和sleep(2)
可能不会执行。