信号的理解

1 .背景认识:

生活中有没有信号的场景呢?
闹钟,红绿灯,烽火,老师的脸色… ->都是给人看的
当我们听到场景触发,我们早就知道了,甚至远远早于信号的产生。我们对特定实践的反应。,是被教育的结果->本质是:记住了!

信号的产生 ->信号是给进程发的 ->进程要在合适的时候,执行相应的动作
进程具有识别信号以及处理信号的能力,远远早于信号的产生
在这里插入图片描述
进程收到某种信号的时候,并不是立即处理的,而是在合适的时候
信号随时都可能产生(异步),但是我当前可能做着更重要的事情
既然这样,进程收到信号后,应当保存下来
进程收到信号之后,需要先将信号保存下起来,以供在合适的时候处理
需要保存在哪里呢?一个task_struct结构体中
信号的本质:数据!
信号的发送->往task_struct内写入信号数据
test_struct是个内核数据结构,内核不相信别人,只相信自己
无论我们的信号什么时候发送,本质都是在底层通过OS发送的

2 .信号处理的常见三种方案

在键盘中ctrl C的本质实际上是在向我们的进程发送2号信号
signal捕捉信号,这把的handler可以随便写函数名,返回值是void
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
信号的捕捉
信号的产生方式其中一种就是通过键盘产生
键盘产生的信号只能用来终止前台进程

在这里插入图片描述
在这里插入图片描述
后台运行test,然后使用kill -9 命令可以将其杀死
在这里插入图片描述
总结:
一般而言进程收到信号的处理方案有三种情况
1.默认动作 – 一部分是终止自己,暂停等
2.忽略动作 – 是一种信号处理的方式,只不过动作是什么也不干
3.(信号的捕捉)自定义动作 --我们刚刚用signal方法,就是修改信号的默认动作 ->自定义动作

这边是自定义信号的捕捉,handler函数中可以对不同信号设置不同的处理方法
在这里插入图片描述

在这里插入图片描述
9号信号不可以被捕捉。为什么呢?(后面来讲)

3.阶段一:信号发送前

信号产生的方式都有哪些呢?
1 .键盘
2 .进程异常,也能产生信号
3 .通过系统调用接口产生
4 .软件条件,产生信号

方式①:键盘发送信号

快捷键ctrl+c ctrl+z 等直接发送

方式②:进程异常产生信号

segmentation fault 在linux下指段错误,一般是有越界/野指针的情况
——>进程的崩溃(语言层面)

那么进程为什么会崩溃呢?
在win或linux下,进程崩溃的本质,是进程收到了对应的信号,然后执行进程信号的默认处理动作(杀死进程)

在这里插入图片描述
为什么会收到信号了呢?
我们在出现浮点数错误(a/=0)段错误(野指针访问)的时候,会体现在硬件或者其他软件上,而OS是硬件的管理者,维护着硬件的健康(计算错误等,而不是硬件损坏之类),当发生错误时候,硬件就会报错,OS就会向你的进程发送信号走终止进程

我们现在所出的异常都是OS向内存发信号导致的
语言层的捕捉异常其实就是在处理信号
2 .信号产生的方式,程序中存在异常问题,导致我们收到信号退出在这里插入图片描述
可以判断崩溃时收到的是什么信号
还想知道在哪一行崩溃了?
在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常信号)
当一个进程异常的时候,进程的退出信号会被设置,表明当前进程退出的原因
如果必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转储到磁盘当中,方便我们后期调试

在这里插入图片描述
在云服务器上,core dump技术是被关掉的
在这里插入图片描述
手动设置
在这里插入图片描述
我们设置一个除0错误
在这里插入图片描述
在开启core dump在发送信后以后会有一个(core dumped)
在这里插入图片描述
这种方案我们叫做事后调试
在这里插入图片描述
在这里插入图片描述
进程如果异常的话, 会被core dump
core dump位置会变成1
在这里插入图片描述
在这里插入图片描述

方式③:通过系统调用接口产生

kill函数
main()中的命令行参数

写一个发自己信号的程序
kill程序 想要发什么信号 给谁发
用自己写的kill命令杀死一个进程
在这里插入图片描述
在这里插入图片描述
所以第三中产生信号的方法·:系统调用
自调函数raise
abort可以给自己发六号信号
在这里插入图片描述
在这里插入图片描述

方法④ :软件条件,产生信号

通过某种软件(OS),来出发信号的发送,系统表面设置定时器,或者某种操作而导致条件不就绪等这样的情况下,触发的信号发送

在进行进程间通信时:当写端不光不读,而且还关闭了读fd,写端一直在写,最终写进程会受到sigpipe(13),就是一种典型的软件条件出发的信号发送

alarm可以延迟收到14号信号
alarm(0)取消闹钟
在这里插入图片描述
在这里插入图片描述
小程序统计1s ++的次数
在这里插入图片描述
在这里插入图片描述
为什么下面的快这么多?因为print是IO函数,不写print可以跟cpu直接交互
在这里插入图片描述
在这里插入图片描述
信号产生的方式种类虽然非常多,但是无论产生信号的信号方式前的千差万别,但是最终一定是OS给目标进程发送的信号
总结:产生信号的方式,本质都是操作系统发送的

如何理解OS给进程发送信号

再没学习理论之前,我们的理解是:
OS发送信号数据给 task_struct
进程中使用位图来表示是否收到了信号
所以现在我们的理解是
本质是OS向指定进程的task_struct中的信号位图写入比特位1,完成信号的发送
->信号的写入
在这里插入图片描述

