【linux】信号(三)

news2024/12/26 0:00:53

本章节将会围绕信号处理进行展开讲解
在这里插入图片描述

目录

  • 回顾一下:
  • 历史问题:
    • 内核态 VS 用户态
      • 地址空间:
      • 键盘的输出如何被检测到:
      • OS如何正常运行:
      • 如何执行系统调用:
  • 信号的处理:
    • sigaction:
      • 信号的特点:
  • 基于信号的理解讨论3个子问题:
    • 可重入函数:
    • volatile:
    • SIGCHLD:

回顾一下:

信号处理也就是信号递达
我们说过递达时一种有3种行为

  1. 默认行为
  2. 忽略行为
  3. 自定义行为

历史问题:

我们其实一直都存在一个问题:一般信号发送时不会被立即处理,而是等到合适的时候进行处理,那么这个合适的时候究竟是什么时候?

先说结论:从内核态返回到用户态时进行处理。

这两个名词等会会有解释,现在重要的是先将脉络理清楚,在去深究细节。

返回时我们会先看此时pending表有没有为1的信号,在看是否阻塞,如果没有,那么我们就会执行handler函数,执行handler函数我们一般有3种行为

  • SIG_DFL :默认行为,大部分进行都是进行终止,如果是终止的话,我们OS在内核态会直接杀死进程,不会返回到main函数了。
  • SIG_ING:忽略行为,把对应的1->0,返回到main函数继续执行
  • 自定义:我们一般把这种情况叫做信号捕捉。
    也就是下图
    在这里插入图片描述

那我们现在就要理清一下这张图的逻辑。

首先,我们因为某些系统调用等情况进入到内核,当处理完进入到内核的情况后会检查信号,观察pending与block位图是否符合要求,如果是默认OS在内核态会直接杀死进程,不会返回到main函数了;如果是忽略把对应的1->0,返回到main函数继续执行;但是最恶心的是自定义行为,也就是我们这样图所示。

此时我们面临一个问题,OS可以直接去执行用户的代码吗?
答案是否定的,如果用户进行了exec等系列函数,或者代码出错,那么OS岂不就完蛋了吗,但是在技术角度肯定可以实现,但是仍然不会给你实现。

那就必须进行状态切换,让用户自己执行自己的代码,自己出错自己负责。

那么我们可以直接从第四步执行到第一步吗,
答案也是否定的,最直观的就是可以看到我们的handler函数并没有调用main函数,也就是没有直接的相互调用关系。

此时需要使用一些特殊的系统调用函数,返回内核再返回main函数继续执行。

这样我们就·大概了解了信号捕捉的流程。一个无穷符号。
对于如何快速记忆,我们可以先画一个无穷符号,在焦点的上方画一条横线。
在这里插入图片描述
横线上方是用户态,下方是内核态。

到现在为止要开始暂停一下了,我们要开始讲几个子问题,知道这些才能搞定内核态与用户态的区别。

内核态 VS 用户态

地址空间:

我们先来看一个大概的图。
在这里插入图片描述
对于程序地址空间我们已经接触过很多次了,在自定义函数中会在正文代码内跳转,在申请内存时又会在堆中跳转,在使用动静态库时会在共享区跳转。综合来看也就只有内核[3,4]GB我们还没有接触过。

我们已经使用过很多次系统调用了,系统调用时函数,那么就会有函数地址,可是我们从没交过函数地址,那么他在哪里呢?


注意:OS是第一个在内存中被加载的软件,另外我们其实还有一份页表是内核级的,专门负责OS的映射。在这里插入图片描述
这也就意味着:OS本身就在我的地址空间中。
但也会有很多个进程同时存在的情况。
用户级的页表每个进程都有一个,但是内核级页表只有一个,他们是按照统一的方式进行映射在这里插入图片描述
这也就意味着,不论进程如何切换,我们总能找到OS,通过访问[3,4]GB的进而找到所有的代码和数据,就可以进行系统调用了!


所以访问系统调用其实是和访问库函数没有区别的,都是在地址空间中进行的。

而OS不会让用户直接进行访问内核的部分,要受到约束,所以要使用OS提供的系统调用进行访问!

键盘的输出如何被检测到:

我们先来看一个简图。
在这里插入图片描述
那么问题出现了,当我们按下键盘时,OS是通过何种方式得到我们的数据呢?
方法一:OS进行轮询–>结果:累死。
方法二:也是那些计算机软件科学家发明出来的。
根据冯诺依曼体系我们知道CPU不会和硬件直接打交道(数据层面),
在这里插入图片描述
但是当我们按下键盘的时候,会向CPU发送硬件中断(控制信号),每个硬件都有自己的中断号(假设键盘为3)。
在这里插入图片描述
CPU有很多针脚,也就是图中的红色凸出部分,我们向特定针脚发送高电平,CPU接受到信号就会将对应的中断号放入到对应的寄存器中,此时就变成软件了!只需要对软件进行操作即可。

