信号sigset_t

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()(多线程)来检查或更改进程/线程的信号屏蔽字。

  • ‌典型用法 ‌:
    1. 创建一个信号集。
    2. 使用 sigemptyset 初始化。
    3. 使用 sigaddset 添加需要阻塞的信号。
    4. 调用 sigprocmask(SIG_BLOCK, &set, NULL) 阻塞这些信号。
    5. 执行临界区代码。
    6. 调用 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. 注意事项

  1. ‌必须初始化 ‌:在使用 sigaddset、sigdelset 等函数前,必须先调用 sigemptyset 或 sigfillset 对 sigset_t 变量进行初始化,否则行为是未定义的。
  2. ‌不可阻塞的信号 ‌:SIGKILL 和 SIGSTOP 不能被阻塞、忽略或捕获。尝试将它们加入阻塞集会被系统忽略。
  3. ‌多线程环境 ‌:在多线程程序中,每个线程有独立的信号掩码。应使用 pthread_sigmask() 而不是 sigprocmask() 来修改线程特定的信号掩码。sigprocmask() 在多线程中的行为是未定义的(尽管在某些实现中等效于修改调用线程的掩码)。
  4. ‌返回值检查‌:在生产代码中,务必检查所有信号集操作函数的返回值,以处理潜在的错误。

总结

sigset_t 是 C 语言系统编程中管理信号的基础工具。它通过一组标准化的 API (sigemptyset, sigaddset, sigdelset, sigismember, sigfillset) 提供安全、可移植的信号集合操作方式,广泛应用于信号阻塞、临界区保护以及异步信号的同步化处理中。

5. 为什么推荐 sigaction 而非 signal?

  1. ‌可移植性 ‌:signal() 的行为在不同 Unix 版本间存在差异(例如处理完后是否重置为默认行为),而 sigaction() 遵循 POSIX 标准,行为一致。
  2. ‌原子性 ‌:sigaction() 允许在注册新处理函数的同时获取旧的处理函数,避免了检查与设置之间的竞态条件。
  3. ‌精细控制 ‌:通过 sa_mask 和 sa_flags,开发者可以精确控制信号处理期间的屏蔽行为和系统调用重启逻辑,这是 signal() 无法做到的。
  4. ‌信息丰富 ‌:支持 SA_SIGINFO,可以获取信号产生的详细上下文
相关推荐
知识分享小能手1 小时前
C学习教程,从入门到精通,C语言输入和输出(4)
c语言·开发语言·学习
沐晓时光12 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
SuperByteMaster13 小时前
wfi进入之前没有清除basepri, 导致中断优先级不够,wfi 退不出来
c语言
Navigator_Z14 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
奋斗的阿狸_198616 小时前
ESP32-S3 + ES7210 四通道麦克风录音上传方案
c语言·开发语言·fpga开发
2401_8628808217 小时前
iMX6ULL 驱动工程搭建 + LED 灯驱动
c语言·驱动开发·嵌入式硬件
ACP广源盛1392462567317 小时前
Type‑C 扩展坞方案选型笔记|国产 DP1.4 转 HDMI2.0 桥接芯片 GSV2201S @ACP评估
c语言·开发语言·笔记·硬件架构·硬件工程·国产芯片
沐欣工作室_lvyiyi17 小时前
超市智能货架系统设计与实现(论文+源码)
c语言·单片机·课程设计·电子信息工程毕业设计·黑龙江工商学院·广州工商学院·成都工业学院
C++ 老炮儿的技术栈17 小时前
static的作用
c语言·c++·人工智能·单片机·c
小小龙学IT19 小时前
Go 语言 cgo 与 C/C++ 互操作深度解析:从 fwlib 直连到工业数采网关实战系列
c语言·c++·golang