news 2026/1/9 13:57:13

Linux进程通信---6.1---进程信号屏蔽

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux进程通信---6.1---进程信号屏蔽

信号屏蔽(Signal Mask)

信号屏蔽是 Linux 进程主动掌控信号处理时机的核心机制,也是进程信号知识点中最易混淆、最贴近实战的部分。以下从「本质→实现→操作→规则→场景→避坑」层层拆解,覆盖所有核心细节:

信号屏蔽的本质与核心价值

1. 核心定义

信号屏蔽(也叫 “信号阻塞”)是进程通过设置「信号屏蔽字(Signal Mask)」,告诉内核:

“以下这些信号,我暂时不想处理;如果它们产生了,先帮我存在 pending 表(未决信号集)里,等我解除屏蔽后再处理。”

2. 关键误区纠正(新手必看)

错误认知正确事实
屏蔽信号 = 阻止信号产生屏蔽不阻止信号产生(内核仍会接收信号),仅阻止信号递达处理(执行默认 / 捕捉 / 忽略逻辑);信号产生后会暂存到 pending 表
屏蔽 = 忽略屏蔽是 “暂存”(解除后仍会处理),忽略(SIG_IGN)是 “直接丢弃”(信号产生后不进 pending 表)
所有信号都能屏蔽SIGKILL (9)、SIGSTOP (19) 是内核 “终极控制信号”,无法屏蔽 / 捕捉 / 忽略,保证管理员能随时控制进程

3. 核心价值

信号是异步的(进程无法预测信号何时到达),而进程执行「临界区代码」(如操作全局数据、调用非异步安全函数)时,被信号打断会导致数据错乱 / 程序崩溃。信号屏蔽的核心价值就是:

把 “随机时机到达的信号”,推迟到 “进程安全的时机” 处理,保证程序稳定性。

信号屏蔽的底层实现(PCB 中的核心结构)

信号屏蔽的实现依赖进程PCB(进程控制块)中的两个核心结构,二者通过sigset_t(信号集,64 位位图)关联:

结构名称类型核心作用与信号屏蔽的关系
信号屏蔽字(signal mask)sigset_t位图存储 “进程当前要屏蔽的信号清单”进程通过sigprocmask主动修改该结构,是 “屏蔽规则”
未决信号集(pending 表)sigset_t位图 + 实时信号队列存储 “已产生但未递达的信号”信号产生后,若在屏蔽字中,则存入该结构,是 “屏蔽的结果”

信号屏蔽的核心流程(可视化)

信号屏蔽的核心操作(函数 + 标准流程)

函数原型核心用途关键说明
int sigprocmask(
int how,
const sigset_t *set, sigset_t *oldset);
设置 / 查询进程的信号屏蔽字how=SIG_BLOCK(添加屏蔽)/SIG_UNBLOCK(解除屏蔽)/SIG_SETMASK(替换屏蔽集);oldset 保存旧屏蔽集

int sigpending(

sigset_t *set);

查询进程的未决信号集(pending 表)拿到的是 pending 表的用户态镜像,可查哪些信号已产生但未处理

int sigsuspend(

const sigset_t *mask);

原子操作:切换屏蔽集 + 暂停进程等待信号替代 pause(无竞态);收到信号后恢复原屏蔽集

所有信号屏蔽的操作都围绕sigprocmask函数展开(进程级),结合sigset_t完成 “编辑屏蔽清单→应用屏蔽规则→恢复旧规则” 的完整流程。

1. 核心函数:sigprocmask

函数原型
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数详解(重点!)
参数取值 / 说明实战场景
how

控制 “如何修改屏蔽字”,仅 3 种合法取值:

SIG_BLOCK:将set中的信号添加到当前屏蔽字(新增屏蔽,保留原有规则)②SIG_UNBLOCK:将set中的信号当前屏蔽字删除(解除屏蔽)③SIG_SETMASK:用set替换整个屏蔽字(全量覆盖,慎用)

- 临时屏蔽少数信号:用SIG_BLOCK- 解除特定信号屏蔽:用SIG_UNBLOCK- 恢复旧屏蔽字:用SIG_SETMASK
set要操作的信号集(sigset_t类型),需先通过sigemptyset/sigaddset编辑

必选,除非how

SIG_SETMASKset为 NULL(仅查询当前屏蔽字)

