sigset_t 是一个用于表示信号集合的数据类型。它主要用于信号处理机制中,允许程序对一组信号进行统一的管理、阻塞或检测。
1. 基本概念
- 定义位置 :定义在头文件
<signal.h>中。 - 用途 :由于信号的数量可能超过标准整数类型的位数,且为了可移植性,POSIX 标准定义了
sigset_t来存储信号集。底层实现通常是一个位图(bitmask)或无符号长整型数组。 - 不可直接操作 :为了保证可移植性,严禁 直接修改
sigset_t变量的内部成员(如直接赋值或位运算)。必须使用标准的库函数来初始化和操作信号集。
2. 核心操作函数
所有操作 sigset_t 的函数都声明在 <signal.h> 中。在使用任何信号集之前,必须先对其进行初始化。
初始化函数
- **
int sigemptyset(sigset_t *set);** - 初始化信号集,使其不包含任何信号(清空集合)。
- 成功返回 0,失败返回 -1。
- **
int sigfillset(sigset_t *set);** - 初始化信号集,使其包含系统支持的所有信号(填充集合)。
- 成功返回 0,失败返回 -1。
增删与检测函数
- **
int sigaddset(sigset_t *set, int signum);** - 将指定的信号
signum添加到信号集set中。
- 将指定的信号
- **
int sigdelset(sigset_t *set, int signum);** - 从信号集
set中删除指定的信号signum。
- 从信号集
- **
int sigismember(const sigset_t *set, int signum);** - 判断信号
signum是否存在于信号集set中。 - 若存在返回 1,不存在返回 0,出错返回 -1。
- 判断信号
注意 :上述函数成功时均返回 0,失败时返回 -1 并设置
errno。常见的错误包括无效的信号编号 (EINVAL)。
3. 主要应用场景
A. 阻塞信号/解除阻塞信号 (Signal Masking)
通过 sigprocmask() 函数(单线程)或 pthread_sigmask()(多线程)来检查或更改进程/线程的信号屏蔽字。
- 典型用法 :
- 创建一个信号集。
- 使用
sigemptyset初始化。 - 使用
sigaddset添加需要阻塞的信号。 - 调用
sigprocmask(SIG_BLOCK, &set, NULL)阻塞这些信号。 - 执行临界区代码。
- 调用
sigprocmask(SIG_UNBLOCK, &set, NULL)恢复信号。
cpp
#include <signal.h>
#include <stdio.h>
void critical_section() {
sigset_t set;
// 1. 初始化空集
if (sigemptyset(&set) == -1) {
perror("sigemptyset");
return;
}
// 2. 添加要阻塞的信号 (例如 SIGINT, 即 Ctrl+C)
if (sigaddset(&set, SIGINT) == -1) {
perror("sigaddset");
return;
}
// 3. 阻塞信号
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
perror("sigprocmask");
return;
}
// --- 临界区代码开始 ---
printf("Critical section: SIGINT is blocked.\n");
// 模拟耗时操作
sleep(5);
// --- 临界区代码结束 ---
// 4. 解除阻塞
if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) {
perror("sigprocmask unblock");
}
}
B. 在信号处理程序中保护关键信号
在使用 sigaction() 注册信号处理函数时,可以通过 struct sigaction 中的 sa_mask 字段(类型为 sigset_t)指定在处理该信号期间需要额外阻塞的其他信号。这可以防止信号处理程序被同类型或其他信号中断,保证处理的原子性。
C. 检查未决信号 (Pending Signals)
使用 sigpending(&set) 函数可以获取当前进程中已被阻塞但尚未处理的信号集合,结果存储在 sigset_t 变量中,随后可用 sigismember 检查具体哪些信号处于未决状态。
4. 注意事项
- 必须初始化 :在使用
sigaddset、sigdelset等函数前,必须先调用sigemptyset或sigfillset对sigset_t变量进行初始化,否则行为是未定义的。 - 不可阻塞的信号 :
SIGKILL和SIGSTOP不能被阻塞、忽略或捕获。尝试将它们加入阻塞集会被系统忽略。 - 多线程环境 :在多线程程序中,每个线程有独立的信号掩码。应使用
pthread_sigmask()而不是sigprocmask()来修改线程特定的信号掩码。sigprocmask()在多线程中的行为是未定义的(尽管在某些实现中等效于修改调用线程的掩码)。 - 返回值检查:在生产代码中,务必检查所有信号集操作函数的返回值,以处理潜在的错误。
总结
sigset_t 是 C 语言系统编程中管理信号的基础工具。它通过一组标准化的 API (sigemptyset, sigaddset, sigdelset, sigismember, sigfillset) 提供安全、可移植的信号集合操作方式,广泛应用于信号阻塞、临界区保护以及异步信号的同步化处理中。
5. 为什么推荐 sigaction 而非 signal?
- 可移植性 :
signal()的行为在不同 Unix 版本间存在差异(例如处理完后是否重置为默认行为),而sigaction()遵循 POSIX 标准,行为一致。 - 原子性 :
sigaction()允许在注册新处理函数的同时获取旧的处理函数,避免了检查与设置之间的竞态条件。 - 精细控制 :通过
sa_mask和sa_flags,开发者可以精确控制信号处理期间的屏蔽行为和系统调用重启逻辑,这是signal()无法做到的。 - 信息丰富 :支持
SA_SIGINFO,可以获取信号产生的详细上下文