Linux信号机制详解:阻塞信号集与未决信号集

文章目录

    • 前言
    • [1. 阻塞信号集(Signal Mask)](#1. 阻塞信号集(Signal Mask))
    • [2. 未决信号集(Pending Signal Set)](#2. 未决信号集(Pending Signal Set))
    • [3. 两者关系与信号递达流程](#3. 两者关系与信号递达流程)
    • [4. 相关系统调用简要说明](#4. 相关系统调用简要说明)
    • [5. 示例代码片段(查看 pending 与 mask)](#5. 示例代码片段(查看 pending 与 mask))
    • 6.示例代码详解
      • [6.1 print_sigset函数](#6.1 print_sigset函数)
      • [6.2 main函数](#6.2 main函数)
        • [6.2.1 信号集初始化与阻塞](#6.2.1 信号集初始化与阻塞)
        • [6.2.2 等待用户发送信号](#6.2.2 等待用户发送信号)
        • [6.2.3 查看挂起信号](#6.2.3 查看挂起信号)
        • [6.2.4 解除信号阻塞](#6.2.4 解除信号阻塞)
      • [6.3 执行流程](#6.3 执行流程)
      • [6.4 关键知识点](#6.4 关键知识点)
      • [6.5 程序运行结果分析](#6.5 程序运行结果分析)
      • [6.6 信号处理机制详解](#6.6 信号处理机制详解)

前言

在 Linux 系统中,信号(signal)是进程间通信(IPC)的一种机制,用于通知进程发生了某种事件。为了更精细地控制信号的处理行为,内核为每个进程维护了两个关键的信号集合:

阻塞信号集(Blocked Signal Set / Signal Mask)

未决信号集(Pending Signal Set)

这两个集合都以位掩码(bitmask)的形式表示,通常称为 sigset_t 类型。

1. 阻塞信号集(Signal Mask)

定义:当前被阻塞、暂不递达的信号集合。

作用:即使某个信号已经发送给进程,只要它在阻塞集中,就不会被立即处理(即不会触发信号处理函数或默认动作),而是被挂起(pending)。

修改方式:通过 sigprocmask() 系统调用修改当前进程的信号掩码。

特点:

是可编程控制的;

对实时信号(如 SIGRTMIN~SIGRTMAX)和标准信号均有效;

子进程会继承父进程的信号掩码(在 fork 时)。

2. 未决信号集(Pending Signal Set)

定义:已经发送给进程、但尚未被递达(因为被阻塞或正在处理)的信号集合。

作用:记录哪些信号"在路上",等待解除阻塞后处理。

查看方式:可通过 sigpending() 系统调用获取当前进程的 pending 信号集。

特点:

内核自动维护,用户无法直接修改;

对于标准信号(如 SIGINT、SIGTERM),多次发送只保留一个(不可排队);

对于实时信号(POSIX Realtime Signals),可以排队(多个实例可 pending)。

3. 两者关系与信号递达流程

当一个信号被发送给进程时,内核执行如下逻辑:

检查该信号是否在 阻塞信号集(mask) 中:

如果 不在 → 立即递达(调用 handler 或执行默认动作);

如果 在 → 将该信号加入 未决信号集(pending),暂不处理。

当进程后续通过 sigprocmask() 解除对该信号的阻塞 时:

内核检查 pending 集合;

若该信号处于 pending 状态,则立即递达。

注意:信号的"递达"发生在进程从内核态返回用户态时(例如系统调用返回、中断返回等),这是信号处理的"时机点"。

4. 相关系统调用简要说明

sigsuspend 常用于"等待特定信号"的场景,它能避免竞态条件(race condition):先解除阻塞再 sleep 可能错过信号,而 sigsuspend 是原子操作。

5. 示例代码片段(查看 pending 与 mask)

c 复制代码
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void print_sigset(const sigset_t *set) {
    for (int i = 1; i < NSIG; i++) {
        if (sigismember(set, i))
            printf(" %d", i);
    }
    printf("\n");
}

int main() {
    sigset_t mask, pending;

    // 阻塞 SIGINT (Ctrl+C)
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    printf("Blocked SIGINT. Now send SIGINT (e.g., press Ctrl+C).\n");
    sleep(3); // 此时 SIGINT 被阻塞,应进入 pending

    sigpending(&pending);
    printf("Pending signals:");
    print_sigset(&pending);

    printf("Unblocking SIGINT...\n");
    sigprocmask(SIG_UNBLOCK, &mask, NULL); // 此时会立即处理 pending 的 SIGINT

    return 0;
}

6.示例代码详解

运行此程序,在 sleep 期间按 Ctrl+C,会看到 SIGINT 被 pending,解除阻塞后进程退出。

这是一个用于演示Linux信号处理机制的C程序,主要功能是阻塞、查看和解除阻塞SIGINT信号(通常由Ctrl+C触发)。

c 复制代码
void print_sigset(const sigset_t *set) {
    for (int i = 1; i < NSIG; i++) {
        if (sigismember(set, i))
            printf(" %d", i);
    }
    printf("\n");
}

功能:打印信号集中的所有信号编号

参数:set - 指向信号集的指针

实现:

遍历从1到NSIG(系统支持的最大信号数)的所有信号

使用sigismember()函数检查每个信号是否在信号集中

如果存在,则打印该信号编号

6.2 main函数

6.2.1 信号集初始化与阻塞
c 复制代码
sigset_t mask, pending;

// 阻塞 SIGINT (Ctrl+C)
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK, &mask, NULL);

sigset_t: 定义信号集类型的变量

sigemptyset(): 初始化信号集为空

sigaddset(): 将SIGINT信号添加到信号集中

sigprocmask(SIG_BLOCK, &mask, NULL): 阻塞mask中包含的所有信号(这里是SIGINT)

6.2.2 等待用户发送信号
c 复制代码
printf("Blocked SIGINT. Now send SIGINT (e.g., press Ctrl+C).\n");
sleep(3); // 此时 SIGINT 被阻塞,应进入 pending

提示用户发送SIGINT信号

sleep(3): 程序休眠3秒,期间如果用户按下Ctrl+C,SIGINT信号会被阻塞并进入挂起状态

6.2.3 查看挂起信号
c 复制代码
sigpending(&pending);
printf("Pending signals:");
print_sigset(&pending);

sigpending(&pending): 获取当前挂起的所有信号,并存储到pending信号集中

调用print_sigset()打印所有挂起的信号

6.2.4 解除信号阻塞
c 复制代码
printf("Unblocking SIGINT...\n");
sigprocmask(SIG_UNBLOCK, &mask, NULL); // 此时会立即处理 pending 的 SIGINT

sigprocmask(SIG_UNBLOCK, &mask, NULL): 解除mask中包含的所有信号的阻塞

当SIGINT信号解除阻塞后,系统会立即处理之前挂起的SIGINT信号,导致程序终止

6.3 执行流程

程序开始执行,初始化信号集

阻塞SIGINT信号

提示用户发送SIGINT信号并休眠3秒

如果用户在3秒内按下Ctrl+C,SIGINT信号被阻塞并进入挂起状态

程序醒来后,检查并打印所有挂起的信号

解除SIGINT信号的阻塞,系统立即处理挂起的SIGINT信号

程序终止

6.4 关键知识点

信号集(sigset_t):用于表示一组信号的数据结构

sigprocmask():修改进程的信号屏蔽字,控制哪些信号可以递送到进程

sigpending():获取当前挂起的所有信号

SIG_BLOCK:向信号屏蔽字添加信号

SIG_UNBLOCK:从信号屏蔽字移除信号

挂起信号(pending signals):已发送但被阻塞的信号

这个程序很好地演示了Linux信号处理中的阻塞和挂起机制,帮助理解信号如何在进程中被处理。

6.5 程序运行结果分析

第一次运行

c 复制代码
xxx:~/Cprogram$ ./sigtest 
Blocked SIGINT. Now send SIGINT (e.g., press Ctrl+C). 
^CPending signals: 2 
Unblocking SIGINT... 

关键观察点:

程序启动后,成功阻塞了SIGINT信号

在3秒等待期间按下了Ctrl+C(显示为^C)

程序检测到并输出了挂起的信号:Pending signals: 2,其中2是SIGINT信号的编号

解除SIGINT阻塞后,系统立即处理了挂起的SIGINT信号,导致程序终止

第二次运行

c 复制代码
xxx:~/Cprogram$ ./sigtest 
Blocked SIGINT. Now send SIGINT (e.g., press Ctrl+C). 
Pending signals: 
Unblocking SIGINT... 

关键观察点:

程序同样成功阻塞了SIGINT信号

但这次在3秒等待期间没有按下Ctrl+C

因此程序检测到的挂起信号列表为空:Pending signals:后没有任何信号编号

解除SIGINT阻塞后,没有需要处理的挂起信号,程序正常执行完毕

6.6 信号处理机制详解

信号编号

SIGINT信号的编号是2,这是由系统定义的标准信号编号。可以通过kill -l命令查看所有信号的编号。

信号状态转换

信号发送:当按下Ctrl+C时,终端会向当前前台进程组发送SIGINT信号

信号阻塞:由于程序使用sigprocmask(SIG_BLOCK, &mask, NULL)阻塞了SIGINT信号,该信号无法立即递送到进程

信号挂起:被阻塞的信号会进入"挂起"状态,保存在进程的挂起信号集合中

检查挂起:通过sigpending()函数可以查询当前挂起的信号集合

解除阻塞:当使用sigprocmask(SIG_UNBLOCK, &mask, NULL)解除SIGINT的阻塞后,挂起的SIGINT信号会立即递送到进程

信号处理:SIGINT的默认处理动作是终止进程,所以程序会立即终止

程序行为差异的原因

两次运行的差异完全是由是否在等待期间发送SIGINT信号导致的:

第一次运行:发送了SIGINT → 信号挂起 → 解除阻塞后处理信号 → 程序终止

第二次运行:未发送SIGINT → 无挂起信号 → 解除阻塞后正常退出

代码与运行结果的对应关系

c 复制代码
// 阻塞SIGINT信号
sigprocmask(SIG_BLOCK, &mask, NULL);

printf("Blocked SIGINT. Now send SIGINT (e.g., press Ctrl+C).\n");
sleep(3); // 这里是接收Ctrl+C的窗口

// 检查挂起信号
sigpending(&pending);
printf("Pending signals:");
print_sigset(&pending); // 这里输出是否有挂起的SIGINT(2)

// 解除阻塞
sigprocmask(SIG_UNBLOCK, &mask, NULL); // 此时会立即处理挂起的SIGINT

实际应用场景

信号阻塞机制在实际编程中有很多应用,例如:

原子操作保护:在执行关键的原子操作时,暂时阻塞某些信号,避免操作被中断

信号处理时序控制:控制信号处理的时机,确保在合适的时候处理信号

信号批量处理:先累积多个相同信号,然后在合适的时候一次性处理

这个程序很好地演示了Linux信号机制的基本概念,包括信号阻塞、挂起和处理的整个流程。

相关推荐
CS创新实验室10 小时前
量子计算机的操作系统:原理、实践与经典架构之对比
架构·操作系统·量子计算
HLC++11 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy15 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕16 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫17 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生17 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1119 小时前
linux基础IO
linux·运维·服务器
BelongPanda20 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算