oldset保存修改前的旧屏蔽字(用于后续恢复),传NULL则不保存建议必传!避免修改后无法恢复原有屏蔽规则
返回值
  • 成功:返回0
  • 失败:返回-1(仅参数无效时失败,如传入非法信号编号)。

2. 信号集操作函数(编辑屏蔽清单)

sigset_t是 “信号清单容器”(64 位位图),必须通过以下函数操作(禁止手动位运算):

函数作用备注
sigemptyset(sigset_t *set)清空信号集(所有位设 0)初始化必备!未初始化的sigset_t位值随机
sigaddset(sigset_t *set, int sig)向信号集添加指定信号(对应位设 1)最常用,如添加SIGINT/SIGTERM
sigdelset(sigset_t *set, int sig)从信号集删除指定信号(对应位设 0)解除部分信号屏蔽时用
sigismember(const sigset_t *set, int sig)判断信号是否在信号集中返回 1 = 存在,0 = 不存在,-1 = 错误
sigfillset(sigset_t *set)填满信号集(所有位设 1)临时屏蔽所有可屏蔽信号时用

3. 信号屏蔽的标准操作流程(必背!)

#include <stdio.h> #include <signal.h> #include <unistd.h> // 临界区函数:模拟操作全局数据(不能被信号打断) void critical_work() { static int global_count = 0; printf("开始执行临界区代码,操作全局变量...\n"); for (int i = 0; i < 5; i++) { global_count++; sleep(1); // 模拟耗时操作 } printf("临界区代码执行完成,global_count = %d\n", global_count); } int main() { // ========== 步骤1:初始化信号集,编辑要屏蔽的信号 ========== sigset_t mask, old_mask; sigemptyset(&mask); // 初始化(必须!) sigaddset(&mask, SIGINT); // 添加要屏蔽的信号:SIGINT(Ctrl+C) sigaddset(&mask, SIGTERM); // 可选:添加更多信号 // ========== 步骤2:保存旧屏蔽字,应用新屏蔽规则 ========== // SIG_BLOCK:新增屏蔽(保留原有屏蔽字,仅添加 SIGINT/SIGTERM) int ret = sigprocmask(SIG_BLOCK, &mask, &old_mask); if (ret == -1) { perror("sigprocmask failed"); return -1; } printf("已屏蔽 SIGINT/SIGTERM,按 Ctrl+C 无反应(5秒内)\n"); // ========== 步骤3:执行临界区代码(核心保护逻辑) ========== critical_work(); // ========== 步骤4:恢复旧屏蔽字(避免永久屏蔽) ========== sigprocmask(SIG_SETMASK, &old_mask, NULL); printf("已恢复原有屏蔽规则,现在按 Ctrl+C 可终止进程\n"); sleep(3); return 0; }
运行效果
  • 执行临界区代码时,按Ctrl+C无反应(SIGINT 被屏蔽,存入 pending 表);
  • 临界区代码执行完成后,恢复屏蔽规则,pending 表中的 SIGINT 会立即处理(默认终止进程)。

信号屏蔽的关键规则(避坑核心)

1. 仅可屏蔽 “可屏蔽信号”

Linux 中仅SIGKILL(9)SIGSTOP(19)无法屏蔽 —— 即使将它们加入sigset_tsigprocmask也会直接忽略该操作。

原因:这两个信号是内核的 “终极控制手段”,保证管理员能随时终止 / 暂停任意进程(哪怕进程屏蔽了所有其他信号)。

2. 屏蔽是 “进程级 / 线程级” 行为

  • 单进程:sigprocmask作用于整个进程,所有线程共享同一套屏蔽字(实际是 “主线程的屏蔽字”);
  • 多线程:sigprocmask仅影响调用线程的屏蔽字(线程级屏蔽),若要设置进程级屏蔽,需用pthread_sigmask(POSIX 线程函数)。

3. 执行信号处理函数时,内核自动屏蔽同类型信号

进程执行某信号的自定义处理函数时,内核会临时屏蔽该类型信号(除非设置sigactionSA_NODEFER标志)。

目的:避免 “信号重入”—— 比如处理 SIGINT 时,再次收到 SIGINT 打断处理函数,导致逻辑错乱。

4. 屏蔽不改变信号的处理方式

