Linux——信号的保存与处理

目录

前言

一、信号的常见概念

1.信号递达 

2.信号未决

3.信号阻塞

二、Linux中的递达未决阻塞

三、信号集

四、信号集的处理

1.sig相关函数

2.sigprocmask()函数

3.sigpending()函数

五、信号的处理时机

六、信号处理函数


前言

在之前,我们学习了信号的概念以及信号的产生方式,知道进程在何种情况下会收到信号,进程收到并不一定会立刻执行,因此需要对信号进行保存,再来处理信号。

一、信号的常见概念

在学习信号的保存前, 我们先来看看信号的常见概念

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

1.信号递达 

执行信号的处理动作称之为信号递达,具体操作中,对信号一般有三种处理方法

  1. 信号默认处理
  2. 信号忽略
  3. 信号自定义捕捉

我们先来看看信号的默认处理,代码中首先是先自定义捕捉了,当我们前sleep(5),之前输入ctrl+c信号,就会被自定义捕捉,而再后输入,就是默认的处理方式了。 

因此5秒之前,输入ctrl+c会先打印自定义内容,然后2号信号变默认处理方式,开始循环,现在我们给2号新号就会终止进程

那如果我们选择忽略2号新号 

 自然ctrl+c就不能终止了。

SIG_DFL和SIG_IGN的本质就是被函数指针强转的0和1,因为用户传递的地址是非常大的,0和1地址处是代码区,用户是没有设置权限的,因此不用担心传递的自定义函数地址是0和1,如果是0和1,编译器就会特殊处理,知道你想要的是默认还是忽略。

注意:忽略也是一种处理方式

2.信号未决

信号从产生到递达之间的状态,称之为信号未决

信号产生时,可能当前进程再做更重要的事情,不能及时递达,这种情况就叫做信号未决,那么就要求进程对信号进行保存,进程使用了信号位图的方式进行保存。那么信号如果被处理,信号位图中肯定就要置0,因此当还在信号位图为1的时候,也可以称作信号未决

3.信号阻塞

进程可以选择阻塞某个信号。

被阻塞的信号产生时将保持在未决状态,直到进程接触对信号的阻塞,才执行递达的操作。

这里我们看到阻塞和忽略好像差不多,那么阻塞和忽略有什么区别呢?

阻塞是进程根本没有处理该信号,而是处于未决状态,而忽略是阻塞通过后处理的一种操作,只不过我的处理方式是忽略。

小总结

信号是未决的,却不一定被阻塞,因为进程可能还没来得及处理信号。

如果信号被阻塞,且收到了当前信号,那么信号一定是未决的。

二、Linux中的递达未决阻塞

我们说了这么多概念,是有点绕,但确实是比较重要的,进程PCB实际中是真有这三张表的,block(阻塞)、pending(未决)、handler(递达:处理方法)

如果我们想给进程2号信号阻塞,那么就将进程task_struct中指针指向的block表的bit位1(2-1)处置1就好了。因为没有0号信号,因此要-1。

那么从此以后,我们要查看进程对信号的设置以及处理方式,只需要横向观看就好。block是否阻塞,pending是否有信号,handler处理方法是什么。

  • block 0 ,pending 0,代表没有阻塞,也没信号产生或进程已经处理完信号
  • block 0 ,pending 1,代表没有阻塞,信号产生,进程还没来得及处理。
  • block 1 ,pending 0,代表阻塞,没有信号产生或进程已经处理完信号
  • block 1 ,pending 1,代表阻塞,信号产生,进程无法处理信号。同时如果当阻塞取消,进程就会收到信号再做相应处理,并将pending表置0。当然如果此时有很多该信号产生,比如你一直ctrl+c,由于阻塞进程收不到信号,当阻塞取消,只会产生一次信号。

三、信号集

从上面可知,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。

这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

sigset_t本质上就是位图。

四、信号集的处理

1.sig相关函数

#include<signal.h>

int sigemptyset(sigset_t *set);                                   //使其中所有信号的对应bit清零

int sigfillset(sigset_t *set);                                          //使其中所有信号的对应bit置1

int sigaddset (sigset_t *set, int signo);                       //该信号集中添加某种有效信号

int sigdelset(sigset_t *set, int signo);                         //该信号集中删除某种有效信号

int sigismember(const sigset_t *set, int signo);       //判断一个信号集的有效信号中是否包含某种信号

2.sigprocmask()函数

