【Linux之拿捏信号3】阻塞信号

文章目录

  • 相关概念
  • 原理
  • sigset_t信号集
  • 信号集操作函数
    • sigprocmask系统调用
    • sigpending


相关概念

  • 实际执行信号的处理动作——信号递达Delivery(例如自定义捕捉动作,core,Term终止进程的动作)。
  • 信号从产生到递达之间的状态——信号未决(Pending)。
  • 进程可以选择阻塞(Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意:阻塞信号和忽略信号是不同的,只要信号被阻塞就会不抵达,而忽略是在递达之后可选的一种处理动作。

原理

保存信号的方式

信号在内核中的表示示意图:
在这里插入图片描述

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块pcb中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但是正在被阻塞,所以暂时不能被递达。虽然它的处理动作是忽略信号,但在没有解除阻塞之前不能忽略这个信号,忽略信号是在信号递达之后才能执行的,进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号从未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?Linux是这样实现的:常规信号在递达之前产生多次,但只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

信号的3个处理动作:分别是返回错误,默认动作,以及忽略信号

在这里插入图片描述

使用方式:
在这里插入图片描述

sigset_t信号集

根据信号在内核中的表示方法,每个信号的未决标志只有一个比特位,非0即1,如果不记录该信号产生了多少次,那么阻塞标志也只有一个比特位。

因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储。在我当前的云服务中,sigset_t类型的定义如下:(不同操作系统实现sigset_t的方案可能不同)

#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;typedef __sigset_t sigset_t;

sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。

  • 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞。
  • 在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数

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

#include <signal.h>int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum);int sigismember(const sigset_t *set, int signum);  
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • sigaddset函数:在set所指向的信号集中添加某种有效信号。
  • sigdelset函数:在set所指向的信号集中删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。
  • 注意:在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

使用方式:

#include <stdio.h>
#include <signal.h>int main()
{sigset_t s; //用户空间定义的变量sigemptyset(&s);sigfillset(&s);sigaddset(&s, SIGINT);sigdelset(&s, SIGINT);sigismember(&s, SIGINT);return 0;
}

sigprocmask系统调用

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集),简单来说就是这个函数可以更改block位图。函数原型如下:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

  • 如果oset是非空指针,则读取进程当前的信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值及其含义:

选项含义
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=set
SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask或~set

代码样例:

void showBlock(sigset_t *oset)
{int signo = 1;for(;signo <= 31; signo++){//判断信号集中2号信号是否是有效信号if(sigismember(oset,signo)) cout << "1";else cout << "0";}cout << endl;
}
int main()
{//只是在用户层面上进行设置sigset_t set,oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set,2);//将2号信号设置进信号集中//谁调用sigprocmask函数,就设置谁//读取或更改信号屏蔽字(阻塞信号集)sigprocmask(SIG_SETMASK,&set,&oset);while(true){showBlock(&oset);sleep(1);}
}

我们把2号信号屏蔽了,所以现在Ctrl+c无法终止进程,即信号阻塞住了,无法递达,我们拿到的是老的信号屏蔽字。
在这里插入图片描述

void showBlock(sigset_t *oset)
{int signo = 1;for(;signo <= 31; signo++){//判断信号集中2号信号是否是有效信号if(sigismember(oset,signo)) cout << "1";else cout << "0";}cout << endl;
}
int main()
{//只是在用户层面上进行设置sigset_t set,oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set,2);//将2号信号设置进信号集中//谁调用sigprocmask函数,就设置谁//读取或更改信号屏蔽字(阻塞信号集)sigprocmask(SIG_SETMASK,&set,&oset);int cnt = 0;while(true){showBlock(&oset);sleep(1);cnt++;if(cnt == 10){//恢复2号信号,即解除对2号信号的屏蔽cout << "recover block" << endl;sigprocmask(SIG_SETMASK,&oset,&set);showBlock(&set);}}
}

在这里插入图片描述

sigpending

sigpending函数可以用于读取进程的未决信号集,该函数的函数原型如下:

int sigpending(sigset_t *set);

sigpending函数读取当前进程的未决信号集,并通过set参数传出。该函数调用成功返回0,出错返回-1。

下面我们来做一个简单的实验

  1 #include<iostream>2 #include<signal.h>3 #include<cassert>4 #include<unistd.h>5 6 using namespace std;7 8 static void PrintPending(const sigset_t pending)9 {10     cout <<"当前进程的pending位图:";11     for(int signo = 1; signo <= 31;signo++)12     {13         if(sigismember(&pending,signo)) cout <<"1";14         else cout <<"0";15     }16     cout << "\n";17 }18 int main()                                                                                                                                                                       19 {20     //1.屏蔽2号信号21     sigset_t set,oset;22     //1.1初始化set所指向的信号集23     sigemptyset(&set);24     sigemptyset(&oset);25 26     //1.2将2号信号添加到set集合中27     sigaddset(&set,2);28 29     //1.3将新的信号屏蔽字,设置到进程当中30     sigprocmask(SIG_BLOCK, &set, &oset);31 32     //2.while获取进程的pending信号集,并以01打印33     while(true)34     {35         //到这个地方为止,虽然2号信号被屏蔽了,但是还并未产生信号,所以pending位图还是全036 37         //2.1先获取pending信号集38         sigset_t pending;39         //初始化pending信号集40         sigemptyset(&pending);//不是必需的41 42         int n = sigpending(&pending);43         assert(n == 0);44         (void)n;//保证不会出现编译时的warning,需要定义并使用,不然有些编译器可能会报错45 46         //2.2打印所有的pending信号集47         PrintPending(pending);48         sleep(1);49     }50 }

在这里插入图片描述

如果我们再对2号信号解除屏蔽,可以看到2号信号的位图结构又由1变成0了,这里要注意一点,解除2号信号的屏蔽之前我们发送ctrl+c(对应2号信号),2号信号是处于未决状态的,不会终止进程,而当我们解除对2号信号的屏蔽之后,进程会立马终止,所以看不到2号信号的位图结构又由1变成0,所以我们要用signal函数对2号信号做一下自定义捕捉动作,使其位图内容的变化能够被我们观察到。

代码及运行结果如下:

  1 #include<iostream>2 #include<signal.h>3 #include<cassert>4 #include<unistd.h>5                   6 using namespace std;7                   8 static void PrintPending(const sigset_t pending)9 {                   10     cout <<"当前进程的pending位图:";11     for(int signo = 1; signo <= 31;signo++)     12     {13         if(sigismember(&pending,signo)) cout <<"1";14         else cout <<"0";                   15     }16     cout << "\n";                                  17 }                       18      19 static void handler(int signo)20 {21     cout << "对特定信号" << signo << "执行捕捉动作" << endl;                                                                                                                     22 }                             23 int main()24 {25     //1.屏蔽2号信号26     sigset_t set,oset;27     //1.1初始化set所指向的信号集28     sigemptyset(&set);29     sigemptyset(&oset);30                                 31     //1.2将2号信号添加到set集合中32     sigaddset(&set,2); 33 34     //1.3将新的信号屏蔽字,设置到进程当中35     sigprocmask(SIG_BLOCK, &set, &oset);36 37     //2.while获取进程的pending信号集,并以01打印38     //2.0 设置对2号信号的自定义捕捉     39     signal(2,handler);40     int cnt = 0;                                41     while(true)                    42     {                 43         //到这个地方为止,虽然2号信号被屏蔽了,但是还并未产生信号,所以pending位图还是全044                45         //2.1先获取pending信号集46         sigset_t pending;                                                                47         //初始化pending信号集48         sigemptyset(&pending);//不是必需的49 50         int n = sigpending(&pending);51         assert(n == 0);52         (void)n;//保证不会出现编译时的warning,需要定义并使用,不然有些编译器可能会报错53         54         //2.2打印所有的pending信号集55         PrintPending(pending);56         sleep(1);57 58         //2.3休眠一下59         sleep(1);60 61         //2.4 10s之后,解除对2号信号的屏蔽62         if(cnt++ == 10)63         {64             cout << "解除对2号信号的屏蔽" << endl;65             sigprocmask(SIG_SETMASK, &oset, nullptr);66         }67     }68 }

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/15834.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

typeScript(持续吐血版)

typeScript-02-进阶(TSVue3) 结合vue3来使用TypeScript 使用vite来创建vue3TS的项目 使用vite创建项目&#xff0c;并选择带ts的版本 npm create vitelatest my-vue-ts-app – --template vue-ts 参考链接&#xff1a;https://vuejs.org/guide/typescript/composition-api…

数据结构与算法--javascript(持续更新中...)

一. 概论 1. 数据结构 队列&#xff1a;一种遵循先进先出 (FIFO / First In First Out) 原则的一组有序的项&#xff1b;队列在尾部添加新元素&#xff0c;并从头部移除元素。最新添加的元素必须排在队列的末尾。&#xff08;例如&#xff1a;去食堂排队打饭&#xff0c;排在前…

[网络安全提高篇] 一二一.恶意软件动态分析Cape沙箱Report报告的API序列批量提取详解

终于忙完初稿,开心地写一篇博客。 “网络安全提高班”新的100篇文章即将开启,包括Web渗透、内网渗透、靶场搭建、CVE复现、攻击溯源、实战及CTF总结,它将更加聚焦,更加深入,也是作者的慢慢成长史。换专业确实挺难的,Web渗透也是块硬骨头,但我也试试,看看自己未来四年究…

超细,设计一个“完美“的测试用例,用户登录模块实例...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 好的测试用例一定…

【Windows】Redis集群部署

集群是如何进行工作的 Redis采用哈希槽来处理数据与节点之间的映射关系&#xff0c;一个集群共有16384 个哈希槽&#xff0c;每个key通过 CRC16算法计算出一个16bit的值&#xff0c;再对16384取模&#xff0c;得到对应的哈希槽&#xff0c;集群通过维护哈希槽与节点的关系来得…

vue开发:vue的插槽功能讲解

vue的插槽 举一个生活中的例子&#xff1a;比如装修房子的时候我们会在很多地方预留出一些插孔&#xff0c;可能要插电冰箱&#xff0c;插电式&#xff0c;插充电器等&#xff0c;反正就是你觉得预留在这个位置的插座一定有用&#xff0c;这个预留的插座就类似我们今天要说的插…

Meta为全天候AR眼镜设计了AI系统的八大指导方针

众所周知&#xff0c;Meta不仅局限在Quest这类VR头显上&#xff0c;同时还在打造更轻量化的AR眼镜&#xff0c;目标就是让产品更好的融入到人们的日常生活中去。除了硬件上轻量化以外&#xff0c;在功能和交互体验上也至关重要&#xff0c;例如自然交互方式&#xff0c;比如手势…

【算法】十大排序算法以及具体用例算法题

文章目录 1:冒泡排序2:选择排序3:插入排序4:希尔排序5:堆排序6:计数排序7:基数排序8:快速排序9:归并排序10:桶排序 源代码下载 1:冒泡排序 /** 冒泡排序是内部排序* 冒泡排序将会每一次都从头开始遍历* 每一次遍历都会把最大的数据放到最后一个* 因此每一次都可以少遍历一个元…

透彻理解 UART 通信的基本方法

UART是一种异步全双工串行通信协议&#xff0c;由 Tx 和 Rx 两根数据线组成&#xff0c;因为没有参考时钟信号&#xff0c;所以通信的双方必须约定串口波特率、数据位宽、奇偶校验位、停止位等配置参数&#xff0c;从而按照相同的速率进行通信。 异步通信以一个字符为传输单位…

MySQL性能瓶颈定位慢查询

目录 1 性能优化的思路2 引言3 MySQL慢查询日志3.1 慢查询参数3.2 开启慢查询日志&#xff08;临时&#xff09;3.3 开启慢查询日志&#xff08;永久&#xff09;3.4 慢查询测试 4 MySQL性能分析 EXPLAIN4.1 概述4.2 EXPLAIN字段介绍4.2.1 id字段4.2.2 select_type 与 table字段…

mac苹果电脑,怎么把mkv转换mp4格式

mac苹果电脑&#xff0c;怎么把mkv转换mp4格式&#xff1f;如果你是一名mac苹果电脑的用户&#xff0c;在电脑上下载到mkv格式的视频后会发现它使用起来非常的麻烦&#xff0c;甚至不能直接打开播放。mkv其实也是一种时间比较久远的视频文件格式&#xff0c;但是不知道是什么原…

责任链模式

责任链模式 概述优缺点应用场景Java 代码示例Spring 代码示例场景一场景二场景三 概述 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它通过将请求的发送者和接收者解耦&#xff0c;使多个对象都有机会处理请求。在这个…