信号屏蔽仅控制 “何时处理”,不改变 “如何处理”:

  • 若进程设置signal(SIGINT, SIG_IGN)(忽略 SIGINT),即使先屏蔽 SIGINT,解除屏蔽后 pending 表中的 SIGINT 也会被直接丢弃;
  • 若进程设置了自定义处理函数,解除屏蔽后会执行该函数。

5. pending 表中的信号仅保存 “未处理状态”

  • 非实时信号(1-31):重复产生的信号在 pending 表中仅保存 1 次(合并丢失);
  • 实时信号(34-64):重复产生的信号在 pending 表中排队保存(每个实例 + 附加数据,不丢失)。

信号屏蔽与易混概念的区分

1. 屏蔽(block)vs 忽略(SIG_IGN)

维度屏蔽(block)忽略(SIG_IGN)
核心逻辑信号暂存 pending 表,解除屏蔽后处理信号产生后直接丢弃,不进 pending 表
操作方式通过sigprocmask设置通过signal/sigaction设置
信号状态未决(pending)无状态(直接丢弃)
示例sigaddset(&mask, SIGINT); sigprocmask(SIG_BLOCK, &mask, NULL);signal(SIGINT, SIG_IGN);

2. 屏蔽(block)vs 捕捉(自定义 handler)

维度屏蔽(block)捕捉(自定义 handler)
核心逻辑控制信号处理时机定义信号的处理逻辑
操作方式通过sigprocmask设置通过signal/sigaction设置
关联关系捕捉不影响屏蔽,屏蔽仅推迟捕捉的执行解除屏蔽后,pending 表中的信号会执行捕捉函数

信号屏蔽的典型实战场景

1. 保护临界区代码(最常用)

场景:进程操作全局数据、共享内存、文件句柄等 “不能被打断” 的逻辑时,屏蔽信号避免数据错乱。

// 示例:修改全局配置时屏蔽信号 void update_global_config() { sigset_t mask, old_mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigprocmask(SIG_BLOCK, &mask, &old_mask); // 屏蔽信号 // 临界区:修改全局配置(不能被打断) global_config.flag = 1; global_config.time = time(NULL); sigprocmask(SIG_SETMASK, &old_mask, NULL); // 恢复屏蔽字 }

2. 避免非异步安全函数重入

场景:信号处理函数中只能调用「异步安全函数」(如write_exit),若进程正在执行malloc/printf(非异步安全),屏蔽信号可避免重入崩溃。

// 示例:调用 malloc 时屏蔽信号 void alloc_large_memory() { sigset_t mask, old_mask; sigfillset(&mask); // 屏蔽所有可屏蔽信号 sigprocmask(SIG_BLOCK, &mask, &old_mask); // 调用非异步安全函数:malloc char *buf = (char*)malloc(1024 * 1024); if (buf == NULL) perror("malloc failed"); sigprocmask(SIG_SETMASK, &old_mask, NULL); // 恢复屏蔽字 }

3. 批量处理信号

场景:进程需要完成批量任务(如文件写入、网络发包)后,统一处理所有待决信号,保证任务完整性。

// 示例:批量写入文件后处理信号 void write_file_batch() { sigset_t mask, old_mask; sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &old_mask); // 屏蔽所有可屏蔽信号 // 批量写入文件(不会被信号打断) for (int i = 0; i < 100; i++) { write(fd, &data[i], sizeof(data[i])); } sigprocmask(SIG_UNBLOCK, &mask, NULL); // 解除屏蔽,统一处理信号 }

4. 实现优雅退出

场景:捕获 SIGTERM 后,屏蔽该信号,先释放资源(关闭文件、断开连接),再解除屏蔽处理退出逻辑。

void sigterm_handler(int sig) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigprocmask(SIG_BLOCK, &mask, NULL); // 屏蔽 SIGTERM,避免重复触发 // 释放资源 close(fd); disconnect_network(); printf("资源已释放,准备退出...\n"); _exit(0); // 异步安全的退出函数 } int main() { signal(SIGTERM, sigterm_handler); while (1) sleep(1); return 0; }

5. 实战示例:直观看到 “信号保存” 的效果

下面的代码演示:屏蔽SIGINT后触发多次SIGINT,观察内核如何保存信号,解除屏蔽后看进程收到几次信号。

