【Linux】信号屏蔽与信号捕捉的原理与实现(附图解与代码)

这一篇的篇幅可能有点长,如果已经了解了以下两个知识点的同学可以自行跳到第三部分——信号屏蔽的实现。

不太了解的同学希望你们能够静下心来看完,相信一定会有不小的收获。那么话不多说,我们这就开始啦!!!

想要明白如何实现信号屏蔽和信号捕捉,我们就要先了解两个知识点

  1.  信号如何在进程中传递
  2. 信号的三大行为和五种动作

而想要了解这两个知识点,我们又得先来了解一些基础概念

  1. PCB中有两个信号集,分别是未决信号集和屏蔽字
  2. 这两个信号集都是由若干个位构成的,系统支持多少个信号,信号集就有多少个信号位,每个信号都走自己对应的那个位置
  3. 信号位的位码分别是0或1,每个信号位就好比一扇门,1表示禁止通过,0表示允许通过,默认情况下每个信号位的位码均为0,也就是开门状态
  4. 未决信号集的信号位位码由系统设置,屏蔽字的信号位位码可由用户自己设置,以屏蔽某些特定信号

在了解了这些基础概念之后,我们就可以来了解信号在进程中的传递过程了,以2号信号—SIGINT举例,具体情况如下图所示

信号在进程中的传递过程

从以上图中,我们可以得出一些有趣的信息,那就是Unix经典信号(1-31号信号)是不支持排队的,同一时间最多处理两个信号,其余的信号都会被直接丢弃掉

但要注意的是,自定义信号(34-64号信号)是支持排队的,采用自定义信号队列来帮助信号进行排队。为什么这两大类信号会有这样的差别呢?这就要涉及到这两大类信号的功能了。

我们知道,1-31号信号大多是用来杀死或挂起进程的,而进程你杀一次或者挂起一次就行了,你要是支持排队的话,那就会变成来一个信号杀一次,来一个信号又杀一次,杀完之后继续杀,咋的,杀一次不过瘾,还要鞭尸啊?这也就是Unix经典信号不支持排队的原因。

而34-64号信号为什么排队呢?举个例子,相信大家都用遥控器换过台,遥控器也是通过信号来完成任务,这里的信号涉及到的就是自定义信号,你连按十次换台键,难道电视只给你换一个台吗?这也就是自定义信号支持排队的原因,它们的工作主要是绑定任务或事件,所以需要支持排队。

信号的三大行为和五种动作

三大行为与五种动作其实就是我们讲的handler里面的详细内容,具体如下图所示

所谓的三大行为就是:SIG_DFL—默认行为、SIG_IGN—忽略行为、SIG_ACTION—捕捉行为

默认情况下,如果我们不做特殊处理的话,信号都会执行默认行为,五种动作也就是默认行为中的五种默认处理动作

这些相信大家都能从上面的图中看出来,这里我们讲一讲捕捉行为

其实也很简单,捕捉函数是由用户实现的,我们可以在这个函数中实现我们想要完成的功能。

当进程的PCB接收到对应信号时,就会触发对应的捕捉函数,在让信号失效的同时也会完成我们所要求的功能

由此,也就引出了本篇博客的正题,让信号失效的三种方式

  1. 信号忽略,将信号挡在未决信号集外或让信号进入忽略行为
  2. 信号屏蔽,将信号延迟递达,阻塞在未决信号集和屏蔽字之间
  3. 信号捕捉,让信号进入捕捉行为并使其执行用户自定义的捕捉函数

但要注意的是,不是所有的信号都能够被失效,因为一旦出现进程出现错误,或者是出现病毒,很可能系统发现了问题,但由于信号都被屏蔽,无法杀死对应进程或病毒

所以操作系统保留了两个高级操作信号,他们不按照上述流程在进程中传递且无法被失效,这两个信号就是9号信号—SIGKILL与19号信号—SIGSTOP,一个用来杀死进程,一个用来挂起进程

信号屏蔽的实现

先和大家说一句,下面所需要的特殊数据类型和相关函数的头文件都是 #include<signal.h> 

#include<signal.h>:信号头文件,定义信号符号常量,信号结构以及信号操作函数原型。

接下来我们来了解一下实现信号屏蔽需要用到的特殊数据类型与相关函数

信号屏蔽相关的特殊数据类型与函数

实现信号屏蔽需要用到一个特殊的数据类型,叫做信号集类型,也就是sigset_t,这里我定义一个信号集类型的变量set,也就是sigset_t set;

再定义一个变量int signo;  signo——信号序号

以下是实现信号屏蔽常用的相关函数

函数功能返回值
int sigemptyset(&set);初始化信号集,将所有信号位的位码置为0,也就是允许通过状态

