【Linux】信号处理以及补充知识
目录
一、信号被处理的时机:
1、理解:
2、内核态与用户态:
1、概念:
2、重谈地址空间:
3、处理时机:
补充知识:
1、sigaction:
2、函数重入:
3、volatile:
4、SIGCHLD:
前言:
信号在保存后,当OS准备对信号进行处理的时候,还需要到合适的时机才能进行处理,那么什么是合适的时机?
一、信号被处理的时机:
1、理解:
信号的产生是异步的
首先,要将信号进行处理之前就需要让OS知道某个进程收到了信号了,所以进程就需要在合适的时机查查其对应的block,pending,handler表,但是这三个表都属于内核级别的,我们用户级别的进程是不允许访问的,所以这里就自然涉及了进程的用户态和内核态之间的转化
合适的时机:在进程从内核态转化到用户态的时候对信号进行检测,如果有并未被屏蔽就进行处理
怎么进行处理:默认,忽略,用户自定义
2、内核态与用户态:
1、概念:
进程在执行代码的时候不仅仅是只执行用户的代码,还有操作系统的代码,想要访问操作系统就需要变成内核态,执行用户的代码就要变成用户态:
用户态:进程只能访问用户自己的代码和数据
内核态:进程允许访问操作系统的代码和数据
用户态-->内核态
进程时间片到了,进行进程切换的时候
调用系统调用接口:open,read等等
产生异常,中断的时候等等
内核态-->用户态
进程切换完毕
系统调用结束后
异常,中断处理完后
OS是不会收到信号就立即执行的,比如当前我正在进行系统调用或者正在切换进程等等,在从内核态转化为用户态的时候,就进行信号的查询三表和处理信号,所以要等到OS将更重要的事情忙完后,在进程从内核态到用户态的时候就进行信号的检测和处理
2、重谈地址空间:
如下,我们回顾下我们地址空间的知识:
每个进程都有其独有的虚拟地址空间,进程具有独立性
通过页表的映射+MMU机制进行虚拟到物理地址之间的转化
进程都具有其对应的虚拟地址空间,能够让进程以统一的时间看待我们的代码和数据
虚拟地址空间又可分为两个空间0~3GB的空间为用户空间,3~4GB的空间为内核空间
为什么要区分内核空间和用户空间?
因为内核空间中存放的是OS的代码和数据,是不允许进程随便访问的,需要进程切换到内核态才能进行访问,并且规划也能够OS进行更好的管理
其中:用户空间就是我们的代码和数据,当进程在用户态的时候就能够访问这段空间的代码和数据
内核空间中存放的就是OS的代码和数据,这里的虚拟地址通过特有的内核级页表从虚拟地址空间映射到物理内存中,由于OS是最先被加载的程序,所以其映射应该在较为底部的位置
我们知道每一个进程都有其对应的进程地址空间, 那么是不是每一个进程都有其独特的内核空间和内核级页表呢?
答案是只有一份
对应用户空间和用户级页表有很多份的,因为进程具有独立性
但是内核级页表只有一份,内核空间比较特殊,所有进程最终映射到物理内存都是同一块区域,进程只是将操作系统代码和数据映射入自己的进程地址空间而已,其中内核级页表只需将虚拟地址空间映射到物理内存,所有进程都是如此
所以,每一个进程看到的内核空间中的内容,看到的内核级页表资源,最后映射到的物理内存中的代码和数据都是一样的
所以在用户空间的代码区中执行对应的系统调用,
首先将进程从用户态转化到内核态,
然后在自己的内核空间中找到对应的系统调用方法,
然后通过内核级页表映射到物理内存找到对应的代码和数据,
然后在返回(在返回的时候进程从内核态转化为用户态),
这样就相当与在进程自己的进程地址空间中进行系统调用
那么上述的切换是如何进行切换的呢?
首先,我们知道,CR3 寄存器存储当前进程的页目录表物理地址,用于分页机制下的内存管理,
在CPU中同样还有一个叫做ESC寄存器的东西,这个寄存器的作用就是用来表示当前进程的状态:是用户态还是内核态
那么这个寄存器是怎样实现的呢?
在这个寄存器中,有着最后两个比特位,两个比特位有4种表示方式:00 01 10 11,其中只使用两种方式,00和11也就是对应十进制的0和3,
当这个寄存器最后两个比特位为0的时候,表示当前进程处于内核态
当这个寄存器最后两个比特位为1的时候,表示当前进程处于用户态
所以切换进程的状态就需要将ESC寄存器中的最后两个比特位修改为对应的值,CPU为我们提供了一种方法来修改自己的工作状态:int 0x80指令
小总结:
1、每一个进程中的0~3GB中的内容是不一样的,因为进程具有独立性
2、每一个进程中的3~4GB中的内容是一样的,在整个系统中,无论进程再怎么切换, 3~4GB中的内核空间内容是不变的
3、在进程视角:我们进行系统调用,就是在我自己的地址空间中进行执行的
4、操作系统视角:任何一个时刻,都有进程执行,想执行OS的代码可以随时执行
5、操作系统本质是一个基于时钟中断的一个死循环
怎么理解操作系统的执行逻辑呢或者说操作系统的本质是什么?
其可以理解为一种基于中断驱动的“死循环”模型,核心在于通过时钟中断等机制实现任务调度,资源管理和实时响应
主框架循环:
操作系统的核心代码是一个死循环(for(; ;) pause(); ),这个循环并非是空转的,而是通过中断机制被动唤醒的,当没有外部事件(用户输入,I/O完成)或内部事件(如时间片耗尽)时,操作系统会进入低功耗状态或执行空闲任务
其中操作系统本会一直卡在pause()这个行代码暂停,等到发生中断机制,被进程“推着走”才能够让代码得以运行
那有什么中断机制呢?
时钟中断:每过一定很短的时间产生一次,用于更新系统时间、检查进程时间片、触发调度
硬件中断:如键盘输入,由外设通过中断控制器通知CPU
软件中断:如系统调用,允许用户程序访问内核空间
进程是如何被操作系统调用的呢?
进程被调度,就意味着它的时间片到了,操作系统会通过时钟中断,检测到是哪一个进程的时间片到了,然后通过系统调用函数schedule()保存进程的上下文数据,然后选择合适的进程去运行
3、处理时机:
有了上述铺垫的知识,接下来可以理解:
我们前面了解到,在进程从内核态转化到用户态的时候对信号进行检测,处理方式如下图:
对于上述的执行用户自定义有两个问题:
为什么要切回用户态再执行对应的方法:
因为如果在内核态中执行用户自定义的方法,可能自定义方法中存在危害操作系统的代码,这是不安全的
为什么在用户态执行完毕后还要返回内核态再到用户态
需要回到内核态找到进程的上下文,将上下文带出到用户态才行,并且自定义的动作和待返回的进程是属于不同的堆栈,不能够直接返回
对于信号捕捉的理解可以看看下面这张图
如上,这是一个横着的8,然后用一横贯穿,上面是用户态,下面是内核态,注意有4个交点,并且8的交点是在横线下方的也就是在内核态中
接下来解释上图:
四个绿色的圈圈就表示两态之间的切换了四次,当进程时间片到了,进行进程切换,产生异常,中断的时候等等就进行用户态到内核态之间的转化
进程切换完毕,系统调用结束后,异常,中断处理完后进行内核态到用户态之间的转化
此时就进行信号的检测:首先看pending表,如果其为1并且该信号没有被阻塞就执行对应动作,如果被屏蔽或者为0就继续从pending表向下找下一个,以此类推
二、补充知识:
1、sigaction:
其中有个新的结构体:sigaction,其内部成员如下
我们只关心第一个和第三个成员:
成功返回0,失败返回-1,错误码被设置
第一个参数signum:信号编号
第二个参数act: 传入该类结构体,设置屏蔽信号什么的在之前就要设置好
第三个参数oldact:保存修改前的结构体
其中sigaction中的第一个成员变量:就是signal的第二个参数,需要自己设置自定义
第三个成员变量就是屏蔽的信号集,怎么理解呢?
首先我们知道,比如当在处理2号信号的时候,如果sa_mask默认,那么OS就只会屏蔽2号信号,如果还想要屏蔽更多信号,就需要sigaddset(&act.sa_mask,1);这样在待传入结构体中的sa_mask进行更多的设置来屏蔽
void Printpending()
{sigset_t set;sigpending(&set);for(int signo = 31; signo>=1; signo--){if(sigismember(&set,signo)) cout <<"1";else cout<<"0";}cout << endl;
}void myhandler(int signo)
{cout << "get a signo : " << signo << endl;while(1){Printpending();sleep(1);}
}int main()
{struct sigaction act,oact;memset(&act,0,sizeof(act));memset(&oact,0,sizeof(oact));//清空信号集sigemptyset(&act.sa_mask);//添加屏蔽信号sigaddset(&act.sa_mask,1);sigaddset(&act.sa_mask,3);sigaddset(&act.sa_mask,4);sigaddset(&act.sa_mask,9);//设置自定义方法act.sa_handler = myhandler;sigaction(2,&act,&oact); while(1){cout << "i am a process mypid : " << getpid() << endl;sleep(1);}return 0;
}
如上,这样就将1 3 4号都屏蔽了,9号和19号信号可以经过试验发现不可屏蔽,
2、函数重入:
可以被重复进入的函数称为可重入函数
如下是一个场景:
如上,当在insert函数中捕捉到了信号,并且在信号的自定义动作中又调用了insert,这样函数就会重入,这样就会导致函数节点丢失,在释放的时候无法释放node2,就会导致内存泄漏
这就是函数重入导致的内存泄漏问题
我们把这种函数称为可重入函数(注意:这个是特性,不具有褒贬含义)
3、volatile:
int flag;void myhandler(int signo)
{cout << "get a signo : " << signo << endl;flag = 1;
}int main()
{signal(2,myhandler);while(!flag); //flag = 0 为假 !flag 为真cout << "a process quit ! " << endl;return 0;
}
如上,上述本来是一个死循环,但是当给当前进程发送2号信号的时候就会进入我们的自定义函数调用,这个时候在里面将flag修改为1,这样就能够退出while死循环了,并且我们的代码运行起来也是达到预期了
但是在编译中,有个-O1/O2/O3这种的优化,如下,我们把这种优化带着,在运行代码试试
如上,此时发现尽管我们给当前进程发送2号信号,并且也看到了自定义代码中打印的字符串,但是进程却没有退出while这个死循环
这是为什么呢?
这是因为在编译的时候进行了O1的优化
如上,正常情况下,CPU在每次进行逻辑检测的时候,每次都从内存中进行读入,这种IO比较费事,当进行O1的优化之后,就会对整个代码进行检测,此时没有信号就检查不到flag被修改了,此时就会将flag放入逻辑检测的寄存器中,这样,当在自定义中修改了flag,这只是把内存中的flag修改了,但是CPU寄存器中的flag并没有被修改,所以就会一直继续死循环
为了防止这种过度优化,保存内存的可见性,可以在全局变量前面加上volatile关键字修饰,这样就无法将flag放入到寄存器中,老老实实地每次都从内存中进行读入
4、SIGCHLD:
在前面实现进程等待的时候,每次当子进程退出的时候,父进程都需要等待,回收子进程,防止其成为僵尸进程造成内存泄漏问题
父进程有两种方式等待子进程:设置0为阻塞等待或者设置WNOHANG为非阻塞轮询
但是上述两种方式都有缺陷,要么父进程阻塞,不能做其自己的事情,要么每次工作的时候都还要关心关心子进程的状态
那么有没有一种方式能够让父进程不在关心子进程,当子进程退出的时候自动回收呢?
有的有的:
首先要了解:子进程在退出的时候会向父进程发送17号信号,所以我们可以在父进程中将17好信号捕捉,然后在自定义函数中等待,这里设置第一个参数为-1为等待任意进程
void myhandler(int signo)
{pid_t rid = waitpid(-1,nullptr,0);cout << "a signo get : " << signo << " mypid " << getpid() << " rid : " << rid << endl;
}int main()
{signal(17,myhandler);pid_t id = fork();if(id == 0){//childcout << "child process " << getpid() << " myppid : " << getppid() << endl;sleep(5);exit(0);}//fatherwhile(1){cout << "father process " << getpid() << endl;sleep(1);}return 0;
}
如上,这样父进程就可以不用管子进程了,能够完成子进程的自动回收
但是这样还不够,如果有多个子进程呢?能够全部回收吗
这显然是不会的,SIGCHLD这是一个信号,当父进程收到了第一个信号的时候,会将block表中的17号信号置为1使其屏蔽,这样,其他子进程的信号就丢失了,就会导致僵尸进程
那么如何解决呢?
我们在自定义中采取while循环的方式回收即可
当然,还有一种更方便的,但是只能在Linux中有效:
将SIGCHLD这个信号的默认动作设置为忽略,这样父进程不会对其处理,但是当子进程退出之后,OS会对其负责,这样的话就会自动清理资源并回收,不会产生僵尸进程引起内存泄漏
相关文章:

【Linux】信号处理以及补充知识
目录 一、信号被处理的时机: 1、理解: 2、内核态与用户态: 1、概念: 2、重谈地址空间: 3、处理时机: 补充知识: 1、sigaction: 2、函数重入: 3、volatile&…...

pandas——to_datatime用法
Pandas中pd.to_datetime的用法及示例 pd.to_datetime 是 Pandas 库中用于将字符串、整数或列表转换为日期时间(datetime)对象的核心函数。它在处理时间序列数据时至关重要,能够灵活解析多种日期格式并统一为标准时间类型。以下是其核心用法及…...

《DataWorks 深度洞察:量子机器学习重塑深度学习架构,决胜复杂数据战场》
在数字化浪潮汹涌澎湃的当下,大数据已然成为推动各行业发展的核心动力。身处这一时代洪流,企业对数据的处理与分析能力,直接关乎其竞争力的高低。阿里巴巴的DataWorks作为大数据领域的扛鼎之作,凭借强大的数据处理与分析能力&…...

Java 大视界 -- 基于 Java 的大数据实时数据处理框架性能评测与选型建议(121)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...

多线程-JUC
简介 juc,java.util.concurrent包的简称,java1.5时引入。juc中提供了一系列的工具,可以更好地支持高并发任务 juc中提供的工具 可重入锁 ReentrantLock 可重入锁:ReentrantLock,可重入是指当一个线程获取到锁之后&…...

DeepSeek:中国AGI先锋,用技术重塑通用人工智能的未来
在ChatGPT掀起全球大模型热潮的背景下,中国AI领域涌现出一批极具创新力的技术公司,深度求索(DeepSeek)便是其中的典型代表。这家以“探索未知、拓展智能边界”为使命的AI企业,凭借长文本理解、逻辑推理与多模态技术的…...

Vue 框架深度解析:源码分析与实现原理详解
文章目录 一、Vue 核心架构设计1.1 整体架构流程图1.2 模块职责划分 二、响应式系统源码解析2.1 核心类关系图2.2 核心源码分析2.2.1 数据劫持实现2.2.2 依赖收集过程 三、虚拟DOM与Diff算法实现3.1 Diff算法流程图3.2 核心Diff源码 四、模板编译全流程剖析4.1 编译流程图4.2 编…...

Python爬虫获取淘宝快递费接口的详细指南
在电商运营中,快递费用的透明化和精准计算对于提升用户体验、优化物流成本以及增强市场竞争力至关重要。淘宝提供的 item_fee 接口能够帮助开发者快速获取商品的快递费用信息。本文将详细介绍如何使用 Python 爬虫技术结合 item_fee 接口,实现高效的数据…...

基于BMO磁性细菌优化的WSN网络最优节点部署算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 无线传感器网络(Wireless Sensor Network, WSN)由大量分布式传感器节点组成,用于监测物理或环境状况。节点部署是 WSN 的关键问…...

Android Activity的启动器ActivityStarter入口
Activity启动器入口 Android的Activity的启动入口是在ActivityStarter类的execute(),在该方法里面继续调用executeRequest(Request request) ,相应的参数都设置在方法参数request中。代码挺长,分段现在看下它的实现,分段一&#x…...

Python深度学习算法介绍
一、引言 深度学习是机器学习的一个重要分支,它通过构建多层神经网络结构,自动从数据中学习特征表示,从而实现对复杂模式的识别和预测。Python作为一门强大的编程语言,凭借其简洁易读的语法和丰富的库支持,成为深度学…...

关于sqlalchemy的使用
关于sqlalchemy的使用 说明一、sqlachemy总体使用思路二、安装与创建库、连结库三、创建表、增加数据四、查询记录五、更新或删除六、关联表定义七、一对多关联查询八、映射类定义与添加记录 说明 本教程所需软件及库python3.10、sqlalchemy安装与创建库、连结库创建表、增加数…...

利用LLMs准确预测旋转机械(如轴承)的剩余使用寿命(RUL)
研究背景 研究问题:如何准确预测旋转机械(如轴承)的剩余使用寿命(RUL),这对于设备可靠性和减少工业系统中的意外故障至关重要。研究难点:该问题的研究难点包括:训练和测试阶段数据分布不一致、长期RUL预测的泛化能力有限。相关工作:现有工作主要包括基于模型的方法、数…...

深度学习 PyTorch 中 18 种数据增强策略与实现
深度学习pytorch之简单方法自定义9类卷积即插即用 数据增强通过对训练数据进行多种变换,增加数据的多样性,它帮助我们提高模型的鲁棒性,并减少过拟合的风险。PyTorch 提供torchvision.transforms 模块丰富的数据增强操作,我们可以…...

视觉图像处理
在MATLAB中进行视觉图像处理仿真通常涉及图像增强、滤波、分割、特征提取等操作。以下是一个分步指南和示例代码,帮助您快速入门: 1. MATLAB图像处理基础步骤 1.1 读取和显示图像 % 读取图像(替换为实际文件路径) img = imread(lena.jpg); % 显示原图 figure; subplot(2…...

深度学习与普通神经网络有何区别?
深度学习与普通神经网络的主要区别体现在以下几个方面: 一、结构复杂度 普通神经网络:通常指浅层结构,层数较少,一般为2-3层,包括输入层、一个或多个隐藏层、输出层。深度学习:强调通过5层以上的深度架构…...

Vue3、vue学习笔记
<!-- Vue3 --> 1、Vue项目搭建 npm init vuelatest cd 文件目录 npm i npm run dev // npm run _ 这个在package.json中查看scripts /* vue_study\.vscode可删 // vue_study\src\components也可删除(基本语法,不使用组件) */ // vue_study\.vscode\lau…...

python中C#类库调用+调试方法~~~
因为开发需要,我们经常会用C#来写一些库供python调用,但是在使用过程中难免会碰到一些问题,需要我们抽丝剥茧来解决~~~ 首先,我们在python中要想调用C#(基于.net)的dll,需要安装一个库,它就是 pythonnet …...

L33.【LeetCode笔记】循环队列(数组解法)
目录 1.题目 2.分析 方法1:链表 尝试使用单向循环链表模拟 插入节点 解决方法1:开辟(k1)个节点 解决方法2:使用变量size记录队列元素个数 获取队尾元素 其他函数的实现说明 方法2:数组 重要点:指针越界的解决方法 方法1:单独判断 方法2:取模 3.数组代码的逐步实现…...

css实现元素垂直居中显示的7种方式
文章目录 * [【一】知道居中元素的宽高](https://blog.csdn.net/weixin_41305441/article/details/89886846#_1) [absolute 负margin](https://blog.csdn.net/weixin_41305441/article/details/89886846#absolute__margin_2) [absolute margin auto](https://blog.csdn.net…...

【Python】Django 中的算法应用与实现
Django 中的算法应用与实现 在 Django 开发中,算法的应用可以极大地扩展 Web 应用的功能和性能。从简单的数据处理到复杂的机器学习模型,Django 都可以作为一个强大的后端框架来支持这些算法的实现。本文将介绍几种常见的算法及其在 Django 中的使用方法…...

Docker 运行 GPUStack 的详细教程
GPUStack GPUStack 是一个用于运行 AI 模型的开源 GPU 集群管理器。它具有广泛的硬件兼容性,支持多种品牌的 GPU,并能在 Apple MacBook、Windows PC 和 Linux 服务器上运行。GPUStack 支持各种 AI 模型,包括大型语言模型(LLMs&am…...

Kubernetes中的 iptables 规则介绍
#作者:邓伟 文章目录 一、Kubernetes 网络模型概述二、iptables 基础知识三、Kubernetes 中的 iptables 应用四、查看和调试 iptables 规则五、总结 在 Kubernetes 集群中,iptables 是一个核心组件, 用于实现服务发现和网络策略。iptables 通…...

解决VScode 连接不上问题
问题 :VScode 连接不上 解决方案: 1、手动杀死VS Code服务器进程,然后重新尝试登录 打开xshell ,远程连接服务器 ,查看vscode的进程 ,然后全部杀掉 [cxqiZwz9fjj2ssnshikw14avaZ ~]$ ps ajx | grep vsc…...

AI 驱动的软件测试革命:从自动化到智能化的进阶之路
🚀引言:软件测试的智能化转型浪潮 在数字化转型加速的今天,软件产品的迭代速度与复杂度呈指数级增长。传统软件测试依赖人工编写用例、执行测试的模式,已难以应对快速交付与高质量要求的双重挑战。人工智能技术的突破为测试领域注…...

【Java代码审计 | 第六篇】XSS防范
文章目录 XSS防范使用HTML转义使用Content Security Policy (CSP)输入验证使用安全的库和框架避免直接使用用户输入构建JavaScript代码 XSS防范 使用HTML转义 在输出用户输入时,对特殊字符进行转义,防止它们被解释为HTML或JavaScript代码。 例如&…...

Android WebSocket工具类:重连、心跳、消息队列一站式解决方案
依赖库 使用 OkHttp 的WebSocket支持。 在 build.gradle 中添加依赖: implementation com.squareup.okhttp3:okhttp:4.9.3WebSocket工具类实现 import okhttp3.*; import android.os.Handler; import android.os.Looper; import android.util.Log;import java.ut…...

认识vue2脚手架
1.认识脚手架结构 使用VSCode将vue项目打开: package.json:包的说明书(包的名字,包的版本,依赖哪些库)。该文件里有webpack的短命令: serve(启动内置服务器) build命令…...

【STM32】STM32系列产品以及新手入门的STM32F103
📢 STM32F103xC/D/E 系列是一款高性能、低功耗的 32 位 MCU,适用于工业、汽车、消费电子等领域;基于 ARM Cortex-M3,主频最高 72MHz,支持 512KB Flash、64KB SRAM,适合复杂嵌入式应用,提供丰富的…...

<建模软件安装教程1>Blender4.2系列
Blender4.2安装教程 0注意:Windows环境下安装 第一步,百度网盘提取安装包。百度网盘链接:通过网盘分享的文件:blender.zip 链接: https://pan.baidu.com/s/1OG0jMMtN0qWDSQ6z_rE-9w 提取码: 0309 --来自百度网盘超级会员v3的分…...