#include <stdio.h> #include <signal.h> #include <unistd.h> // SIGINT 处理函数 void sigint_handler(int sig) { printf("收到 SIGINT 信号(已处理)\n"); } int main() { sigset_t block_set, pending_set; // 1. 注册 SIGINT 处理函数(避免默认终止进程) signal(SIGINT, sigint_handler); // 2. 初始化屏蔽集,添加 SIGINT(屏蔽该信号) sigemptyset(&block_set); sigaddset(&block_set, SIGINT); sigprocmask(SIG_BLOCK, &block_set, NULL); printf("已屏蔽 SIGINT,接下来 5 秒内按多次 Ctrl+C...\n"); // 3. 等待 5 秒,期间可多次按 Ctrl+C(触发 SIGINT,内核会保存) sleep(5); // 4. 查看当前保存的未决信号 sigpending(&pending_set); if (sigismember(&pending_set, SIGINT)) { printf("检测到内核保存的未决信号:SIGINT\n"); } else { printf("无未决信号\n"); } // 5. 解除 SIGINT 屏蔽(保存的信号会立即递达处理) printf("解除 SIGINT 屏蔽,处理保存的信号...\n"); sigprocmask(SIG_UNBLOCK, &block_set, NULL); printf("程序继续运行\n"); return 0; }
运行结果分析:
  1. 运行程序后,快速按 3 次 Ctrl+C;
  2. 5 秒后,程序检测到pending中保存了SIGINT(位图位为1);
  3. 解除屏蔽后,进程仅收到 1 次SIGINT(非实时信号仅保存一次),处理函数打印一次 “收到 SIGINT 信号”;
  4. 若将示例中的SIGINT换成实时信号SIGRTMIN,并多次用sigqueue()发送,解除屏蔽后会收到所有发送的信号(逐个保存)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/6 11:49:13

web前端crud (修改,删除)

对于修改操作&#xff0c;通常会分为两步进行&#xff1a; 查询回显 保存修改 交互逻辑&#xff1a; 点击 编辑 按钮&#xff0c;根据ID进行查询&#xff0c;弹出对话框&#xff0c;完成页面回显展示。&#xff08;查询回显&#xff09; 点击 确定 按钮&#xff0c;保存修改…

作者头像 李华
网站建设 2026/1/7 13:59:29

智慧树学习助手:告别手动刷课的完整使用手册

还在为智慧树网课的手动操作烦恼吗&#xff1f;这款智慧树学习助手专为提升学习效率而生&#xff0c;通过智能自动化系统让你轻松完成网课学习。无论是大学生必修课程还是职场技能提升&#xff0c;这款工具都能帮你节省宝贵时间&#xff0c;真正实现高效学习。 【免费下载链接】…

作者头像 李华
网站建设 2026/1/8 21:44:43

音乐格式解密:从加密囚笼到自由播放的完美蜕变

还记得那种感觉吗&#xff1f;精心收藏的音乐&#xff0c;却被无形的锁链束缚在特定的格式中。当你想要在车载音响、运动手表或任何非官方设备上播放时&#xff0c;只能面对"格式不支持"的冰冷提示。今天&#xff0c;让我们一起开启这场音乐解放之旅。 【免费下载链接…

作者头像 李华
网站建设 2026/1/2 4:05:17

NCM格式解密工具:3步解锁网易云音乐加密文件

NCM格式解密工具&#xff1a;3步解锁网易云音乐加密文件 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的歌曲无法在其他播放器播放而烦恼吗&#xff1f;NCM加密格式限制了音乐的自由使用&#xff0c;这款简单…

作者头像 李华
网站建设 2026/1/8 0:40:02

CosyVoice3适合哪些人群使用?内容创作者开发者必看指南

CosyVoice3适合哪些人群使用&#xff1f;内容创作者开发者必看指南 在短视频、播客、在线教育等内容形态爆发的今天&#xff0c;高质量语音生成已不再是配音演员的专属。越来越多的内容生产者面临这样的挑战&#xff1a;如何低成本、高效率地为作品配上自然、富有表现力的声音&…

作者头像 李华
网站建设 2026/1/2 4:03:31

DownKyi:B站视频离线收藏完整指南

还在为B站精彩视频无法保存而烦恼吗&#xff1f;DownKyi作为专业的B站视频获取工具&#xff0c;完美解决了视频离线收藏的技术难题。这款开源软件支持从基础480P到极致8K的全方位画质&#xff0c;让视频保存变得前所未有的简单高效&#xff01;&#x1f389; 【免费下载链接】d…

作者头像 李华