sigprocmaks:可以读取或更改进程的信号屏蔽字

  • 参数1:how,选项:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK

        1.SIG_BLOCK,添加到阻塞的信号                         mask = mask|set

        2.SIG_UNBLOCK:解除阻塞的信号                       mask = mask&~set

        3.SIG_SETMASK:直接设置当前set的值               mask = set

  • 参数2:set,表示对那些信号进行处理
  • 参数3:oset,输出型参数,设置为block表之前的信号信息

函数有点多,我们直接使用起来,我们让进程2号信号阻塞,如果没阻塞,肯定会打印消息,阻塞了发送2号信号就没有反应。

现在无论我们是ctrl+c,还是发送2号信号,该进程都不会有反应,因为2号信号被阻塞了。

你可以阻塞绝大部分信号,但是进程无法阻塞9号信号。9号信号是管理员信号,操作系统发现恶意进程会直接发9号信号kill他,可不管你有没有屏蔽。

3.sigpending()函数

sigpending:读取当前进程的未决信号集,通过set参数传出。

  • 参数1:set,输出型参数,可以将进程的未决信号输出到set中。
  • 返回值:成功返回0,出错返回1

 我们直接来使用sigpending,并写了打印函数帮助我们观看pending表的变化。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>using namespace std;void PrintPending(const sigset_t &pending)
{for (int i = 31; i > 0; i--){if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}int main()
{cout << "pid: " << getpid() << endl;// 屏蔽2号信号sigset_t block, oblock;sigemptyset(&block);  // 初始化为全0sigemptyset(&oblock); // 初始化为全0sigaddset(&block, 2);                    // 将bit位置1sigaddset(&block, 3);                    // 将bit位置1                  // 将bit位置1sigprocmask(SIG_BLOCK, &block, &oblock); // 写入到block信号集中sigset_t pending;while (true){sigpending(&pending);  // 获取pending表里的内容PrintPending(pending); // 打印sleep(1);}
}

效果如下,可以看到pending表的状态变化 

我们知道,当进程收到信号,执行相关操作后,pending表对应的信号bit位会被置为0,那他是将处理操作做完再置0还是处理之前就置为0了呢?

我们写如下代码验证一下

 我们发现pending表在自定义捕捉函数中已经是0了,证明在信号处理前pending表已经被置0

我们学习了这么多,都没有提到信号的保存,但实际上,什么block表,pending表,都是信号保存的实现

五、信号的处理时机

我们一直在说,信号会在合适的时候处理,那么具体什么时候是合适的时候?

其实是进程从内核态返回到用户态的时候,会进行信号的检测和信号的处理。

其中用户态是一种受控的状态,能够访问的资源是有限的,内核态是一种操作系统的工作状态,能访问大部分的系统资源。

我们所写的代码,如果涉及到了系统调用,那么就会将身份切换为内核,那么此时就可以访问内核中的数据结构或字段,当访问结束,准备返回时,并不会立刻返回,而是利用内核身份先看看进程block和pending是否有内容,发现有内容,就会做相应的处理。

就算你没有代码里写得很干净,确实没有系统调用,但是当进程切换的时候,进程也会在用户态和内核态转化,因此不用担心你的代码不会被终止。但这里也涉及到一些问题,这样可能会中断关键代码的执行,因此还需要合理的信号函数或者设置关键代码适当的同步机制来确保其原子性执行。(不懂没关系,等学过多线程就明白了)

如果检测的时候发现某个信号block没有被阻塞,同时pending表为1,就要根据handler表做相应的处理,此时身份一定要切换为用户,为了防止进程利用handler的内核身份去执行自己的恶意操作。那么执行完毕,需要返回时,如果直接返回到用户代码处,那么你怎么知道代码执行到哪一步了,该返回到那个地方,你仅仅是个函数呀!

那么你执行完sighandler函数后,仍然需要执行系统调用sigreturn回到内核(虽然我们代码中没写,但是操作系统会帮我们执行),之后就能访问到相关寄存器或者pc指针等等内容,直到当前进程执行到哪里了,回去继续执行代码。

 

六、信号处理函数

上一章信号的概念和产生,我们学过signal自定义捕捉信号。今天我们再来学一个函数sigaction

sigaction:检查并改变信号的处理动作

参数1:signum,对那个信号进行自定义捕捉

参数2:是一个结构体,重要字段我们只看sa_handler和sa_mask

        1.sa_handler:跟signal的第二个参数一样,自定义捕捉函数的地址

        2.sa_mask:设置其他信号屏蔽(在当前自定义捕捉函数中,将其他信号也屏蔽,等到捕捉函数处理完,再取消屏蔽)

参数3:输出型参数,用来保存调用之前的结构体数据

返回值:成功返回1,失败返回0。

直接上代码,我们让sa_handler进行2号信号自定义捕捉,同时给sa_mask设置3号信号,观察给2号信号后,再发2号信号与3号信号pending表的变化。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>using namespace std;void PrintPending(const sigset_t &pending)
{for (int i = 31; i > 0; i--){if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;
}void sighandler(int signo)
{cout << "收到了" << signo << "号信号" << endl;while(1){sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);}
}int main()
{cout << "pid: " << getpid()  << endl;struct sigaction act,oact;act.sa_handler = sighandler;    //自定义捕捉(更改变量阶段,还没进行写入)sigemptyset(&act.sa_mask);      //sa_mask清空sigaddset(&act.sa_mask,3);      //sa_mask写入3号信号sigaction(2,&act,&oact);        //更改信号的处理动作while(1)sleep(1);
}

当我们ctrl+c给了2号信号,就开始执行自定义捕捉的代码了,由于自定义捕捉是死循环,因此你再发送2号信号,只会让pending表置1,同时由于我们设置了sa_mask的3号信号,于是在执行2号信号的自定义捕捉时,3号信号也无法处理,于是pending表3号信号也置1了。 

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

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

相关文章

verilog设计-cdc:多比特信号跨时钟域(DMUX)

一、前言 多比特一般为数据&#xff0c;其在跨时钟域传输的过程中有多种处理方式&#xff0c;比如DMUX&#xff0c;异步FIFO&#xff0c;双口RAM&#xff0c;握手处理。本文介绍通过DMUX的方式传输多比特信号。 二、DMUX同步跨时钟域数据 dmux表示数据分配器&#xff0c;该方…

【Pt】马灯贴图绘制过程 02-制作锈迹

目录 一、边缘磨损效果 二、刮痕效果 三、边缘磨损与刮痕的混合 四、锈迹效果 本篇效果&#xff1a; 一、边缘磨损效果 将智能材质“Iron Forge Old” 拖入图层 打开“Iron Forge Old” 文件夹&#xff0c;选中“Sharpen”&#xff08;锐化&#xff09;&#xff0c;增大“…

SCI论文改写、防查重神器QuillBot如何付费高级版本?

写论文时候的修改软件QuillBot&#xff0c;正常的文献里的句子帖进去&#xff0c;直接给各种倒装和各种同义词替换至少10次&#xff0c;保证查不出来是别人的句子。 QuillBot是一个帮助改写内容的转述工具。 Quillbot让你的内容重组变得简单。 转述是指你用不同的词来表达&a…

联发科成功在天玑 9300 芯片部署大模型;小米SU7车载“小爱大模型”语音交互技术

&#x1f989; AI新闻 &#x1f680; 联发科成功在天玑 9300 芯片部署大模型 摘要&#xff1a;联发科宣布&#xff0c;在天玑 9300 等旗舰芯片上首次实现大模型&#xff08;通义千问&#xff09;的深度适配&#xff0c;能够在离线状态下运行多轮 AI 对话。此外&#xff0c;阿…

TQ-DDL contention事件导致数据库hang死

数据库一天内多次hang住&#xff0c;最后只能重启恢复&#xff0c;操作系统及数据库版本&#xff1a;Windows Oracle 12.2.0.1检查hang住时间段alert日志&#xff0c;发现数据库多次重启日志&#xff0c;基本上是hang住然后手工重启。检查ash记录&#xff0c;发现重启前有很多“…

BOM系统:贯穿制造全程的管理利器

在制造行业中&#xff0c;BOM系统的应用已经成为提高生产效率、降低成本和确保产品质量的关键因素。BOM系统作为产品结构和物料清单的管理工具&#xff0c;为制造企业提供了全面的控制和协同能力。 1.产品设计与开发&#xff1a;在产品设计阶段&#xff0c;BOM系统为工程师提供…

C++进阶--位图和布隆过滤器

位图和布隆过滤器是两种常用的数据结构&#xff0c;它们在计算机科学领域有着广泛的应用。本文将介绍这两种数据结构的基本原理和应用场景。 位图 前提 位图的概念 位图&#xff08;Bitmap&#xff09;是一种用于表示集合的数据结构&#xff0c;它将每个元素映射为一个位&…

Java八股文(JVM)

Java八股文のJVM JVM JVM 什么是Java虚拟机&#xff08;JVM&#xff09;&#xff1f; Java虚拟机是一个运行Java字节码的虚拟机。 它负责将Java程序翻译成机器代码并执行。 JVM的主要组成部分是什么&#xff1f; JVM包括以下组件&#xff1a; ● 类加载器&#xff08;ClassLoa…

MySQL 数据库的日志管理、备份与恢复

一. 数据库备份 1.数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中&#xff0c;数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因&#xff1a; 程序错误人为,操作错误,运算错误,磁盘故障灾难&#xff08;如火灾、地震&#xff0…

【漏洞分析】浅析android手游lua脚本的加密与解密(二)

反编译本人用到的是luajit-decomp,这里需要注意,luajit-decomp默认的lua版本为5.1,luajit版本为2.0.2,我们需要下载对应lua和luajit的版本,编译后替换luajit-decomp下的lua51.dll、luajit.exe、jit文件夹。反编译时需要注意的文件和文件夹: 这里需要下载版本为2.1.0-bet…

AJAX-项目优化(目录、基地址、token、请求拦截器)

目录管理 基地址存储 在utils/request.js配置axios请求基地址 作用&#xff1a;提取公共前缀地址&#xff0c;配置后axios请求时都会baseURLurl 填写API的公共前缀后&#xff0c;将js文件导入到html文件中 <script src"../../utils/request.js"></script&…

【IntelliJ IDEA】运行测试报错解决方案(附图)

IntelliJ IDEA 版本 2023.3.4 (Ultimate Edition) 测试报错信息 命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行&#xff0c;然后重新运行 解决方案 修改运行配置&#xff0c;里面如果没有缩短命令行&#xff0c;需要再修改选项里面勾选缩短命令行让其显示&#x…

【HTTP完全注解】一些神奇的URL

URL HTTP 请求的内容被称为"资源"&#xff0c;‘资源’这一概念非常宽泛&#xff0c;它可以是一份文档&#xff0c;一张图片&#xff0c;或所有其他你能够想到的格式。每个资源的名称和位置由一个 URL&#xff08;统一资源定位符&#xff0c;它是 URI 的一种&#x…

GT收发器第一篇_总体结构介绍

文章目录 前言GT收发器介绍 前言 之前写过一篇简单介绍GT的文章https://blog.csdn.net/m0_56222647/article/details/136730026&#xff0c;可以先通过这篇文章对整体进行简单了解一下。 GT收发器介绍 参考xilinx手册ug476 对于7系列的FPGA&#xff0c;共有3个系列&#xf…

SQL Server 数据库常见提权总结

前面总结了linux和Windows的提权方式以及Mysql提权&#xff0c;这篇文章讲讲SQL Server数据库的提权。 目录 基础知识 权限判定 系统数据库 存储过程 常见系统存储过程 常见扩展存储过程 xp_cmdshell扩展存储过程提权 xp_dirtree写入文件提权 sp_oacreate提权 xp_re…

那些激励你深入研究技术的语录

遇到耗时高的问题&#xff0c;90%能用重启解决&#xff0c;但是不找到原因&#xff0c;问题一定会再次出现。 代码里的Bug就像房间里的老鼠&#xff0c;你不找到它&#xff0c;它就会一直捣乱。 代码的质量决定了程序的稳定性&#xff0c;而程序的稳定性则决定了业务的成败。 不…

面试题:Redis

一、为什么要用Redis&#xff1f; 1、内存数据库&#xff0c;快&#xff0c;很快....... 2、工作单线程worker&#xff0c;串行化、原子操作. &#xff08;IO线程是多线程&#xff09;- 避免上下文切换 3、IO模型&#xff08;epoll&#xff09;, 支撑高并发. 4、kv模型&…

风电机组的CMS(Condition Monitoring System,状态监测系统)收集的振动信号中包含的噪声主要来源

风电机组的CMS&#xff08;Condition Monitoring System&#xff0c;状态监测系统&#xff09;收集的振动信号中包含的噪声主要来源于风电机组在运行过程中的各种机械部件和环境因素。具体来说&#xff0c;这些噪声主要包括&#xff1a; 机械噪声及结构噪声&#xff1a; 齿轮箱…

华为防火墙配置指引超详细(包含安全配置部分)以USG6320为例

华为防火墙USG6320 华为防火墙USG6320是一款高性能、高可靠的下一代防火墙,适用于中小型企业、分支机构等场景。该防火墙支持多种安全功能,可以有效抵御网络攻击,保护网络安全。 目录 华为防火墙USG6320 1. 初始配置 2. 安全策略配置 3. 防火墙功能配置 4. 高可用性配…

Linux shell编程学习笔记42:md5sum

0 前言 前几天在国产电脑上遇到一个问题&#xff0c;先后接到两个文件&#xff0c;如何判断这两个文件内容是否相同&#xff1f; 如果是在Windows系统&#xff0c;可以用fc命令&#xff0c;或者用我自己写的FileInfo&#xff0c;提取两个文件有MD5、SHA1、CRC32值进行比较来判…