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

news2024/7/4 4:25:44

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

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

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

  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);//将所有信号位位码全部置为0
    sigaddset(&newset , SIGINT);//屏蔽SIGINT信号,将其对应信号位的位码置为1
    int bitcode = sigismember(&newset , SIGINT);//利用sigismember函数检验位码是否被置为1
    if(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_mask
    sigaction(SIGINT , &act , &old_act);//将信号SIGINT的行为替换为捕捉行为,并传出原有行为
    //写一个死循环,每次睡一秒,看CTRL+C能否杀死该进程
    while(1){
	    sleep(1);
    }
    return 0;
}

图解

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

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

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

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

相关文章

企业防止泄密,应该做到哪些?(防止数据泄露的方法有哪些)

随着信息化时代的快速发展&#xff0c;信息泄露问题越来越普遍。一个数据泄露事件就可能导致企业巨额损失&#xff0c;甚至影响用户的安全和隐私。因此&#xff0c;如何防止失泄密问题的发生是企业和个人都需要重视的事情。下面介绍一些具体的方法和措施来防止失泄密问题发生。…

FR问题记录

1. 问题&#xff1a;下拉框搜索查询时&#xff0c;出现所有的搜索信息 解决方法&#xff1a;使用数据集sql进行数据筛选 在数据库查询中使用where条件筛选&#xff0c;如 ${IF(LEN(所属省份) 0,"","AND province in (" 所属省份 ")")}参考…

百度统计统计第三方网站的浏览量和访问量

百度统计简介 百度统计——领先的中文网站分析平台 百度统计是百度推出的一款免费的专业网站流量分析工具&#xff0c;能够告诉用户访客是如何找到并浏览用户的网站&#xff0c;在网站上做了些什么&#xff0c;有了这些信息&#xff0c;可以帮助用户改善访客在用户的网站上的…

【单调栈】下一个更大元素 I

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;暴力枚举方法二&#xff1a;单调栈哈希表 知识回顾单调栈 写在最后 Tag 【单调栈哈希表】【数组】 题目来源 496. 下一个更大元素 I 题目解读 找出 nums1 中数字 x 在 nums2 中的位置&#xff0c;并找出在 nums2 中比…

【SpringBoot】文件分片上传、合并

背景 在上传大型文件时&#xff0c;一般采用的都是分片、断点续传等技术&#xff0c;这样不会导致因文件过大而造成系统超时或者过压等情况。 接下来我们进入教学 如果有帮助到您&#xff0c;麻烦请点击个收藏、赞&#xff0c;谢谢~ 一、实际效果图 整个前端网页的效果图&…

商城小程序代客下单程序开发演示

一款专为传统电商、实体商家开发的商城系统小程序&#xff0c;做私域、做留存、做社交必备功能全都有。 1、丰富的营销玩法&#xff1a;拼团、秒杀、定金预售、分销、社区团购、积分商城、支付有礼等主流获客玩法都有。 2、强大的会员体系&#xff1a;普通会员、付费会员、会…

spring 事务源码阅读

开启事务 使用EnableTransactionManagement注解开启事务 该注解会引入TransactionManagementConfigurationSelector类&#xff0c;然后该类导入两个类AutoProxyRegistrar和ProxyTransactionManagementConfiguration。 1、添加bean后置处理器 AutoProxyRegistrar类的作用是注…

883. 高斯消元解线性方程组

883. 高斯消元解线性方程组 - AcWing题库 输入一个包含 n 个方程 n 个未知数的线性方程组。 方程组中的系数为实数。 求解这个方程组。 下图为一个包含 m 个方程 n 个未知数的线性方程组示例&#xff1a; 输入格式 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含 n1…

千万不要支付赎金!解密.halo勒索病毒的秘诀在这里

导言&#xff1a; .halo 勒索病毒等勒索病毒已经成为网络犯罪分子的利器&#xff0c;威胁着很多企业的数据安全。本文91数据恢复将为您介绍 .halo 勒索病毒的新面貌&#xff0c;以及一些创新的方法&#xff0c;如何保护和恢复被 .halo 勒索病毒加密的数据文件&#xff0c;并提供…