成功返回0,失败返回-1,并设置errno以返回失败原因

int sigfillset(&set);初始化信号集,将所有信号位的位码置为1,也就是禁止通过状态成功返回0,失败返回-1,并设置errno以返回失败原因
int sigdelset(&set , int signo);将信号集中序号为signo的信号位的位码置为0,也就是允许通过状态(请看下方的注释①)成功返回0,失败返回-1
int sigaddset(&set , int signo);将信号集中序号为signo的信号位的位码置为1,也就是禁止通过状态成功返回0,失败返回-1
int sigismember(&set , int signo);获取信号集中序号为signo的信号位的位码返回对应信号位的位码,即0或1
int sigprocmask(替换方式 , 新信号集 , 是否传出原有屏蔽字)(参数介绍请看注释②)用新的信号集替换旧的屏蔽字,得到新的屏蔽字替换成功返回0,失败返回-1

注释①:注意这里的signo要传入的是信号名而不是信号编号,就比如我们想要屏蔽SIGINT信号,但是不同系统下同样信号的信号编号可能不一样,可能系统1中SIGINT的信号编号是2,系统2中SIGINT的信号编号是3,所以我们在传入时都是直接传入信号名而不是信号编号

注释②:sigprocmask函数的参数介绍

参数变量类型解释
替换方式------

替换方式有以下三种:

SIG_SETMASK (把新信号集直接覆盖到原有屏蔽字上)、

SIG_BLOCK (将新信号集和原有屏蔽字的对应信号位的位码进行位或运算)

SIG_UNBLOCK (将新信号集中每个信号位的位码取反后,和原有屏蔽字进行位与运算),这三种替换方式中,最常用的就是SIG_SETMASK

新信号集sigset_t*用户设置的新信号集,用于替换旧的屏蔽字,以屏蔽某些特定信号
是否传出原有屏蔽字sigset_t*传入NULL表示不传出原有屏蔽字,传入sigset_t* 类型的变量表示传出原有屏蔽字

了解完这些之后,我们就可以来实现信号屏蔽了

实现信号屏蔽的具体步骤

我们来完成一个功能——让进程屏蔽SIGINT信号,具体如下所示

代码实现

//sig_shield.c#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>int main(void)
{sigset_t newset;//初始化一个信号集sigemptyset(&newset);//将所有信号位位码全部置为0sigaddset(&newset , SIGINT);//屏蔽SIGINT信号,将其对应信号位的位码置为1int bitcode = sigismember(&newset , SIGINT);//利用sigismember函数检验位码是否被置为1if(bitcode == 1){printf("新信号集设置成功!\n");}else{printf("新信号集设置失败!\n");exit(1);}sigprocmask(SIG_SETMASK , &newset , NULL);//用新信号集替换屏蔽字以得到新的屏蔽字,不传出旧的屏蔽字//写一个死循环,让进程一次睡一秒,同时测试终端界面下CTRL+C能否杀死该进程int count = 0;while(1){sleep(1);count++;printf("        count = %d\n" , count);}exit(0);
}

图解

我们可以很清楚地发现,CTRL+C所发出的SIGINT信号被屏蔽掉了,CTRL+\发出的SIGQUIT信号成功过杀死了进程,以上,简单的信号屏蔽就实现了,是不是很轻松呢?

接下来,我们来了解一下信号捕捉的相关知识和具体实现吧

信号捕捉的实现

在之前的讲解中,我们提到过,信号一共有三种行为,分别是:SIG_DFL(默认行为)、SIG_IGN(忽略行为)、SIG_ACTION(捕捉行为)

正常情况下,信号是走默认行为的,想要实现信号捕捉,也就意味着我们要对进行信号行为选择,让其走入信号行为,而要进入捕捉行为,代表信号要进入handler,这也就意味着信号必须要先通过未决信号集和屏蔽字,变成递达态,才能够进入处理流程

信号捕捉相关的特殊数据类型与函数

这里就涉及到了一个结构体:叫做struct sigaction ,  我们来介绍一下这个结构体里的成员

成员变量类型功能解释
sa_handler

函数指针类型,参数类型为int

void (*sa_handler)(int)

行为选择可传入三种参数——分别是SIG_DFL(默认行为)、SIG_IGN(忽略行为)、捕捉函数的地址。需要注意的是,捕捉函数的写法必须是【void 函数名(int)】,来匹配sa_handler的变量类型
sa_flagsint指定对信号进行什么特殊处理一般情况下传0,表示默认选项,更多的参数大家可以去自己搜索一下,这里就不做过多介绍了
sa_masksigset_t作为临时屏蔽字,阻挡多个同类信号同时进入handler。使用前需要初始化sa_mask是一个信号集,当某个信号通过屏蔽字进入sa_handler函数之前,该信号集里面的对应信号被加入到进程的信号掩码当中,当sa_handler函数执行完之后,屏蔽字复位为原先值。这样就保证了某个信号正在被处理时,如果其他同类信号再次传入就会被阻塞

