sigprocmask 函数深度解析
sigprocmask
函数深度解析
sigprocmask
是 Linux 信号处理的核心函数,用于控制进程的信号屏蔽字(Signal Mask)。它直接决定了哪些信号会被阻塞(blocked),是构建可靠信号处理机制的基础。
函数原型
#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数详解
1. how
:操作类型(行为模式)
值 | 宏定义值 | 含义 | 数学表达 |
---|---|---|---|
SIG_BLOCK | 0 | 将 set 中的信号添加到当前屏蔽字 | 新掩码 = 当前掩码 ∪ set |
SIG_UNBLOCK | 1 | 将 set 中的信号移除出当前屏蔽字 | 新掩码 = 当前掩码 - set |
SIG_SETMASK | 2 | 将当前屏蔽字直接替换为 set 指定的信号集 | 新掩码 = set |
⚠️ 非法值处理:如果
how
不是以上三个值,函数返回 -1 并设置errno = EINVAL
2. set
:输入信号集指针
值 | 含义 |
---|---|
非NULL | 指向包含操作信号的 sigset_t 对象,具体操作由 how 决定 |
NULL | 不改变当前屏蔽字(此时 how 被忽略),仅用于获取当前屏蔽字到 oldset |
3. oldset
:输出信号集指针
值 | 含义 |
---|---|
非NULL | 函数将在此存储修改前的信号屏蔽字 |
NULL | 不保存之前的屏蔽字 |
返回值
值 | 含义 |
---|---|
0 | 成功 |
-1 | 失败,设置 errno |
错误码(errno)
错误码 | 含义 | 常见触发场景 |
---|---|---|
EFAULT | 无效的内存地址 | set 或 oldset 指向非法地址 |
EINVAL | 无效的 how 参数 | how 不是 SIG_BLOCK/UNBLOCK/SETMASK |
核心功能图解
使用场景详解
场景1:阻塞特定信号(SIG_BLOCK)
sigset_t new_set;
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT); // 添加 SIGINT
sigaddset(&new_set, SIGTERM); // 添加 SIGTERM// 阻塞 SIGINT 和 SIGTERM
sigprocmask(SIG_BLOCK, &new_set, NULL);/* 临界区代码(不会被 SIGINT/SIGTERM 中断) */
场景2:解除信号阻塞(SIG_UNBLOCK)
sigset_t unblock_set;
sigemptyset(&unblock_set);
sigaddset(&unblock_set, SIGINT);// 解除 SIGINT 阻塞
sigprocmask(SIG_UNBLOCK, &unblock_set, NULL);
场景3:完全替换屏蔽字(SIG_SETMASK)
sigset_t full_set, old_set;
sigfillset(&full_set); // 包含所有信号// 阻塞所有信号,保存旧屏蔽字
sigprocmask(SIG_SETMASK, &full_set, &old_set);/* 绝对安全临界区 */// 恢复原始屏蔽字
sigprocmask(SIG_SETMASK, &old_set, NULL);
场景4:获取当前屏蔽字(set=NULL)
sigset_t current_mask;// 获取但不修改当前屏蔽字
sigprocmask(SIG_BLOCK, NULL, ¤t_mask);// 检查 SIGINT 是否被阻塞
if (sigismember(¤t_mask, SIGINT)) {printf("SIGINT is currently blocked\n");
}
特殊信号处理规则
-
不可阻塞信号:
// 尝试阻塞 SIGKILL/SIGSTOP 会被忽略 sigset_t mask; sigaddset(&mask, SIGKILL); sigprocmask(SIG_BLOCK, &mask, NULL); // 实际无效
-
信号处理期间的自动屏蔽:
void handler(int sig) {// 执行期间自动阻塞当前信号 (SIGINT)// 同时阻塞 sa_mask 中指定的信号 }int main() {struct sigaction sa;sa.sa_handler = handler;sigemptyset(&sa.sa_mask);sigaddset(&sa.sa_mask, SIGQUIT); // 额外屏蔽 SIGQUITsigaction(SIGINT, &sa, NULL); }
多线程注意事项
// 错误!进程级函数影响所有线程
sigprocmask(SIG_BLOCK, &set, NULL); // 正确:线程安全版本
pthread_sigmask(SIG_BLOCK, &set, NULL);
特性 | sigprocmask | pthread_sigmask |
---|---|---|
作用范围 | 整个进程 | 单个线程 |
线程安全 | 否 | 是 |
标准 | POSIX 进程标准 | POSIX 线程标准 |
推荐场景 | 单线程程序/主线程初始化 | 多线程程序的信号控制 |
原子操作保障
危险的非原子操作:
// 第1步:解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);// 此处可能发生信号递送并丢失!// 第2步:等待信号
pause(); // 可能永远阻塞
安全的原子方案:
// 准备等待的信号集
sigset_t wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);// 原子操作:设置临时屏蔽+等待
sigsuspend(&wait_mask); // 安全等待SIGINT
完整示例:临界区保护
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void critical_section() {sigset_t new_mask, old_mask;// 步骤1:准备阻塞所有信号sigfillset(&new_mask);// 步骤2:设置屏蔽字(进入临界区)if (sigprocmask(SIG_SETMASK, &new_mask, &old_mask) == -1) {perror("sigprocmask failed");return;}/* ===== 临界区开始 ===== */printf("Entered critical section\n");// 模拟关键操作for (int i = 0; i < 3; i++) {write(STDOUT_FILENO, ".", 1);sleep(1);}write(STDOUT_FILENO, "\n", 1);/* ===== 临界区结束 ===== */// 步骤3:恢复原始屏蔽字if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {perror("sigprocmask restore failed");}
}int main() {// 设置SIGINT处理signal(SIGINT, SIG_IGN); // 简单忽略printf("Try Ctrl+C during critical section:\n");critical_section();printf("Now Ctrl+C will work:\n");pause(); // 等待信号return 0;
}
运行效果:
Try Ctrl+C during critical section:
Entered critical section
...(此时按Ctrl+C无效)
...
Now Ctrl+C will work:
^C # 程序退出
最佳实践总结
-
初始化信号集:
sigset_t set; sigemptyset(&set); // 或 sigfillset(&set)
-
错误检查:
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {perror("sigprocmask error");// 错误处理 }
-
资源清理:
sigset_t orig_mask; sigprocmask(SIG_SETMASK, &new_mask, &orig_mask); /* 操作 */ sigprocmask(SIG_SETMASK, &orig_mask, NULL); // 恢复
-
配合 sigsuspend:
sigset_t wait_mask; // 配置 wait_mask... sigsuspend(&wait_mask); // 原子等待
-
线程环境:
// 使用 pthread_sigmask pthread_sigmask(SIG_BLOCK, &set, NULL);
sigprocmask
是信号控制的核心枢纽,理解其参数机制和与其它信号函数的协作关系,是编写健壮 Linux 系统程序的关键基础。