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信号机制的基本概念,包括信号阻塞、挂起和处理的整个流程。

相关推荐
shandianchengzi2 小时前
【记录】Tailscale|部署 Tailscale 到 linux 主机或 Docker 上
linux·运维·docker·tailscale
John Song3 小时前
Linux机器怎么查看进程内存占用情况
linux·运维·chrome
sichuanwuyi3 小时前
Wydevops工具的价值分析
linux·微服务·架构·kubernetes·jenkins
持戒波罗蜜3 小时前
ubuntu20解决intel wifi 驱动问题
linux·驱动开发·嵌入式硬件·ubuntu
不做无法实现的梦~3 小时前
使用ros2来跑通mid360的驱动包
linux·嵌入式硬件·机器人·自动驾驶
点云SLAM4 小时前
C++内存泄漏检测之Windows 专用工具(CRT Debug、Dr.Memory)和Linux 专业工具(ASan 、heaptrack)
linux·c++·windows·asan·dr.memory·c++内存泄漏检测·c++内存管理
LuiChun4 小时前
Docker Compose 容器服务查询与文件查看操作指南(Windows Docker Desktop 版)【一】
linux·运维·windows·docker·容器
${王小剑}4 小时前
在离线ubuntu上布置深度学习环境
linux·运维·ubuntu
Java程序之猿5 小时前
Linux使用U盘安装centos及报错You might want to saue “/run/initramfs/rdsosreport.txt“ 处理
linux·运维·服务器