那我们现在以软件的角度进行观察,第一个被加载的软件是OS,实际上,OS会首先形成一分函数指针数组,这些函数实际上是OS的一些方法,包括但不限于硬件的读写方法。
在这里插入图片描述
,所谓的中断号其实也就是对应的下标。
从此,当我们进行摁键盘的操作时,CPU会把当前所有的硬件任务停止,把中断号读到并去索引对应的方法下标。

这个表叫做中断向量表,所有的外设都是这样的!

这个程序流程是不是与信号有些相似呢?
实际上历史原因是中断先出现,后来发现进程也是需要类似的操作,于是使用纯软件仿照了中断这一硬件+软件的操作。

OS如何正常运行:

首先我们要明确一个事实:OS是第一个被加载的软件,从电脑开机到关机,他一直运行,本质上是一个死循环。

既然他是一个死循环,那么是如何进行驱动的呢?
我们在键盘那里提到会有一个中断向量表,同时外部硬件可以发送中断给CPU,进而会使用这个中断号去索引中断向量表去执行对应的方法。
同样的,我们也有一个时钟,时钟可能会每隔10纳秒发送一个中断去执行调度方法在这里插入图片描述
调度方法会检测时间片是否到达,未到达就结束,到达就执行切换进程。

所以进程被OS进行驱动,OS被硬件进行驱动!

结论:OS本质是死循环+时钟中断

如何执行系统调用:

系统调用首先肯定与我们库函数和自定义函数有很大的不同,就凭他是在地址空间的[3,4]GB,那我们是怎样去执行系统调用呢?
肯定不是使用函数地址直接访问,这样就不能限制用户访问[3,4]GB了。

所以我们有一张表:函数指针数组表。
下图正是源代码中的表示,我们也可以看到我们使用的fork在用户层叫做fork(),在内核其实还有一个sys_前缀。

在这里插入图片描述
所以我们直接通过特定数组下标即可调用对应系统调用,这个下标叫做系统调用号
那我们怎么调用呢?(问题一)我们系统调用号怎么来?(问题二)

我们在中断向量表中,还有一个方法是 执行任意系统调用(问题一:通过中断向量表进行调用),在这里插入图片描述
那我们中断向量表的下标呢,系统调用号呢?

在CPU中我们需要使用两个寄存器,一个叫做eax,另一个我们暂命名为x(问题二)。在这里插入图片描述
当我们使用fork时

pid_t fork()
{
	mov 2 eax // 将系统调用号放入eax中,我们的系统调用号从fork函数中来
	int 0x80 
}

我们说过CPU可以由外部硬件产生中断,外部产生叫做外部中断,其实CPU内部也可以自己产生的!叫做陷阱或缺陷,就是我们上面代码中的int 0x80,也就是放入寄存器x中的值,我们暂且可以将他理解为中断向量表中的下标,在这里插入图片描述
于是CPU就可以去中断向量表中索引执行系统调用的方法,在拿eax中的系统中断号去索引系统调用表中的下标即可!

注意:不论是内部中断还是外部中断都会陷入内核


回到主线,相信细心的小伙伴已经发现了一个不对劲的地方,我们在执行系统调用中说,不能直接以函数地址去访问对应系统调用方法,是为了限制用户,可是我们的流程中并没有提到如何限制啊。
由此我们引出两个问题
问题一:OS是如何进行限制我们的?
问题二:我们是如何还是调用到的?

说在前边,这是个很复杂的过程,但只会说其中一部分。
我们需要硬件进行配合,这个硬件叫做cs(code segment)寄存器,我们的寄存器有整体使用的,就比如eax,也有按位使用的,比如eflag,我们的cs也是按位使用,他的作用是指向特定代码的起始位置与结束位置,标识一个范围。在这里插入图片描述
只有为指定的状态才可以执行对应的代码,所以我们的主线任务内核态与用户态其实就由这两个数字表示!
那么是什么时候进行修改呢?
我们可以认为在执行在这里插入图片描述
int 0x80之前会有一个动作是进行状态转换的。这个动作是我们用户无法操作的!

结论:用户只能访问[0,3]GB的空间,当执行系统调用时,系统调用内部会进行状态的转换,同时提供了系统调用号,然后int 0x80产生陷阱,执行对应的中断向量表,进而由系统调用号去索引系统调用方法,与PC指针相互配合,最终返回。

