【Linux】信号(一)

信号我们将从信号产生,信号的保存,信号处理分别进行讲解~

至少大思路是这样。开始之前还要进行一些基础知识的铺垫。

从生活中提炼一些结论:

a. 信号在生活中随处可以产生------比如红绿灯,天空打雷…
b. 我可以识别识别这些信号-----虽然我可能在课堂中,但是并不妨碍我认识
c. 当信号产生了,该如何进行处理------比如我知道红灯亮了不能走,绿灯亮了可以走
d. 我在做别的更重要的事,对到来的信号暂时不做处理

我上段阶段中的“我”换为“进程”,“生活”换为“OS”就是我们的信号了!

但是仅仅有了上边的概念还不够,还要进行一些补充
对于a,信号的产生与进程是异步的
对于b,c说明进程可以识别并处理信号
对于d,其一:进程需要记住这个事。其二:进程需要再合适的时候在去处理。

信号概念的一些储备:

这里会涉及到我们刚刚提到的信号产生,信号保存,信号处理,先分别涉及一点点,在详细的进行解释。

异步:
那么怎样理解异步?异步就像是在课堂上,老师让一个学生去拿快递,如果老师在等待那个学生过来就是同步,不等待直接进行讲课就是异步。
你拿你的快递,我将我的课,互不干扰

信号:
是linux提供的一种,向指定进程发送发送特定实践的方式。


我们基于以上的两点先来看一看!到底信号是个啥?
使用kill -l命令即可看到linux中的全部信号。
1到31号是普通信号,超过31是实时信号,我们不进行考虑
在这里插入图片描述我们写个如下程序,并对其发送9号信号(相信我们目前都用过的一个信号,相对来说也是最熟悉的)在这里插入图片描述
发现果然被kill了
在这里插入图片描述
上图其实就是一个进程对信号进行处理的一个例子,我们就以这个为切入点进行信号处理的渗透。

信号处理常用的有3种方式
a. 默认动作
b. 忽略动作
c. 自定义动作

对于a:进程处理信号都是默认的,通常包括终止,暂停,忽略…
下图中框起来的就是默认动作,Core与Term其实都代表终止,但具体的区别我们等等在谈。
在这里插入图片描述

对于b:忽略就是忽略的意思

对于c:那么就不得不提我们的signal函数了
在这里插入图片描述
这是一个对信号进行捕捉的系统调用,捕捉后收到该信号就会去执行自定义的操作。

我们来用代码具体实践一下
在这里插入图片描述
执行指令
在这里插入图片描述
现象:没有执行默认动作,而是执行我的自定义动作,并且ctrl + c也终止不了!
在这里插入图片描述
结论
我们handler函数中的参数其实就是发送的信号,发送2号信号就是ctrl + c, ctrl + c就是向进程发送2号新号!

那么此刻我想问几个问题:
1 如果没有产生信号呢
2 我们捕捉更多的信号呢

对于问题一:没有产生新的信号就代表是正常运行
对于问题二:我们进行捕捉多个信号,进行测试。

对2 3 4进行测试,仍然是和我们预期一致:收到信号会执行我们的自定义函数,但真的是这样吗?对于这点我们待会有验证。

其实此时我们就可以总结出两个信号产生的方式

  1. kill
  2. 组合键

我们如何理解信号的发送与保存?
这里只是浅度的认识一下,毕竟我们也说了这只是一些对产生,保存,处理的一些预备知识。

先来看保存:
详细细心的小伙伴以经发现我们的信号是从1开始,31结束,没有0。为什么呢?

这里就到回到进程了,进程是task_struct结构体,结构体中有成员变量,在这些变量中有一个uint32_t 的类型变量(名字不准确却能反映出关键)
这个无符号整形有0000 0000 0000 0000 0000 0000 0000 000032个零。
我们使用位图对这32位比特位进行利用!

当发送信号1时就将1号比特位置为1,
0000 0000 0000 0000 0000 0000 0000 0010
发送2时就将2号比特位置为1
0000 0000 0000 0000 0000 0000 0000 0100
以此类推。

发送:
我们修改指定的PCB中的信号位图即可!
将0置为1,所以发信号貌似叫做写信号更合适。

但是这里有个要注意的点,OS是软硬件资源的管理者,PCB是一个内核数据结构,理所应当的只有OS有权利进行写入,所以OS才有资格进行修改。

信号产生:

这里在强调一下,我们现在刚刚结束预备知识的部分。
在这里插入图片描述

我们已经得出了两点结论。

一、kill指令:


二、键盘组合键:

ctrl+c。
ctrl+c是我们最常用的,但不是唯一一个组合键,还有一个ctrl+\,也是让进程终止的组合键。


三、系统调用:

说到系统调用那必然要提到一个接口在这里插入图片描述
其实看到这个命令我们大概也能想到kill指令其实也就是由这个调用来的。
那我们来模拟一下kill指令。
在这里插入图片描述

进行测试,果然如此。
在这里插入图片描述


再来看另外一个系统调用raise。
在这里插入图片描述
本质上是对kill的封装,对调用此函数的进程发送你指定的信号。
在这里插入图片描述
现象:被捕捉后就去执行我们的自定义代码随后死循环->和我们预期一样。
在这里插入图片描述


再来看一个系统调用:
abort:
在这里插入图片描述
与raise是一样的,不过这个是指定发送6号信号。
在这里插入图片描述
但是要注意一点:虽然允许捕捉,但仍然会终止,是我们常用的3种处理方式例外。

现在我们有两个问题:
一:把信号全部捕捉会怎样?
二:如何理解发送?

对于1:我打个for循环全部捕捉即可~
在这里插入图片描述
当我们尝试9时就会发现虽然在代码中捕捉了,但是实际上是不允许的,不然如果你真的全捕捉那岂不是反了天了??哈哈。
但实际上除了9还有别的指令也和9一样,不允许捕捉。

对于二:只是为了再次强调,是OS进行发送信号,修改PCB中的位图。


四、软件条件:

一个很抽象的名词。

我们来举一个例子。
在管道阶段我们知道当读端关闭,写端继续写入就会被OS发送SIGPIPE信号。
那么和软件条件有什么关系的?
在这里插入图片描述
管道的产生条件是与struct file内核缓冲区等具有非常紧密的关系的,他们都是软件,当当读端关闭,写端继续写入就会不满足软件条件,最终导致13号信号的发送。

这时候我们就有需要认识一个新的函数了
在这里插入图片描述
闹钟函数。
我们先来看一看这函数
a54660e44698f3f445c80838cc7.png)

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

这实际上是14号新号。
在这里插入图片描述
现象:果然收到了14号新号
在这里插入图片描述
现在有了以上的基础我们要谈论3个子问题
a. 理解一下IO成本
b. 理解alarm
c. alarm的返回值

对于a,我们其实很好验证,
在这里插入图片描述
稍微修改一下代码即可观察到现象
在这里插入图片描述
11w到9亿,足以看到IO的速度是非常慢的。

对于b,
在理解之前我们要先说另一个话题,我们肯定经历过手机断电还几天或者电脑,但是开机之后仍然可以保持标准的时间,这是由于我们的电子设备内置了一个纽扣电池,帮助我们计时。
而闹钟可以准时提醒我们是根据一个时间戳的东西。

我们理解一个事物往往需要一个切入角度:而这个角度往往都是先描述在组织,我们有那么多进程,每个进程都可能有一个闹钟,那么就需要先描述则个闹钟。
在这里插入图片描述
这个结构体内有各种各样的描述闹钟的变量。

那我们选择什么进行组织呢?
当我们要寻找一个没有超时的闹钟时,只要找到最后一个超时的后一个就可以了
我们可以采用有序链表,但这样太慢了,所以我们最终选择堆!每次pop时就是当前的最小堆,观察是否超时即可!

对于3:我们在设一个闹钟进行观察。
在这里插入图片描述

在这里插入图片描述
当把sleep改为4时
在这里插入图片描述
由此我们可以得出结论:返回值是闹钟剩余的时间。

而我们alarm(0)本质上就是取消闹钟,因为它实际上没有什么意义。

那我们设置一个alarm(2),也是取消上一个闹钟,在重新设置一个。

注意:闹钟默认是触发一次的,
在这里插入图片描述
虽然设置了多个但是只会触发一个,因为每次设置都是对上一次的闹钟的取消。
在这里插入图片描述
从这里我们也可以得出一个结论:闹钟是一种很简单的组织形式,是不可以设置多个闹钟的,如果设置多个闹钟,那么如何制定取消呢?

那我们如何设置一个闹钟一直触发?
答:在捕捉函数里在设置一个即可,也叫做常设闹钟。

总结:
闹钟 = 结构体 + 数据结构,是软件,也是属于软件条件范畴的!

五、异常:

关于异常我们一定遇到过,真的是太经典了…
我们来看两个。

当程序中发生除0
在这里插入图片描述
当程序中发生对空指针解引用
在这里插入图片描述


我们先来谈论除0错误

这里我们要引出3个问题:

为什么除0会崩溃呢?
是因为非法访问导致OS发送了一个信号,也就是SIGFPE信号,那么OS发送的内部机制是什么?(第一个问题)。

我们来进行捕捉验证一下。

捕捉代码:
在这里插入图片描述
现象:在疯狂打印?直到我们进行ctrl+c终止掉进程。
在这里插入图片描述

这又是为什么?(问题二

对于这个程序我们可以不退出吗?
可以,可以通过捕捉,但是不推荐。
推荐直接终止,为什么推荐终止?(问题三

想要解决以上三个问题,我们就需要了解一下硬件了!
在这里插入图片描述
也就顺便解释清楚了发送信号进行终止的机制(问题一

那么为什么捕捉了之后会重复打印呢?
我们就要谈一下进程切换の话题了。

虽然我们的CPU中只有一套寄存器,但是存储的数据有多套,有几个进程就有几套数据。

此时就需要涉及到硬件上下文的切换,当当前进程的时间片到了就会把此时寄存器的数据放入对应的PCB中,等到重新运行这个进程在放入寄存器,这就是上下文的切换。

那到底为什么会疯狂打印?
我们进行捕捉异常信号,进程没有退出,这是正常的,所以这个进程就需要进行切换,进而被继续调度,在这个过程中会将CPU中的寄存器的值进行保存和回复,因为不退出,我们又给恢复了错误的数据,所以OS只能再次发信号…(问题二
注意:只要OS看到硬件异常就会直接进行发送对应的错误信号,不会在继续执行代码了。

为什么推荐终止?这个进程的上下文已经是错误的了,是没有意义的,我们肯定是推荐释放没有意义的资源的!(问题三


我们再来看除0错误

页表我们都知道是进行映射关系的,但是不是单纯的使用某种数据结构,例如数组…进行在页表中查找映射关系(这样太慢了),
而是使用了一个硬件电路MMU进行转化(将来你把虚拟地址给他他可以直接转化为物理地址)。
这就叫做页表+MMU(内存管理模块)的做法。同时他是被集成在CPU内部的。

同时,我们还有CR0CR4的寄存器。这里我们终点关注CR3与CR2,
3里存放的是页表初始地址,所以有一个虚拟地址,利用CR3+MMU在来一个页表转化。就可以得到对应的物理地址。

计算会出错误,那么转化肯定也会出现错误。
2(页故障线性地址寄存器)就是一个表示故障的寄存器,当你的虚拟地址是错误的,就会将这个地址放入2中,代表转化出错,也就是硬件错误,OS又是软硬件的管理者就要发送SIGSEGV信号。

具体的流程和除零错误一致。

异常总结:程序中所有的异常都体现在硬件上,OS是硬件管理者,所以理所当然的要进行处理~

大总结:

都是由OS进行发送信号!本质是对位图的修改。

core与term的区别:

本质上都是终止进程,但是以core终止的某些信号会生成一个文件(调试文件)。
core文件本质是出错瞬间进程上下文的镜像数据,我们进行gdb时可以帮助我们快速定位!


我们开始验证:
在这里插入图片描述

我们发送3信号,为什么没有生成core文件?
在这里插入图片描述
是因为没有打开这个功能,这个功能在云服务器默认关闭的。
在这里插入图片描述
按照指示即可打开,我们这里选择10240大小。
在这里插入图片描述
在进行运行观察是否生成。
在这里插入图片描述
果然生成了一个core文件!
但是由于博主的云服务器内核版本比较高,所以不会生成出core文件。

但是还是要说一下为什么云服务器默认要关闭呢?因为生成的core文件会带有pid后缀,如果是一个大项目,在夜晚出故障,疯狂的被core并生成core.pid文件,那么就会导致云服务器没有足够的磁盘空间而挂掉,那么这个情况就很严峻了。
但这也只是其中一个比较主要原因。更何况.pid是可以进行配置进而取消这个设置的。但出于安全性,效率等原因也还是关闭了。

现在我们要解决一个历史问题:
在这里插入图片描述
我们之前说进程退出会3中情况:1. 正常退出结果正确 2. 正常退出结果错误 3. 被终止退出(被信号所杀)。
在第三种情况下有一个core dump标志位,代表是否生成了core文件。我们可以验证一下。

我们使用以下代码进行验证:
在这里插入图片描述

现象:core dump果然为1,接收到的信号也同样是8号(SIGFPE错误在这里插入图片描述
我们在调试时,进行file-core操作即可快速定位到出错位置。

虽然因为某种配置原因博主没有生成core文件,但还是大概说了一下他的大概流程与作用。

信号1完结~

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值