news 2026/4/23 18:33:26

15-2.【Linux系统编程】进程信号 - 信号保存(信号处理流程的三种状态:未决、阻塞、递达,信号保存由未决表完成、sigset_t信号集类型及相关函数)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
15-2.【Linux系统编程】进程信号 - 信号保存(信号处理流程的三种状态:未决、阻塞、递达,信号保存由未决表完成、sigset_t信号集类型及相关函数)

目录

  • 3. 保存信号-内核通过 “未决信号集” 为每个进程存储已产生但未处理的信号
    • 3.1 信号处理流程中的不同状态
    • 3.2 信号在内核中的表示
    • 3.3 sigset_t信号集类型
    • 3.4 信号集操作函数
      • 3.4.1 sigprocmask读取或更改进程的信号屏蔽字
      • 3.4.2 sigpending读取当前进程的未决信号集
      • 3.4.3 综合测试用例

3. 保存信号-内核通过 “未决信号集” 为每个进程存储已产生但未处理的信号

3.1 信号处理流程中的不同状态

  • 实际执行信号的处理动作称为信号递达(Delivery)【自定义、默认、忽略】
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。【信号不是立即处理的,信号产生到处理之间的状态成为信号未决】(内核通过 “未决信号集(Pending Set)” 为每个进程存储已产生但未处理的信号
  • 进程可以选择阻塞(Block)某个信号。(即不让信号未决状态转到递达状态(保持在未决状态),即不处理信号)(阻塞也称屏蔽)
    • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而信号忽略是递达动作的一种

3.2 信号在内核中的表示

信号在内核中的表示示意图

进程与信号相关的有三张表:[Linux提供信号的操作,就是围绕这三张表展开的]

  • pending:unsigned int pending,保存收到的信号的位图,即未决表。比特位的位置:表示的是第几个信号;比特位的内容:是否收到。

  • block:unsigned int block,保存信号的阻塞状态的位图,即阻塞表。比特位的位置:表示第几个信号;比特位的内容:是否阻塞。

    • 可被递达的信号:pending & (~block)
  • handler:sighandler_t handler[31],函数指针,数组下标表示信号编号,信号重定义(为指定的信号绑定处理动作)signal()函数的本质就是修改handler表。

    • SIG_DEF:定义即为把0强转成sighandler_t 格式。功能:将信号处理恢复默认动作。

    • SIG_IGN:定义即为把1强转成sighandler_t格式。功能:忽略信号。

      // 用法:signal(信号,SIG_DEF)signal(信号,SIG_IGN)
      NAME signal-ANSI C signal handling SYNOPSIS#include<signal.h>typedefvoid(*sighandler_t)(int);sighandler_tsignal(intsignum,sighandler_t handler);

将下表横着看,每一个信号一行。

  • 每个信号都有两个标志位分别表示阻塞(block)未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

  • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理? POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的: 常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。

// 内核结构 2.6.18structtask_struct{.../* signal handlers */structsighand_struct*sighand;sigset_t blockedstructsigpendingpending;...}structsighand_struct{atomic_t count;structk_sigactionaction[_NSIG];// #define _NSIG 64spinlock_t siglock;};struct__new_sigaction{__sighandler_t sa_handler;unsignedlongsa_flags;void(*sa_restorer)(void);/* Not used by Linux/SPARC */__new_sigset_t sa_mask;};structk_sigaction{struct__new_sigactionsa;void__user*ka_restorer;};/* Type of a signal handler. */typedefvoid(*__sighandler_t)(int);structsigpending{structlist_headlist;sigset_t signal;};

3.3 sigset_t信号集类型

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

📌信号屏蔽字(Signal Mask)类似权限的umask

3.4 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态, 至于这个类型内部如何存储这些bit则依赖于系统实现, 从使用者的角度是不必关心的, 使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释, 比如用printf直接打印sigset_t变量是没有意义的。

#include<signal.h>intsigemptyset(sigset_t*set);// 初始化信号集为空集: 将参数 set 指向的 sigset_t 变量清空。intsigfillset(sigset_t*set);// 初始化信号集为全集: 将参数 set 指向的 sigset_t 变量填充为包含所有可被捕获、阻塞的信号(逻辑上包含所有有效信号)。intsigaddset(sigset_t*set,intsigno);// 向已初始化的信号集中添加指定信号:将信号编号 signo 对应的信号,加入到 set 指向的信号集中。intsigdelset(sigset_t*set,intsigno);// 从已初始化的信号集中删除指定信号:将信号编号 signo 对应的信号,从 set 指向的信号集中移除。intsigismember(constsigset_t*set,intsigno);//判断指定信号是否为信号集的成员:检查信号编号 signo 对应的信号,是否存在于 set 指向的信号集中。
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。

  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

3.4.1 sigprocmask读取或更改进程的信号屏蔽字

调用函数sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。

代码块#include<signal.h>intsigprocmask(inthow,constsigset_t*set,sigset_t*oset);返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信 号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

3.4.2 sigpending读取当前进程的未决信号集

#include<signal.h>intsigpending(sigset_t*set);读取当前进程的未决信号集,通过set参数传出。 调用成功则返回0,出错则返回-1

3.4.3 综合测试用例

综合测试用例1:屏蔽信号,再发送信号,观察未决表pending

#include<iostream>#include<cstdio>#include<vector>#include<functional>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidPrintPending(sigset_t&pending){printf("我是一个进程(%d),pending: ",getpid());for(intsigno=31;signo>=1;signo--){if(sigismember(&pending,signo))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;}intmain(){// 1.屏蔽2号信号sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block,SIGINT);// for(int i = 1; i < 31; i++) // 5.将所有信号屏蔽,并用"kill 信号 pid"测试// sigaddset(&block, i);intn=sigprocmask(SIG_SETMASK,&block,&oblock);(void)n;// 4.重复获取打印过程while(true){// 2.获取pending信号集合sigset_t pending;intm=sigpending(&pending);// 3.打印PrintPending(pending);sleep(1);}return0;}

📌结论:9号信号不可被捕捉,不可被阻塞

综合测试用例2:测试功能在代码结尾注释

#include<iostream>#include<cstdio>#include<vector>#include<functional>#include<unistd.h>#include<signal.h>#include<sys/types.h>voidPrintPending(sigset_t&pending){printf("我是一个进程(%d),pending: ",getpid());for(intsigno=31;signo>=1;signo--){if(sigismember(&pending,signo))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;}voidhandler(intsig){std::cout<<"######################"<<std::endl;std::cout<<"递达"<<sig<<"信号!"<<std::endl;sigset_t pending;intm=sigpending(&pending);PrintPending(pending);// 如果是 0000 0010 则结论为:处理完handler函数再将2号信号置0。// 验证结果:0000 0000 ,实际结论为:先给pending未决表的2号信号置0,再执行handler函数。std::cout<<"######################"<<std::endl;}intmain(){// 补0.对2号信号的功能重定义signal(SIGINT,handler);// 1.屏蔽2号信号sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block,SIGINT);// for(int i = 1; i < 31; i++) // 5.将所有信号屏蔽,并用"kill 信号 pid"测试// sigaddset(&block, i);intn=sigprocmask(SIG_SETMASK,&block,&oblock);(void)n;// 4.重复获取打印过程intcnt=0;while(true){// 2.获取pending信号集合sigset_t pending;intm=sigpending(&pending);// 3.打印PrintPending(pending);if(cnt==10){// 5.恢复对2号信号的block情况std::cout<<"解除对2号信号的屏蔽"<<std::endl;sigprocmask(SIG_SETMASK,&oblock,nullptr);}sleep(1);cnt++;}return0;}// 前提,执行过程中使用Print函数每秒打印pending未决表// 1.将2号信号重定义// 2.将2号信号屏蔽// 3.运行程序,给进程发送2号信号// 4.10秒之后对2号信号恢复,即接触屏蔽// 5.解除之后调用重定义后的2号信号函数// 5.1 验证先给未决表置0,还是先执行2号信号函数(handler函数中)

📌结论:在递达之前,先清空pending未决表中对应的信号位图:1 → 0

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 11:28:23

36、国际化文本处理与客户端间通信功能解析

国际化文本处理与客户端间通信功能解析 1. 国际化文本处理 在国际化文本处理方面,有几个关键的功能和概念需要了解。 1.1 输入方法相关 输入方法架构对客户端是透明的,但客户端需要遵循一些约定以确保正常工作。 客户端约定 :表现良好的客户端(或工具包)应首先查询输…

作者头像 李华
网站建设 2026/4/23 16:06:24

42、Xlib应用实用函数详解

Xlib应用实用函数详解 1. 重绑定KeySym含义 在处理键盘输入时,有时需要重绑定 KeySym 的含义,这时可以使用 XRebindKeysym 函数。 1.1 函数原型 XRebindKeysym(Display *display, KeySym keysym, KeySym list[], int mod_count, char *string, int num_bytes);1.2 参…

作者头像 李华
网站建设 2026/4/23 11:28:18

技术性突破:Noi浏览器如何用AI技术3分钟解决历史研究挑战

你是否曾经面对堆积如山的古籍文献感到无从下手&#xff1f;那些尘封的历史档案中&#xff0c;是否隐藏着你一直想要解开的秘密&#xff1f;现在&#xff0c;Noi浏览器的历史研究版将彻底改变你的历史探索方式&#xff01; 【免费下载链接】Noi 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/4/23 11:28:27

2025年中国GEO公司综合实力排名发布 五大服务商引领行业

随着生成式人工智能技术深度融入搜索领域&#xff0c;传统的搜索引擎优化&#xff08;SEO&#xff09;正全面演进为生成式引擎优化&#xff08;GEO&#xff09;&#xff0c;成为企业获取AI时代流量与增长的关键。近日&#xff0c;一份基于多维度评估的《2025年中国GEO服务商综合…

作者头像 李华
网站建设 2026/4/23 12:52:46

Cartographer PBStream终极指南:从原理到实战的完整解析

Cartographer PBStream终极指南&#xff1a;从原理到实战的完整解析 【免费下载链接】cartographer Cartographer is a system that provides real-time simultaneous localization and mapping (SLAM) in 2D and 3D across multiple platforms and sensor configurations. 项…

作者头像 李华
网站建设 2026/4/23 12:59:00

Foliate电子书阅读器终极指南:重新定义你的数字阅读体验

Foliate电子书阅读器终极指南&#xff1a;重新定义你的数字阅读体验 【免费下载链接】foliate Read e-books in style 项目地址: https://gitcode.com/gh_mirrors/fo/foliate 在信息爆炸的时代&#xff0c;如何找到一款既美观又实用的电子书阅读器&#xff1f;Foliate电…

作者头像 李华