硬件上他的表示是由CPU的状态标志位决定的!

回过头来看在这里插入图片描述
当我们由第一步到第二步,先在eax中存入对应的系统中断号,执行int 0x80陷入内核,根据PC指针的指向跳入内核执行系统调用,在做信号检测。

信号的处理:

sigaction:

我们不仅仅signal可以进行捕捉,还有一个sigaction也可以进行捕捉!
在这里插入图片描述
他的功能实际上与signal是很相似的!
signum与signal中的signum含义一致。
对于这个结构体我们来看看:
在这里插入图片描述
用黑线划掉的是与实时信号相关的,我们不用管,其中flags设为0就好。
其中最主要的是要了解sa_mask,但先把sigaction测试完再来。
sa_handler就是我们的自定义函数。
act是一个输入型传输,而oact是输出型参数,修改之前先把当前的结构体拷贝给oact。

测试代码:
在这里插入图片描述
现象:
在这里插入图片描述

信号的特点:

那么说到sa_mask就不得不说信号的特点了。

直接说结论
当信号在被处理,默认被处理信号被屏蔽;
当此信号被处理完成,屏蔽自动解除。

代码验证:
在这里插入图片描述
现象:
在这里插入图片描述
果然如我们所料,是被屏蔽了。

但是其他信号仍旧不会被屏蔽。
可以看到发送3号新号时进程终止。在这里插入图片描述
所以当你的2号屏蔽被屏蔽时,同时也想3号被屏蔽,在sa_mask内进行设置即可~
想屏蔽几个就设置几个。

代码:
在这里插入图片描述
现象:果然2与3号新号都被屏蔽~
在这里插入图片描述
那么我们可以把所有的信号都addset到mask中吗?
操作上可以,但是最终结果是无法屏蔽一些信号的。
否则你不就成了金刚不坏的进程了吗?
例如9号。

基于信号的理解讨论3个子问题:

可重入函数:

听着是一个很高大上的函数。
具体是怎么一回事呢?
在这里插入图片描述
我们以上图的链表头插举例,可以看到头插是分为两步代码的。
当我们执行到第二句时突然来了一个信号,去执行自定义函数.
等等,信号的捕捉去执行自定义函数不是需要从内核态转移到用户态吗,可是我们一直处于用户态,都未曾进入过啊。

其实不然,我们提到过有一个外设时钟,他会每隔几纳秒就发一次中断,进而执行对应的调度方法,但是我们的程序并不知道什么时候会产生这个中断,所以在程序的任意一行都有可能产生,当外部中断产生就会陷入内核,执行完自己的方法后检测信号,进行信号处理,所以结论就是在程序的任意一行都可能会发生从内核态转移到用户态进而递达。

可是这样就导致了我们上图中节点的丢失,造成内存泄露。

我们将这种现象称为insert函数被重入了,叫做不可重入函数,我们学的大部分函数都是不可冲入函数,基本上涉及到全局的数据结构之类的一般都属于不可重入函数。

volatile:

这是C语言中一个冷门的关键字,在只有C语言的基础上比较难以理解这个关键字,但是信号可以比较好的帮助我们进行理解。

先来看这样一段代码:
在这里插入图片描述
现象:
在这里插入图片描述
这些对于我们都是很容易理解的代码。

但是编译器是会优化的!

我们先看未优化的,每次执行while(!gflag)都会执行一下逻辑
在这里插入图片描述

编译器发现在main函数中没有任何代码对gflag做修改,所以可能会对程序进行优化。
在寄存器中的值一直是第一次从内存中获取的值,不在每次都去内存中获取。
在这里插入图片描述
那我们如何验证?
g++进行编译时是有等级划分的。在这里插入图片描述

默认的优化等级是-O 0,也就是没有优化。
那我们进行优化一下。
可以看到仅仅只是提升到了O1就不可以了。
在这里插入图片描述
这种现象叫做寄存器隐藏了内存的真实值,我们的代码将gflag修改了吗,在内存中修改了,但是寄存器不从内存中获取了。

如何进行解决?
那就是volatile关键字啦~
声明时加上在这里插入图片描述
现象:变正常~
在这里插入图片描述

SIGCHLD:

我们回顾一下进程退出的问题。
在以前我们的父进程都会对子进程进行等待,否则就僵尸进而造成内存泄漏。

这是不是就代表我们的进程是悄悄的退出的啊?
毕竟如果不是悄悄的退出,那我们的父进程为何还要进行阻塞或者非阻塞轮询呢?
但实际上并不是,我们的子进程退出并不是悄悄的退出,会给父进程发一个SIGCHLD的信号。只不过这个信号的默认处理是忽略罢了。

