文章目录
-
- 前言
- [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触发)。
6.1 print_sigset函数
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信号机制的基本概念,包括信号阻塞、挂起和处理的整个流程。