这里我们定义两个变量,分别是struct sigaction act;和 struct sigaction old_act;

然后我们就要了解一个函数,用于改变信号的行为,这个函数和上面的结构体同名,叫做sigaction函数

头文件#include<signal.h>
功能将目标信号的行为替换为用户要求的行为,可传出原有行为
语法int sigaction(int signo , struct sigaction *act , struct sigaction *old_act);
返回值成功返回0,失败返回-1

了解完这些之后,我们就可以实现信号捕捉了

实现信号捕捉的具体步骤

代码实现

//Sig_Catch.c#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>//这个n不是我们填入的,是系统填入的信号序号
void sig_do(int n){printf("成功捕捉%d信号\n",n);
}int main()
{struct sigaction act , old_act;//创建两个sigaction类型的结构体act.sa_handler = sig_do;//令信号从默认行为变成捕捉行为act.sa_flags = 0;//默认选项,不做特殊处理sigemptyset(&act.sa_mask);//初始化结构体中的sa_masksigaction(SIGINT , &act , &old_act);//将信号SIGINT的行为替换为捕捉行为,并传出原有行为//写一个死循环,每次睡一秒,看CTRL+C能否杀死该进程while(1){sleep(1);}return 0;
}

图解

以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答

今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!

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

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

相关文章

代码随想录算法训练营第四十六天 | 518. 零钱兑换 II、377. 组合总和 Ⅳ

518. 零钱兑换 II 视频讲解&#xff1a;动态规划之完全背包&#xff0c;装满背包有多少种方法&#xff1f;组合与排列有讲究&#xff01;| LeetCode&#xff1a;518.零钱兑换II_哔哩哔哩_bilibili 代码随想录 &#xff08;1&#xff09;代码 377. 组合总和 Ⅳ 视频讲解&…

JOSEF约瑟 闭锁继电器 LB-7 YDB-100 100V 50HZ 控制断路器的合闸或跳闸

闭锁继电器LB-7导轨安装名称:闭锁继电器型号:LB-7闭锁继电器额定电压100V功率消耗≤10VA触点容量220V1.5A40W返回系数≥0.8 LB-1A、LB-1D、DB-1、HBYB-102/D YDB-100、HLO、DB-100、LB-7型闭锁继电器 一、用途 LB-7型闭锁继电器(以下简称继电器)用于发电厂及变电所内高压母线…

Electron笔记

基础环境搭建 官网:https://www.electronjs.org/zh/ 这一套笔记根据这套视频而写的 创建项目 方式一: 官网点击GitHub往下拉找到快速入门就能看到下面这几个命令了 git clone https://github.com/electron/electron-quick-start //克隆项目 cd electron-quick-start //…

提取歌曲伴奏?用对软件一键帮你搞定~

相信大家经常想获取某首歌曲的伴奏&#xff0c;但是不知从何下手&#xff0c;今天这篇教程给大家分享一个超神奇软件&#xff0c;一键提取歌曲伴奏&#xff01; 第一步&#xff1a;打开【音分轨】APP&#xff0c;进入首页点击【人声分离】 第二步&#xff1a;选择导入方式&…

SpringBoot 中使用JPA

最近忙里偷闲&#xff0c;想写一点关于JPA的东西&#xff0c;另外也加深下对JPA的理解&#xff0c;才有了此篇博文。 一、JPA JPA &#xff08;Java Persistence API&#xff09;Java持久化API&#xff0c;是一套Sun公司Java官方制定的ORM 规范&#xff08;sun公司并没有实现…

为什么mac上有的软件删除不掉?

对于Mac用户来说&#xff0c;软件卸载通常是一个相对简单的过程。然而&#xff0c;有时你可能会发现某些软件似乎“顽固不化”&#xff0c;即使按照常规方式尝试卸载&#xff0c;也依然存在于你的电脑上。这到底是为什么呢&#xff1f;本文将探讨这一问题的可能原因。 1.卸载失…

C#制做一个 winform下的表情选择窗口

能力有限&#xff0c;别人可能都是通过其他方式实现的&#xff0c;我这里简单粗暴一些&#xff0c;直接通过点击按钮后弹出个新窗体来实现。 1、先在form1上增加一个toolstrip控件&#xff0c;再增加个toolstripbutton按钮&#xff0c;用来点击后弹出新窗体&#xff0c;如图&a…