在这里插入图片描述
现象:父进程果然收到了SIGCHLD信号。
在这里插入图片描述


所以以后我们就可以解放父进程了,在handler中进行等待即可(将等待的子进程pid设置为-1即可),顺便取消了强耦合。

代码:增加了一个fatherRun,同时在handler进行等待。

在这里插入图片描述

现象:
在这里插入图片描述

那既然如此,我们就对这个程序找找茬

如果我们有10个进程同时要进行退出,我们的程序可能会同时收到10个SIGCHLD信号,但是pending位图只能记录一个,那我们怎么进行等待?

将handler中的等待设置为死循环即可。

我们进行程序验证:
在这里插入图片描述
现象:一共等待了10次,并且都成功了,最后父进程仍然doOtherThing。
在这里插入图片描述

那么如果我们有10个进程,5个不退出呢?
将handler中的wait进行一下修改,需要将阻塞改为WNOHANG即可,这样就不会阻塞了。
在这里插入图片描述
此时还有最后一个知识点:
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。

我们进行等待的目的是为了
释放空间和获得退出信息。
当你不需要获得退出信息时就可以采取这种方法。
但是还有一个疑问,SIGCHLD的默认处理动作已经是忽略了啊,再次设置为忽略为什么就可以等待了呢?
是因为这两个忽略不一样,一个是用户级一个是内核级!

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

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

相关文章

QML学习十九:ttf字体库使用

一、前言 在使用QML时,常常自定义按钮,按钮上有显示个图标,其实,那不是图标,是文本,如何显示? 本篇记录,如何导入阿里巴巴字体库,并调用显示。 二、阿里巴巴字体库下载…

分布式系统设计指南

目录 一、分布式简介 二、分布式系统核心概念 2.1 CAP 理论 2.2 BASE 原理 三、分布式系统设计 3.1 微服务拆分 3.2 通信模型 3.3 负载均衡 3.4 数据一致性 3.5 容错限流 3.6 扩展性 3.7 监控预警 3.8 自动化运维 一、分布式简介 分布式系统是由单体应用发展而来的&#xff…

统计绘图 | 既能统计分析又能可视化绘制的技能

在典型的探索性数据分析工作流程中,数据可视化和统计建模是两个不同的阶段,而我们也希望能够在最终的可视化结果中将相关统计指标呈现出来,如何让将两种有效结合,使得数据探索更加简单快捷呢?今天这篇推文就告诉你如何…

使用 Scapy 库编写 TCP 窗口大小探测攻击脚本

一、介绍 1.1 概述 TCP窗口大小探测攻击是一种信息收集攻击,攻击者通过向目标服务器发送特制的TCP数据包,探测目标服务器的TCP接收窗口大小(TCP Window Size)。了解目标服务器的TCP接收窗口大小,可以帮助攻击者优化后…

Spring Web MVC之过滤器Filter和拦截器HandlerInterceptor的区别和用法

作用时机不一样 Spring 框架有一个很重要的类DispatcherServlet。这个类继承了HttpServlet,HttpServlet实现了Servlet接口。相当于图片中的Servlet。所有和Spring框架相关配置,例如注解、xml配置、其他数据库连接配置、bean配置、拦截器配置等其他配置&…

深度学习研究生的职业前景:未来趋势与机遇

deep learning 深度学习研究生的职业前景:未来趋势与机遇一、深度学习的应用领域1. 计算机视觉2. 自然语言处理(NLP)3. 数据分析4. 游戏开发5. 健康医疗 二、职业机遇与挑战1. 工作机会2. 竞争与挑战3. 薪资前景 三、职业发展策略对于深度学习…

国外创意二维码应用:飞利浦旧物翻新活动,传播可持续性消费的重要性!

你知道去年有超过1000万件礼物被扔进了垃圾场吗? 这些被丢弃的物品中有许多仍在使用,飞利浦希望改变这种浪费现象。 去年的地球日,飞利浦策划了一场名为“Better than New” 的二维码营销活动。他们发布了一个视频,通过这个短视频将所有最终…

钉钉魔点指纹考勤机多少钱一台,指纹门禁考勤一体机价格

钉钉魔点指纹考勤机一台多少钱呢,指纹门禁考勤一体机的价格又是多少 钉钉魔点 X2 智能指纹考勤门禁一体机的参考价格是 359 元。 其具体参数情况如下: 产品类型:属于指纹考勤门禁一体机; 验证方式:为电容指纹&…

4、优化阶段