【计算机视觉|人脸建模】学习从图像中回归3D面部形状和表情而无需3D监督

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Learning to Regress 3D Face Shape and Expression from an Image without 3D Supervision 链接&#xff1a;[1905.06817] Learning to Regress 3D Face Shape and Expression from an I…

6-10 单链表分段逆转 分数 15

void K_Reverse( List L, int K ) { //此题已经默认size > K 因为当size < K时 反转后将不再符合链表的定义//求出表中元素个数int size 0;for (List cur L->Next; cur ! NULL; cur cur->Next)size; List prv, cur, next, first, head L;//共需要反转 si…

功率放大器可以放大电压吗为什么

功率放大器是一种电子设备&#xff0c;用于将输入信号的功率放大到更高的水平。而电压是功率的一个组成部分&#xff0c;所以功率放大器可以放大电压。 在功率放大器中&#xff0c;输入信号经过放大器的放大阶段后&#xff0c;会得到一个更强的输出信号。这个放大过程是通过增加…

HTML5开发实例-3D全景(ThreeJs全景Demo) 详解(图)

前言 在现在市面上很多全景H5的环境下,要实现全景的方式有很多,可以用css3直接构建也可以用基于threeJs的库来实现,还有很多别的制作全景的软件使用 本教学适用于未开发过3D全景的工程狮 如果觉得内容太无聊可以直接跳到最后 下载代码 理论 整个3D全景所用的相关理论就…

CSS布局 | flex布局

flex布局 flex是CSS3中新增的布局手段&#xff0c;优势是适用于不同屏幕尺寸和设备&#xff0c;当布局涉及到不定宽度&#xff0c;分布对⻬的场景时&#xff0c;我们可以优先考虑弹性盒布局。 任何一个容器都可以指定为Flex布局&#xff0c;容器设为 Flex 布局以后&#xff0…

电驱2035目标及新材料研究应用进展-2023

今天同大家一起了解DOE电驱2035目标&#xff08;成本、功率密度、电压、峰值功率&#xff09;&#xff0c;及当前研发项目中关于电驱电机的新材料研究进展与应用。 2030-2035电驱系统目标 峰值功率和功率密度按每5年50%的速度提升&#xff0c;电压平台800V&#xff0c;增加峰值…

数据被加密?.locked1勒索病毒的简单解决方法

引言&#xff1a; 在数字化的今天&#xff0c;数据宛如生命的一部分&#xff0c;而 .locked1 勒索病毒这种威胁正在如影随形地威胁着我们的数字宝库。本文将为您生动地介绍 .locked1 勒索病毒&#xff0c;以及如何摆脱它的束缚&#xff0c;解锁被其加密的数据&#xff0c;同时提…

Unity Golang项目教程-创建项目

安装Unity Unity的安装比较简单。这里我不做详细介绍&#xff0c;提供一些安装教程链接&#xff0c;如果还有困难下面我提供联系方式可以私信我。 安装教程参考 创建工程如下图所示&#xff1a; 等待项目创建完成即可。 如有问题可以Q联系我&#xff0c; 873149745

CCF中国开源大会专访 | 刘旭东:着眼“开源联合”,实现“聚力共赢”

受访嘉宾 | 刘旭东 记者 | 朱珂欣 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 2023 CCF 中国开源大会&#xff08; CCF ChinaOSC &#xff09;拟于 2023 年 10 月 21 日至 22 日在湖南省长沙市北辰国际会议中心召开。 作为第二届 CCF 中国开源大会&a…

计算CPK及规范限合格率,并绘制概率密度曲线

CPK&#xff08;过程能力指数&#xff09;是一个用于衡量一个过程的稳定性和一致性的统计指标&#xff0c;特别用于制造业和质量管理中。它衡量了一个过程的变异性与规范界限的关系&#xff0c;帮助确定过程是否能够产生合格的产品或服务。 正态分布假设&#xff1a;CPK的计算…

Kfka监控工具--Kafka-eagle安装

1、开启Kafka JMX端口 JMX 是一个为应用程序植入管理功能的框架 在启动Kafka脚本之前&#xff0c;添加&#xff1a; export JMX_PORT9988 nohup bin/kafka-server-start.sh comfig/server.properties 2、安装jdk配置好JAVA_HOME 3、将kafka_eagle 上传并解压 tar -zxvf …