信号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,可以获取信号产生的详细上下文
相关推荐
念恒123061 小时前
进程控制---进程等待
linux·c语言
cen__y2 小时前
Linux05(管道)
linux·运维·服务器·c语言·开发语言·文件流
sghuter2 小时前
数字资源分发的技术架构与未来趋势
c语言·开发语言·后端·青少年编程
Sakuyu434682 小时前
C语言基础(三)
c语言·开发语言
渡己之道3 小时前
笔记-lvgl移植到stm32f407
c语言·笔记·stm32
Legendary_0084 小时前
Type-C 赋能传统显示器:一线通联,LDR6020 重构显示互联体验
c语言·计算机外设·pd芯片
Rabitebla4 小时前
【数据结构】消失的数字+ 轮转数组:踩坑详解
c语言·数据结构·c++·算法·leetcode
Navigator_Z4 小时前
LeetCode //C - 1025. Divisor Game
c语言·算法·leetcode
爱编码的小八嘎5 小时前
C语言完美演绎8-19
c语言