信号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. 必须初始化 ‌:在使用 sigaddsetsigdelset 等函数前,必须先调用 sigemptysetsigfillsetsigset_t 变量进行初始化,否则行为是未定义的。
  2. 不可阻塞的信号 ‌:SIGKILLSIGSTOP 不能被阻塞、忽略或捕获。尝试将它们加入阻塞集会被系统忽略。
  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_masksa_flags,开发者可以精确控制信号处理期间的屏蔽行为和系统调用重启逻辑,这是 signal() 无法做到的。
  4. 信息丰富 ‌:支持 SA_SIGINFO,可以获取信号产生的详细上下文
相关推荐
三品吉他手会点灯9 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
kkeeper~9 小时前
0基础C语言积跬步之动态内存管理
c语言·开发语言
艾iYYY9 小时前
string 类的模拟实现
android·服务器·c语言·c++·算法
hai31524754311 小时前
FlashAttention C语言(C++)实现(展示版)
c语言·开发语言·c++·人工智能·算法
wuminyu12 小时前
Java锁机制之Java对象重量级锁源码剖析
java·linux·c语言·jvm·c++
apocelipes13 小时前
GNU GCC 多版本函数扩展
c语言·c++·linux编程
辰痕~13 小时前
指针,结构体,动态内存分配
c语言
luj_176814 小时前
残熵算法:风险缓冲与效率优化的融合
c语言·开发语言·网络·经验分享·算法
Legendary_00814 小时前
从 DC 圆口到 USB-C PD:LED 照明设备的供电升级逻辑
c语言·开发语言
ss27317 小时前
【入门OJ题解】分苹果问题(Python/Java/C 实现)
java·c语言·python