Linux C信号集(Signal Set)

在 Linux C 编程中,信号集(Signal Set) 是用于批量管理信号的数据结构,核心作用是 “打包” 多个信号(如标记信号是否被阻塞、是否处于待处理状态),配合信号相关系统调用(如 sigprocmasksigpending)实现对信号的精细化控制。

一、信号集的核心数据结构

Linux 中通过标准库定义的 sigset_t 类型表示信号集,其本质是一个 “位掩码”:每个信号对应 sigset_t 中的一个比特位,比特位为 1 表示信号在集合中,为 0 表示不在集合中

  • 定义位置:<signal.h> 头文件中(具体实现依赖系统,无需用户手动定义)。
  • 用途限制:sigset_t 是 “opaque 类型”(抽象类型),禁止直接通过位运算操作,必须使用标准库提供的 “信号集操作函数” 修改和查询。

二、信号集的核心操作函数

所有信号集的操作都通过 <signal.h> 提供的标准函数实现,按功能可分为 “初始化 / 清空”“添加 / 删除信号”“查询信号” 三类,下表整理了常用函数:

函数原型

功能描述

返回值

int sigemptyset(sigset_t *set)

初始化信号集 set,清空所有信号(所有比特位设为 0)

成功返回 0,失败返回 -1(设置 errno

int sigfillset(sigset_t *set)

初始化信号集 set,包含所有已定义的信号(所有比特位设为 1)

成功返回 0,失败返回 -1(设置 errno

int sigaddset(sigset_t *set, int signum)

将信号 signum添加到信号集 set中(对应比特位设为 1)

成功返回 0,失败返回 -1(signum 无效时出错)

int sigdelset(sigset_t *set, int signum)

将信号 signum 从信号集 set中删除(对应比特位设为 0)

成功返回 0,失败返回 -1(signum无效时出错)

int sigismember(const sigset_t *set, int signum)

判断信号 signum是否在信号集 set

在集合中返回 1,不在返回 0,失败返回 -1

注意事项:
  1. 初始化必须优先:使用 sigaddset/sigdelset 前,必须先调用 sigemptysetsigfillset 初始化信号集,否则 sigset_t 的初始值不确定,操作会出错。
  2. 信号编号有效性signum 必须是有效的信号编号(如 SIGINTSIGQUIT,范围 1 ~ NSIG-1),不能是 0 或大于 NSIG 的值(NSIG 是系统定义的最大信号编号)。

三、信号集的核心应用场景

信号集本身不直接处理信号,而是作为 “参数” 传递给系统调用,实现对信号的阻塞控制待处理信号查询,这是信号集最核心的两个用途。

场景 1:信号阻塞(Signal Blocking)

Linux 内核为每个进程维护一个 “信号阻塞集(Signal Mask)”,若某个信号在阻塞集中,内核会暂时 “屏蔽” 该信号:信号触发后不会立即执行处理函数,而是标记为 “待处理(Pending)”,直到信号被从阻塞集中移除。

通过 sigprocmask 系统调用修改进程的信号阻塞集,语法如下:

#include <signal.h>

// how:控制阻塞集的修改方式
// set:新的信号集(若非NULL,按how修改阻塞集)
// oldset:保存原阻塞集(若非NULL,将修改前的阻塞集存入)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
关键参数 how 的取值:

how

含义

SIG_BLOCK

set

中的信号 “添加” 到阻塞集(阻塞新信号)

SIG_UNBLOCK

set

中的信号从阻塞集 “移除”(解除阻塞)

SIG_SETMASK

将阻塞集 “替换” 为 set

(完全覆盖原阻塞集)

场景 2:查询待处理信号(Pending Signals)

进程中被阻塞的信号触发后,会进入 “待处理状态”。通过 sigpending 系统调用可以查询当前进程的 “待处理信号集”,语法如下:

#include <signal.h>

// 将当前进程的待处理信号集存入 `set`
int sigpending(sigset_t *set);

返回值:成功返回 0,失败返回 -1(设置 errno)。

四、完整示例:信号集的实际使用

以下代码主要演示了信号(signal)的阻塞(block)与解除阻塞(unblock)机制,通过SIGINT信号(用户按下Ctrl+C触发)的处理,展示了如何控制信号在特定时间段内不被响应,以及何时允许响应。

#include <signal.h>   // 信号处理相关函数(如signal、sigprocmask等)
#include <stdio.h>    // 标准输入输出(但代码中未直接使用,主要用的是unistd.h的write)
#include <unistd.h>   // 系统调用(如write、sleep)
#include <stdlib.h>   // 标准库函数(如exit)

static void int_handle()
{
    write(1, "123", 3);  // 向标准输出(stdout)写入字符串"123"(长度3)
}

int main()
{
    int i,j;                  // 循环控制变量
    sigset_t set;             // 信号集(用于存放一组信号,类似“信号容器”)

    signal(SIGINT, int_handle); // 注册SIGINT信号的处理函数:按Ctrl+C时执行int_handle
    /*
    信号集操作函数
    sigemptyset(&set); // 清空信号集
    sigaddset(&set, SIGINT); // 将SIGINT信号加入信号集
    sigdelset(&set, SIGINT); // 将SIGINT信号从信号集中删除
    */
    sigemptyset(&set);        // 清空信号集set(初始化为空,不包含任何信号)
    sigaddset(&set, SIGINT);  // 将SIGINT信号添加到信号集set中(现在set里只有SIGINT)

    for (i = 0; i < 5; i++) // 外层循环5次,每次循环演示“阻塞信号→执行任务→解除阻塞”
    {
        /*
        信号屏蔽函数
        sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞SIGINT信号
        sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞SIGINT信号
        */
        sigprocmask(SIG_BLOCK, &set, NULL); // ① 阻塞SIGINT信号(不让SIGINT进来)
        for (j = 0; j < 3; j++)            // ② 执行“受保护任务”:打印3个*(耗时3秒)
        {
            write(1, "*", 1);              // 打印1个星号*
            sleep(1);                      // 休眠1秒(模拟任务执行时间)
        }
        write(1, "\n", 1);                 // 打印换行(3个*后换行)
        sigprocmask(SIG_UNBLOCK, &set, NULL); // ③ 解除SIGINT阻塞(允许SIGINT进来)
    }
    exit(0);
}
/*
信号阻塞:通过sigprocmask(SIG_BLOCK, ...)可以“保护”一段代码不被特定信号打断,直到调用SIG_UNBLOCK解除阻塞后才处理信号。
• 不可靠信号特性:SIGINT是不可靠信号(不会排队),如果阻塞期间多次按Ctrl+C,解除阻塞后只会处理1次(打印1次"123")。
• 信号集作用:sigset_t、sigemptyset、sigaddset用于定义“需要阻塞的信号列表”,方便批量控制多个信号的阻塞状态。
*/

以下示例演示:使用信号集阻塞 SIGINT(Ctrl+C)信号,触发信号后查询待处理状态,最后解除阻塞并处理信号。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// 信号处理函数(仅打印提示)
void sigint_handler(int signum) {
    printf("\n收到 SIGINT 信号(编号:%d),已处理!\n", signum);
}

int main() {
    sigset_t block_set, pending_set, old_set;
    struct sigaction act;

    // 1. 设置 SIGINT 的处理函数
    act.sa_handler = sigint_handler;
    sigemptyset(&act.sa_mask);  // 处理信号时不阻塞其他信号
    act.sa_flags = 0;
    if (sigaction(SIGINT, &act, NULL) == -1) {
        perror("sigaction 失败");
        exit(1);
    }

    // 2. 初始化信号集:阻塞集中仅包含 SIGINT
    sigemptyset(&block_set);    // 清空信号集
    sigaddset(&block_set, SIGINT);  // 添加 SIGINT 到阻塞集

    // 3. 修改进程阻塞集:阻塞 SIGINT,保存原阻塞集到 old_set
    printf("开始阻塞 SIGINT(Ctrl+C),持续 5 秒...\n");
    if (sigprocmask(SIG_BLOCK, &block_set, &old_set) == -1) {
        perror("sigprocmask 失败");
        exit(1);
    }

    // 4. 等待 5 秒:此期间按 Ctrl+C 会触发 SIGINT,但被阻塞(待处理)
    sleep(5);

    // 5. 查询待处理信号集
    if (sigpending(&pending_set) == -1) {
        perror("sigpending 失败");
        exit(1);
    }
    // 判断 SIGINT 是否在待处理集中
    if (sigismember(&pending_set, SIGINT)) {
        printf("查询到:SIGINT 信号处于待处理状态\n");
    } else {
        printf("查询到:无待处理的 SIGINT 信号\n");
    }

    // 6. 解除阻塞:恢复原阻塞集(移除 SIGINT 的阻塞)
    printf("开始解除 SIGINT 阻塞...\n");
    if (sigprocmask(SIG_SETMASK, &old_set, NULL) == -1) {
        perror("sigprocmask 恢复失败");
        exit(1);
    }

    // 7. 解除阻塞后,待处理的 SIGINT 会立即被处理
    printf("阻塞已解除,等待信号处理(按任意键退出)...\n");
    getchar();
    return 0;
}
示例运行流程:
  1. 程序启动后,先阻塞 SIGINT,并提示 “持续阻塞 5 秒”。
  2. 在 5 秒内按 Ctrl+CSIGINT 被阻塞,不会立即执行处理函数。
  3. 5 秒后查询待处理信号:会显示 “SIGINT 处于待处理状态”。
  4. 解除阻塞后:待处理的 SIGINT 会立即触发处理函数,打印 “收到信号”。

五、常见注意事项

  1. 不可阻塞的信号SIGKILL(9)和 SIGSTOP(19)是内核强制信号,无法被阻塞、忽略或捕获,任何修改这两个信号的操作都会失败。
  2. 信号集的初始化:忘记调用 sigemptyset/sigfillset 直接操作 sigset_t,会导致未定义行为(如信号集初始值乱码)。
  3. 线程安全sigprocmask 是 “进程级” 调用,在多线程程序中需使用 pthread_sigmask(线程级信号阻塞集),避免影响其他线程。
  4. 待处理信号的生命周期:信号解除阻塞后,若存在待处理的该信号,内核会立即处理一次(即使该信号被多次触发,待处理状态也只会记录一次,即 “信号丢失”)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值