优化概述 编译程序总框架: 优化:对程序进行各种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。 等价:不改变程序的运行结果。 有效:目标代码运行时间短,占用存储空间小。 >目的 产生更高效的代码 >遵循的原则 …

618值得购买的东西有哪些?618四款必囤好物清单分享!

随着618购物狂欢节的脚步日益临近,身为数码领域的资深爱好者,我深感有必要为大家推荐一系列经过精心挑选的数码产品精选。无论是热衷于科技前沿的探索者,还是希望通过智能设备提升生活品质的时尚达人,本文所介绍的每一款数码产品都…

MT2096 数列分段

代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e5 10; int n, m; int a[N]; int ans 1; int main() {cin >> n >> m;for (int i 1; i < n; i)cin >> a[i];int num 0;for (int i 1; i < n; i){if (num a[i…

(1)图像识别yolov5—安装教程

目录 1、安装YOLOv5: 2、下载预训练模型: 3、识别示例图片: 1、安装YOLOv5: 首先,你需要在你的计算机上下载 YOLOv5 的文件包,下载链接:https://github.com/ultralytics/yolov5。下载后对压缩文件进行解压。 通常使用 YOLOv5 识别物体,需要安装必要的 依赖…

DataX(DataX简介、部署、同步数据)

DataX&#xff08;DataX简介、部署、同步数据&#xff09; ☀快乐无限 法力无边 目录 DataX&#xff08;DataX简介、部署、同步数据&#xff09; 1.DataX简介 1&#xff09;数据采集模块&#xff1a; 2&#xff09;数据写入模块&#xff1a; 2.DataX部署 1&#xff09;Da…

《AI企业级知识库》-rasa爆改中文版本-实战!

阿丹&#xff1a; 之前有同学反应分享的东西有点概念化&#xff0c;表示不看着代码无法更深刻能理解。那么今天直接上代码&#xff01;&#xff01;&#xff01; 有两种方式使用自己训练好的nlu 1、rasa与nul分开启动&#xff0c;就是在rasa中的配置中配置好目标对应的nlu的服…

语法04 C++ 标准输入语句

标准输入 使用格式&#xff1a;cin >> 输入的意思就是把一个值放到变量里面去&#xff0c;也就是变量的赋值&#xff0c;这个值是由我们自己输入的。 (注意:输入变量前要先定义&#xff0c;输入完之后要按Enter键。) 输入多个变量&#xff0c;与输出类似&#xff0c;…

全球知名哲学家思想家教育家颜廷利:清明节的教育意义

在21世纪全球公认十大思想家的行列中&#xff0c;颜廷利大师以其独到的见解和深刻的哲学思考而备受推崇。随着清明节的临近&#xff0c;人们纷纷前往先人的墓地进行祭奠&#xff0c;其中烧纸钱是一项重要仪式。然而&#xff0c;亚洲十大顶级杰出人物、当代易学泰斗三大人物颜廷…

Python 围棋游戏【含Python源码 MX_008期】

简介&#xff1a; 围棋&#xff0c;源自中国&#xff0c;是一种两人对弈的策略棋类游戏。它被认为是世界上最复杂的棋类游戏之一&#xff0c;因为它的规则简单&#xff0c;但变化复杂多样。围棋的游戏目标是在棋盘上占领更多的地盘&#xff0c;并用自己的棋子围住对手的棋子&am…

禁用PS/Photoshop等一系列Adobe旗下软件联网外传用户数据操作

方案一&#xff1a; 下载火绒杀毒&#xff0c;在联网请求上禁用Adobe软件的联网请求&#xff0c;甚至还可以额外发现哪些是它要想要偷偷摸摸干的。 方案二&#xff1a; 最后注意&#xff1a; 用盗版软件只是获得了使用权&#xff01;

Mcgs 屏幕Modbus RTU通讯调试

目录 1. 设备窗口1.1 添加设备构件1.2 设备配置1.2.1 通用串口父设备配置1.2.2 设备0--ModbusRTU配置2. 设计用户窗口2.1 关联设备通道与实时数据库2.3 用户窗口3. 通信测试本文想要实现通过Modbus协议与Mcgs屏幕进行通信收发数据。在使用Mcgs屏幕进行Modbus通信时,一般Mcgs屏…

个股期权103call是什么意思?

个股期权103call是什么意思&#xff1f; 在金融市场中&#xff0c;个股期权作为一种金融衍生工具&#xff0c;为投资者提供了多样化的投资策略。其中&#xff0c;“103call”这一术语&#xff0c;特指一种特定的期权交易策略&#xff0c;它涉及到看涨期权与虚值状态。 文章来…