【Linux】:信号的保存和信号处理
朋友们、伙计们,我们又见面了,本期来给大家带来信号的保存和信号处理相关代码和知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!
C 语 言 专 栏:C语言:从入门到精通
数据结构专栏:数据结构
个 人 主 页 :stackY、
C + + 专 栏 :C++
Linux 专 栏 :Linux
目录
1. 信号的保存
1.1 信号相关概念
1.2 信号的保存
1.3 处理位图的接口
2. 信号的处理
2.1 状态的切换
2.2 信号的处理
2.3 sigaction函数
3. 信号的其他补充
3.1 可重入函数
3.2 SIGCHLD信号
1. 信号的保存
1.1 信号相关概念
- 实际执行信号的处理动作称为信号递达(Delivery)。
信号递达的方式有三种:
- ① 信号的默认处理
- ② 信号的忽略
- ③ 信号的自定义捕捉
当我们自定义捕捉信号的时候使用的signal接口就是对指定信号进行捕捉,然后去执行我们自定义的方法,下面再来介绍一下两种用法:
- ① signal(signo, SIG_DFL):对指定信号恢复默认操作;
- ② signal(signo, SIG_IGN):对指定信号进行忽略(忽略也算做对信号进行处理)。
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
- 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
1.2 信号的保存
当信号产生时,我们不一定要立即对信号进行递达,而是在合适的时候进行递达,那么在信号未决时期,我们要有能力将信号保存,所以在进程PCB中会存在三张位图表,用于保存信号:
信号屏蔽字(block表):比特位的位置表示信号的编号、比特位的内容表示是否对特定信号进行屏蔽(阻塞)。
未决位图表(pending表):比特位的位置表示信号编号、比特位的内容表示特定的信号时候被递达。
handler表(函数指针数组):比特位的位置表示信号编号、比特位的内容是一个函数指针,指向该信号的处理方法。
注意:常规信号在递达之前产生多次只记一次!
1.3 处理位图的接口
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量:
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset (sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismember(const sigset_t *set, int signo);
- 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
- 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
- 函数sigaddset用于向指定的信号集添加某种信号。
- 函数sigdelset用于删除指定信号集中的某种信号。
- 函数sigismember用于判断指定信号在指定信号集是否存在。
对block表进行操作:
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
① set:将要设置的新的信号屏蔽字
② oldset:获取旧的信号屏蔽字
③ how:修改block表的选项
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask = mask l set SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask & ~set
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于
mask = set
对pengding表操作:#include <signal.h> int sigpending(sigset_t *set);
参数:
① set:获取当前进程的信号未决表
返回值:
成功返回0,出错返回-1
接下来通过这些接口我们可以实现一个动态的打印pending表的一个代码:
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> using namespace std;// 打印pending表 void PrintPending(const sigset_t &pending) {for (int signo = 31; signo > 0; signo--){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n"; }int main() {// 1. 屏蔽2号信号sigset_t set, oset;// 1.1 初始化信号集sigemptyset(&set);sigemptyset(&oset);// 1.2 添加信号sigaddset(&set, 2);// 1.3 修改信号集sigprocmask(SIG_BLOCK, &set, &oset);// 2. 让进程不断获取当前进程的pendingint cnt = 0;sigset_t pending;while (true){// 2.1 获取pending表sigpending(&pending);// 2.2 打印PrintPending(pending);sleep(1);cnt++;if (cnt == 10){std::cout << "解除对2号信号的屏蔽, 2号信号准备递达" << std::endl;// 2.3 恢复原pending表sigprocmask(SIG_SETMASK, &oset, nullptr);}}return 0; }
2. 信号的处理
2.1 状态的切换
进程会在合适的时候处理信号,那么这个合适的时候是指什么时候呢?
进程从内核态返回到用户态时,进行信号的检测和处理。
- 用户态:一种受控的状态,能够访问的资源是有限的。
- 内核态:操作系统的工作状态,能访问大部分的系统资源,并且可以让用户以操作系统的身份访问内核空间。
- ① 用户是无法直接访问OS底层资源,只能通过系统调用间接访问,所以用户调用系统调用,必然包含了身份的变化;
- ② 进程要被调度首先得加载到内存然后通过页表映射到物理内存,那么操作系统也是需要被映射到物理内存的;
- ③ 用户空间由用户级页表映射到物理内存;
- ④ 内核空间由内核级页表映射到物理内存;
- 所以在调用系统调用时访问OS直接在进程地址空间内进行跳转,就如同函数调用一样,调用系统调用接口也是在进程地址空间内进行的。
① 操作系统的代码、系统调用、数据结构、数据在整个系统中只有一份,所以内核级页表只需要有一张即可;
② 如果有多个进程,只需将内核空间通过内核级页表映射到物理内存,尽管有多个进程,使用的也是同一份系统调用接口;
③ 无论进程如何调度,CPU都可以直接找到操作系统!
④ 我们进程所有代码的执行,都可以在自己的进程地址空间内通过跳转的方式,进行调用和返回。
那么如何区分内核态和用户态呢?
CPU内存在的寄存器CS寄存器,CS寄存器用来保存代码段的,其中有两个比特位01表示内核态(1)、11表示用户态(3);切换用户的状态其实就是修改CS寄存器中对应的比特位。
CPU内部还存在一些CR寄存器:
CR3寄存器用于保存当前运行进程的用户级页表的物理地址;
CR1寄存器用于保存上一次引发缺页中断的虚拟地址。
2.2 信号的处理
用户在调用系统调用之后,在要完成调用任务时,会从用户态切换至内核态完成对应的任务,此时并不是直接切换回用户态,而是先要检测信号,如果有需要处理的信号,根据对信号的处理方法,如果是默认动作、忽略就直接处理,如果是用户自定义方法,那么此时不能在内核态处理,而是要返回用户态去执行用户自定义方法,在执行完之后,不能直接跳转到用户代码处,而是要再次返回内核态,再从内核态返回进入内核态的用户代码处。
简化的图就是一个♾️
在信号捕捉中,一共会涉及到4次状态的切换!
上述情况是只有一个信号需要被处理,那么如果存在多个需要处理的信号,那么在处理完一个信号之后会轮训式的检测需要处理的信号,在所有信号处理完之后再切换为用户态。
2.3 sigaction函数
该函数是一个检测信号并改变处理动作的函数:
参数:
signum:要改变的信号的编号;
sigaction是一个结构体:
其中我们只需要关注sa_handler和sa_mask
sa_handler是要指定的处理动作;
sa_mask是要额外屏蔽的信号集。
act:表示要改变的新的处理方法;
oldact:表示被改变之前的处理方法。
Linux是不允许同一个信号已经在被处理的过程中,再次进行嵌套处理的,所以当某一个信号在被处理的过程中,内核会自动将该信号加入到信号屏蔽字中,当处理的函数返回之后,会对该信号进行恢复,除了当前处理的信号被屏蔽外,我们也可以通过sa_mask(信号集)添加一些额外的信号进行屏蔽。
代码演示:
#include <iostream> #include <unistd.h> #include <signal.h>void Print(const sigset_t &pending);void handler(int signo) {std::cout << "get a sig: " << signo << std::endl;sleep(1);while (true){sigset_t pending;sigpending(&pending);Print(pending);sleep(1);} }// 打印pending表 void Print(const sigset_t &pending) {for (int signo = 31; signo > 0; signo--){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << std::endl; } int main() {std::cout << "pid: " << getpid() << std::endl;struct sigaction act, oact;// 自定义处理方法act.sa_handler = handler;// 初始化信号集sigemptyset(&act.sa_mask);// 添加3号信号sigaddset(&act.sa_mask, 3);// 自定义捕捉2号信号sigaction(2, &act, &oact);while (1)sleep(1);return 0; }
3. 信号的其他补充
3.1 可重入函数
将函数和信号结合起来研究:
在链表阶段我们实现了一个头插的函数接口,头插的阶段分为两步,做完第一步的时候由于某些硬件中断使进程回到了内核态,再次返回用户态时需要进行信号的检测与处理,如果此时的信号自定义方法中也调用了头插的函数,在做完头插的两步之后又重新返回用户态的代码处继续向下执行,那么在自定义方法中插入的头节点就会被丢失掉,此时这个头插的这个函数就是一个不可重入函数。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的 :
- 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
- 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
3.2 SIGCHLD信号
在进程等待的章节说到过,子进程退出时父进程必须进行等待(waitpid()),否则会造成僵尸问题,并且我们有时还需要知道子进程的退出信息,另外在子进程退出的时候回向父进程发送SIGCHLD信号。
#include <iostream> #include <unistd.h> #include <signal.h>void handler(int signo) {std::cout << "get a sig: " << signo << std::endl; }int main() {std::cout << "pid: " << getpid() << std::endl;// 自定义捕捉信号signal(SIGCHLD, handler);pid_t id = fork();if (id == 0){std::cout << "child is running" << std::endl;sleep(5);exit(10);}while (true)sleep(1);return 0; }
父进程自定义捕捉SIGCHLD信号,子进程在运行5秒后退出,可以看到果然子进程给父进程发送了SIGCHLD信号。
所以我们就可以基于信号来对子进程进行回收等待了:
#include <iostream> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h>void handler(int signo) {std::cout << "get a sig: " << signo << std::endl;// 等待任意进程waitpid(-1, nullptr, 0); }int main() {std::cout << "pid: " << getpid() << std::endl;// 自定义捕捉信号signal(SIGCHLD, handler);pid_t id = fork();if (id == 0){std::cout << "child is running" << std::endl;sleep(5);exit(10);}while (true)sleep(1);return 0; }
Linux支持手动忽略SIGCHLD,如果对其进行忽略,那么所有的子进程都不要父进程进行等待了,子进程会在终止时自动的清理。
#include <iostream> #include <unistd.h> #include <signal.h>int main() {std::cout << "pid: " << getpid() << std::endl;// 手动忽略SIGCHLDsignal(SIGCHLD, SIG_IGN);pid_t id = fork();if (id == 0){std::cout << "child is running" << std::endl;sleep(5);exit(10);}return 0; }
朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!
相关文章:

【Linux】:信号的保存和信号处理
朋友们、伙计们,我们又见面了,本期来给大家带来信号的保存和信号处理相关代码和知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入…...

深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.1 意义 对于C、C程序开发来说,程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制,让java程序员不需要手写delete或者free代码,不容易出现内存泄漏和内存溢出问题,但是如果…...

跨境电商必备保护账号的4个网络环境设置
在跨境电商的世界里,一个稳定可靠的网络环境就是你事业成功的关键!但是,不稳定的IP很容易导致账号被封,让你的辛苦付之东流,相信许多小伙伴也经历过莫名其妙的账号封禁情况! 为了让大家避免这种心痛的情况…...

Python+requests接口自动化测试框架实例教程
前段时间由于公司测试方向的转型,由原来的web页面功能测试转变成接口测试,之前大多都是手工进行,利用postman和jmeter进行的接口测试,后来,组内有人讲原先web自动化的测试框架移驾成接口的自动化框架,使用的…...

【网络安全】DNS重绑定原理详析
原创文章,不得转载。 文章目录 DNSDNS查询过程同源策略DNS重绑定攻击原理DNS重绑定攻击步骤DNS重绑定工具工具一工具二DNS 在网络中,访问网站实际上是通过其对应的 IP 地址实现的,然而,IP 地址往往难以记忆。因此,DNS(域名系统)应运而生。 DNS(Domain Name System)是…...

C语言初识编译和链接
目录 翻译环境和运行环境编译环境预编译编译词法分析语法分析语义分析 汇编 链接运行环境 翻译环境和运行环境 在ANSI C的任何⼀种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令(⼆进制指令&…...

TrinityCore环境搭建
1)https://192.168.3.96:41797/soft/app root jianan2)mysql322bb8f85b0920d9 192.168.3.96 9f5c813fefbbc3aa3) su wow cd /home/wow/TrinityCore/TrinityCore-TDB335.22061/build cmake ../ -DCMAKE_INSTALL_PREFIX/home/wow/server3.5.5 #构建项目cmake ../ -DCMAKE_INSTALL…...

Proteus 仿真设计:开启电子工程创新之门
摘要: 本文详细介绍了 Proteus 仿真软件在电子工程领域的广泛应用。从 Proteus 的功能特点、安装与使用方法入手,深入探讨了其在电路设计、单片机系统仿真、PCB 设计等方面的强大优势。通过具体的案例分析,展示了如何利用 Proteus 进行高效的…...

microchip dspic3一些奇怪问题
UART初始化,导致一些MCU PIN输出低电平。 https://microchip.my.site.com/s/case/500V4000007jvz4IAA/detail 板子上电EEPROM读取不稳定,增加延时解决问题。 –If delay 1ms, will read EE Err –If delay 10ms, program and reset, will read EE err.…...

FinOps原则:云计算成本管理的关键
导语: FinOps 原则为我们提供了北极星(North Star),在我们实践云财务管理时指导我们的活动。这些原则由 FinOps 基金会成员制定,并通过经验磨练出来。 北极星(North Star)的含义: …...

JavaScript之如何优化模板字符串的性能
在 JavaScript 中,优化模板字符串的性能可以从几个方面入手。模板字符串(Template Literals)是 ES6 引入的特性,它们使用反引号 () 包围,可以嵌入表达式并支持多行字符串。虽然模板字符串通常很方便,但在性…...

不能将类型“null”分配给类型“number | undefined”。ts(2322)
错误解释: 这个TypeScript错误表明你正在尝试将null赋值给一个预期为number类型或undefined类型的变量。在TypeScript中,null和undefined是有效的值,但通常我们希望它们与number类型不兼容。 解决方法: 检查导致错误的赋值语句&…...

Nginx部署前端Vue项目详细教程
文章目录 Nginx部署前端Vue项目详细教程准备工作打包Vue项目安装Nginx配置Nginx创建配置文件启用配置文件 部署Vue项目配置SSL(可选)测试和验证总结 Nginx部署前端Vue项目详细教程 本教程将详细介绍如何使用Nginx部署前端Vue项目,涵盖从项目…...

kvm 虚拟机命令行虚拟机操作、制作快照和恢复快照以及工作常用总结
文章目录 kvm 虚拟机命令行虚拟机操作、制作快照和恢复快照一、kvm 虚拟机命令行虚拟机操作(创建和删除)查看虚拟机virt-install创建一个虚拟机关闭虚拟机重启虚拟机销毁虚拟机 二、kvm 制作快照和恢复快照**创建快照**工作常见问题创建快照报错::intern…...

内网安全-横向移动【3】
1.域横向移动-内网服务-Exchange探针 Exchange是一个电子右键服务组件,由微软公司开发。它不仅是一个邮件系统,还是一个消息与协作系统。Exchange可以用来构建企业、学校的邮件系统,同时也是一个协作平台,可以基于此开发工作流、…...

语言中的浮点数
浮点数相比定点数或者整数,为了处理小数点引入了指数,导致小数点的位置根据不同浮点数而不同,故名为Floating Point Number. 一般而言,IEEE754标准被大部分编程语言的浮点数使用,它节省了浮点数的保存空间。如不然&…...

Pyspark下操作dataframe方法(1)
文章目录 Pyspark dataframe创建DataFrame使用Row对象使用元组与scheam使用字典与scheam注意 agg 聚合操作alias 设置别名字段设置别名设置dataframe别名 cache 缓存checkpoint RDD持久化到外部存储coalesce 设置dataframe分区数量collect 拉取数据columns 获取dataframe列 Pys…...
注解实现json序列化的时候自动进行数据脱敏
最近在进行开发的时候遇到一个问题,需要对用户信息进行脱敏处理,原有的方式是写一个util类,在需要脱敏的字段查出数据后,显示掉用方法处理后再set回去,觉得这种方式能实现功能,但是不是特别优雅,…...

使用Python下载文件的简易指南
在日常的数据处理、自动化任务或软件开发中,经常需要从网络上下载文件。Python作为一门功能强大的编程语言,提供了多种方法来实现文件的下载。本文将介绍几种常用的方法来使用Python下载文件,包括使用requests库和urllib库。 准备工作 在开…...

中秋国庆双节长假,景区迎来客流高峰,如何保障景区安全管理?
一、方案背景 近年来,国内旅游市场持续升温,节假日期间景区游客数量激增,给景区安全管理带来了巨大挑战。然而,景区安全风险意识不足、防护措施不完善、游客安全意识欠缺等问题依然存在,导致景区安全事故频发。随着中秋…...

多维数组转一维数组:探索 JavaScript 中的数组扁平化
在 JavaScript 编程中,我们经常会遇到需要将多维数组转换为一维数组的情况。无论是处理复杂的数据结构还是进行数据的进一步操作,数组扁平化都是一个常见且有用的技术。本文将介绍几种在 JavaScript 中将多维数组转换为一维数组的方法。 什么是数组扁平…...

配环境时的一些记录
连centos:正常连就好(密码验证码)连rocky:需要在centos上连,终端里直接ssh [rocky_ip];在vscode中需要: 修改配置文件:打开命令面板(ctrlshiftp) -> 输入并…...

如何解析域名到网站?
在现代互联网中,域名解析是用户访问网站的关键过程。用户通过输入易于记忆的域名来访问网站,而背后则是复杂的域名解析机制将域名转换为服务器的IP地址,使得浏览器能够找到并加载目标网站。聚名网详细介绍域名解析的过程及其相关技术。 一、…...

【F172】基于Springboot+vue实现的智能菜谱系统
作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 近些年,随着中国经济发展,人民的…...

Spring-AOP核心源码、原理详解前篇
本文主要分4部分 Aop原理介绍介绍aop相关的一些类通过源码详解aop代理的创建过程通过源码详解aop代理的调用过程Aop代理一些特性的使用案例 Spring AOP原理 原理比较简单,主要就是使用jdk动态代理和cglib代理来创建代理对象,通过代理对象来访问目标对象…...

Reflection反射——Class类
概述 在Java中,除了int等基本类型外,Java的其他类型全部都是class(包括interface)。例如: String、Object、Runnable、Exception…… Java反射机制是Java语言的一个重要特性。在学习Java反射机制前,需要了…...

王朝兴替的因果
天道好轮 回,苍天饶过谁。王朝兴亡,天道无情。 而其因果循环,天道之森严,让人敬畏。 王朝创业帝王造下什么业,后世子孙在兴替之时,往往要承担何种果 报。 中国几千年的王朝史,因 果循环&…...

损坏SD数据恢复的8种有效方法
SD卡被用于许多不同的产品来存储重要数据,如图片和重要的商业文件。如果您的SD卡坏了,您需要SD数据恢复来获取您的信息。通过从损坏的SD卡中取回数据,您可以确保重要文件不会永远丢失,这对于工作或个人原因是非常重要的。 有许多…...

好评如潮的年度黑马韩剧,惊喜从一上线就开始
韩剧一直以来都以细腻的情感和紧凑的剧情打动观众,而最近播出的一部作品更是掀起了不小的风波-《法官大人》。孙贤周与金明民两大演技派领衔主演,凭借他们的深沉演技和复杂的角色关系,让这部剧集迅速成为热议焦点。故事围绕着一起交通事故展开…...

超好用的PC端语音转文字工具CapsWriter-Offline结合内网穿透实现远程使用
文章目录 前言1. 软件与模型下载2. 本地使用测试3. 异地远程使用3.1 内网穿透工具下载安装3.2 配置公网地址3.3 修改config文件3.4 异地远程访问服务端 4. 配置固定公网地址4.1 修改config文件 5. 固定tcp公网地址远程访问服务端 前言 本文主要介绍如何在Windows系统电脑端使用…...