文章目录
- 前言
- 1. 阻塞信号集(Signal Mask)
- 2. 未决信号集(Pending Signal Set)
- 3. 两者关系与信号递达流程
- 4. 相关系统调用简要说明
- 5. 示例代码片段(查看 pending 与 mask)
- 6.示例代码详解
- 6.1 print_sigset函数
- 6.2 main函数
- 6.2.1 信号集初始化与阻塞
- 6.2.2 等待用户发送信号
- 6.2.3 查看挂起信号
- 6.2.4 解除信号阻塞
- 6.3 执行流程
- 6.4 关键知识点
- 6.5 程序运行结果分析
- 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)
#include<signal.h>#include<stdio.h>#include<unistd.h>voidprint_sigset(constsigset_t*set){for(inti=1;i<NSIG;i++){if(sigismember(set,i))printf(" %d",i);}printf("\n");}intmain(){sigset_tmask,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 被阻塞,应进入 pendingsigpending(&pending);printf("Pending signals:");print_sigset(&pending);printf("Unblocking SIGINT...\n");sigprocmask(SIG_UNBLOCK,&mask,NULL);// 此时会立即处理 pending 的 SIGINTreturn0;}6.示例代码详解
运行此程序,在 sleep 期间按 Ctrl+C,会看到 SIGINT 被 pending,解除阻塞后进程退出。
这是一个用于演示Linux信号处理机制的C程序,主要功能是阻塞、查看和解除阻塞SIGINT信号(通常由Ctrl+C触发)。
6.1 print_sigset函数
voidprint_sigset(constsigset_t*set){for(inti=1;i<NSIG;i++){if(sigismember(set,i))printf(" %d",i);}printf("\n");}功能:打印信号集中的所有信号编号
参数:set - 指向信号集的指针
实现:
遍历从1到NSIG(系统支持的最大信号数)的所有信号
使用sigismember()函数检查每个信号是否在信号集中
如果存在,则打印该信号编号
6.2 main函数
6.2.1 信号集初始化与阻塞
sigset_tmask,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 等待用户发送信号
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 查看挂起信号
sigpending(&pending);printf("Pending signals:");print_sigset(&pending);sigpending(&pending): 获取当前挂起的所有信号,并存储到pending信号集中
调用print_sigset()打印所有挂起的信号
6.2.4 解除信号阻塞
printf("Unblocking SIGINT...\n");sigprocmask(SIG_UNBLOCK,&mask,NULL);// 此时会立即处理 pending 的 SIGINTsigprocmask(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 程序运行结果分析
第一次运行
xxx:~/Cprogram$./sigtest Blocked SIGINT.Now sendSIGINT(e.g.,press Ctrl+C).^CPending signals:2Unblocking SIGINT...关键观察点:
程序启动后,成功阻塞了SIGINT信号
在3秒等待期间按下了Ctrl+C(显示为^C)
程序检测到并输出了挂起的信号:Pending signals: 2,其中2是SIGINT信号的编号
解除SIGINT阻塞后,系统立即处理了挂起的SIGINT信号,导致程序终止
第二次运行
xxx:~/Cprogram$./sigtest Blocked SIGINT.Now sendSIGINT(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 → 无挂起信号 → 解除阻塞后正常退出
代码与运行结果的对应关系
// 阻塞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信号机制的基本概念,包括信号阻塞、挂起和处理的整个流程。