4 阶段二:信号发送中

术语了解

①实际执行信号的处理动作称为信号递达
{ 自定义捕捉,默认,忽略 }

②信号从产生到递达之间的状态,叫做信号未决(Pending)
{本质是这个信号被暂存在task_struct 信号位图中,未决}

③进程可以选择阻塞某个信号
{
本质是OS,允许进程暂时屏蔽指定的信号
1 .该信号依旧是未决的
2 .该信号不会被递达,直到接触阻塞,方可抵达
}
阻塞的例子:
小学生中有个课代表,他有一本小本本记录不听话讲话的学生,每天晚上交给班主任,班主任对这些学生进行相应处理
小学生就代表一个进程,这里的小本本就是位图(对应下面的block位图),班主任就是操作系统
有一个学生请求课代表不要告诉老师,这里的请求就是一种阻塞行为

递达-忽略 VS 阻塞 两个是一个东西吗?
当然不是。忽略是递达的一种方式,阻塞是没有被抵达,一种独立状态

信号的三张表结构

处理信号的本质就是修改pending位图,处理信号的时候就实行对应的handlier方法
block表:本质上,也是位图结构
uint32_t block:
比特位的位置,代表信号的编号
比特位的内容,代表信号 是否 被阻塞
阻塞信号还有个名字叫 信号屏蔽字

进程是可以识别信号的
在这里插入图片描述
在这里插入图片描述
一个信号任何时候都可以pending,但抵达与否主要看是否block

三张表的这个类型是sigset_t,是一个位图结构

有效信号集

在这里插入图片描述

sigpromask

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

sigpending

在这里插入图片描述

2号信号被屏蔽掉,不能被递达,就一直保留在pending位图中
在这里插入图片描述

在这里插入图片描述
多加信号恢复过程
在这里插入图片描述在这里插入图片描述
为什么没有看到过程2的1变成0?
因为2的默认动作是终止进程,所以看不到现象
我们可以对二号信号进行自定义功能
在这里插入图片描述
在这里插入图片描述

5. 阶段三:信号发送后

当我们收到一个信号的时候,我们不是立即处理的,而是在合适的时候:信号的产生是异步的,当前进程可能在处理更重要的事情

信号延时处理(取决于OS和进程)

感性认识

我们要解决
什么是合适的时候,信号什么时候被处理

因为信号是 被保存在进程的PCB中,pending位图里的,处理(检测,递达(默认,忽略,自定义))
当进程从 内核态 返回到 用户态 的时候,进行上面的处理

内核态: 执行OS的代码和数据时,计算机所处的状态就叫做内核态。OS的代码的执行都是在内核态中的

用户态:就是用户的数据和代码被访问的时候,所处的状态。我们自己所写的代码,都是用用户态中执行的

内核态和用户态的主要区别:在于权限

在这里插入图片描述

较为理性认识

用户的代码和数据是一定要加载到内存
OS的代码和数据也是一定要加载到内存中的!
OS的代码是怎么执行的呢?只有一个CPU!
内核的页表是被所有的进程共享的

CPU内部有一个CR3寄存器是0的时候代表内核态,是3是用户态

CPU内的寄存器保存了进程的状态
->用户态使用的是用户级页表,只能访问用户代码和数据
->内核态使用的是内核级页表,只能访问内核级的数据和代码
在这里插入图片描述
所谓的系统调用:就是进程的身份转化为内核,然后根据内核页表找到系统函数,执行就可以了
在大部分情况下,实际上我们OS都是可以在进程的上下文中直接运行的

一个小朋友现实中保护好自己的同时,也不要拿别人的东西

在这里插入图片描述

高度抽象图
在这里插入图片描述

信号整个生命周期

在这里插入图片描述

sigaction

在作用上和signal是一模一样的,也是用注册一个信号对特定信号的处理动作
在这里插入图片描述
在这里插入图片描述
信号集中加入三号信号,两个都没有效果
在这里插入图片描述

6 .信号的整体周期

在这里插入图片描述
各种语言的异常处理实际上就是发信号

7 .关键字volatile

在这里插入图片描述

在GCC中,优化级别是O0~O4
编译器在优化后会对同一份代码产生不同结果
在这里插入图片描述
加上volatile关键词后
在这里插入图片描述
通过现象看本质

常规情况下要将内存中的flag读入CPU的寄存器中,再进行判断

  • 优化情况下直接从CPU读,这叫做优化到cpu中

  • 不加volatile,修改的是内存中的flag,但读的是直接从CPU中读的,就会产生一种内存屏蔽的现象

  • 加上volatile,作用:告诉编译器,不要对我这个变量做任何优化,读取必须要贯穿式的读取内存,不要读取中间寄存器的软冲去寄存器中的数据
    在这里插入图片描述

8 .可重入函数

一个函数没有返回但又被进入了,就可以说一个函数被重复进入了
在这里插入图片描述

  • insert函数一旦重入,有可能出现问题 ->该函数不能重入
  • inset函数一旦重入,不会出现问题 -> 该函数:可重入函数
  • 我们所学的大多数函数,STL,boost库中的函数,大部分都是不可重入的
  • 不可重入函数是一种普遍现象,没有褒贬之说

9 .SIGCHID

waitpid时子进程会等待,会形成僵尸进程,不要等待,直接SIG_IGN进行忽略,然后退出
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值