智能井盖传感器:城市安全卫士

随着城市人口的不断增加和城市基础设施的不断发展&#xff0c;井盖作为城市道路和排水系统的重要组成部分&#xff0c;承担着确保城市安全和便利性的关键角色。然而&#xff0c;井盖在日常使用中常常面临倾斜、水浸和翻转等问题&#xff0c;这些问题可能导致交通阻塞、行人坠井…

小谈设计模式(20)—组合模式

小谈设计模式&#xff08;20&#xff09;—组合模式 专栏介绍专栏地址专栏介绍 组合模式对象类型叶节点组合节点 核心思想应用场景123 结构图结构图分析 Java语言实现首先&#xff0c;我们需要定义一个抽象的组件类 Component&#xff0c;它包含了组合节点和叶节点的公共操作&a…

Windows配置ADB工具

一、目的 在进行嵌入式开发时&#xff0c;我们经常使用ADB工具登录到开发板上进行命令操作&#xff0c;本篇我们介绍如何在windows平台配置ADB环境。 二、实战 1.下载adb工具包​​​​​​​https://developer.android.com/studio/releases/platform-tools?hlzh-cnhttps://d…

任务工单发送失败重试方案设计

需求背景&#xff1a; 该系统为一个工单系统&#xff0c;其中任务工单为该系统中的一个模块&#xff1b;任务工单它是需要周期性调度的一种任务类型&#xff1b;可以按照用户配置的时间周期定时性触发的。由于任务需要发送到对应的工作人员上&#xff0c;所以这里需要先对员工进…

DM宣传单制作,利用在线模板,快速替换文字

如果你需要制作一批宣传单&#xff0c;但是时间很紧&#xff0c;而且没有专业的设计人员协助&#xff0c;那么你可以选择使用在线模板来快速制作宣传单。本文将介绍如何使用乔拓云平台&#xff0c;快速制作宣传单的方法。 步骤一&#xff1a;选择适合的在线制作工具 首先&…

Leetcode hot 100之前缀和、差分数组、位运算

目录 差分数组-区间增减 和为K的子数组&#xff1a;前缀和 哈希表优化 除自身以外数组的乘积&#xff1a;前后缀区间 位运算 异或&#xff1a;同为0&#xff0c;不同为1 136. 只出现一次的数字&#xff1a;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现2次…

【Unity3D编辑器开发】Unity3D编辑器开发基础性框架结构【全面总结】

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 嗨&#xff0c;大家好&#xff0c;我是恬静的小魔龙。 同学们…

Spring MVC 中的国际化和本地化

Spring MVC 中的国际化和本地化 国际化&#xff08;Internationalization&#xff0c;简称i18n&#xff09;和本地化&#xff08;Localization&#xff0c;简称l10n&#xff09;是构建多语言应用程序的重要概念。Spring MVC提供了丰富的支持&#xff0c;使开发人员能够轻松地处…

Spark基础

一、spark基础 1、为什么使用Spark Ⅰ、MapReduce编程模型的局限性 (1) 繁杂 只有Map和Reduce两个操作&#xff0c;复杂的逻辑需要大量的样板代码 (2) 处理效率低 Map中间结果写磁盘&#xff0c;Reduce写HDFS&#xff0c;多个Map通过HDFS交换数据 任务调度与启动开销大 (…

前后端通信到底是怎样一个过程

前后端通信是怎样 前言&#xff1a;Http协议 超文本传输协议 规定&#xff1a;每一次前后端通信&#xff0c;前端需要主动向后端发出请求&#xff0c;后端接收到前端的请求后&#xff0c;可以给出响应 1、Http报文 浏览器向服务器发送请求时&#xff0c;请求本身就是信息&…

【gcc】RtpTransportControllerSend学习笔记 1

本文是woder大神 的文章的学习笔记。主要是大神文章: webrtc源码分析(8)-拥塞控制(上)-码率预估 的学习笔记。大神的webrtc源码分析(8)-拥塞控制(上)-码率预估 详尽而具体,堪称神作。因为直接看大神的文章,自己啥也没记住,所以同时跟着看代码。跟着大神走一遍,不求甚解,…

踩坑日记 uniapp 底部 tabber遮挡住购物车结算

tabbar 被购物车结算遮挡 在小程序上tabbar没有将固定栏遮挡&#xff0c;如果直接调高&#xff0c;浏览器H5页面是对了&#xff0c;但在小程序上面离底部的定位就太高了 原代码 // 底部结算样式.shop-foot {border-top: 2rpx solid #F7F7F7;background-color: #FFF;position: …

机器学习 不均衡数据采样方法:imblearn 库的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…