信号收尾.

sigaction 信号捕捉

它也是信号捕捉,不仅能处理普通信号还能处理实时信号,但我们不管实时信号
在这里插入图片描述
我们发现函数名和形参中结构体名一样都是sigaction,这在c/c++中允许吗?
不建议,但是可以

signo你要捕捉几号信号
输入型参数act结构体里面包含了你要设置新的动作,输出型参数oact是保存老的设置以便你后续恢复
看看sigaction结构体里面,红线是我们不关心的,里面有个sa_handler不就是之前信号捕捉的函数指针类型吗,也就是处理方法,其实也就是修改了handler表里面的方法
在这里插入图片描述
这个结构体红线你都不关心,那直接用memset都清零就可以

问题
1、结构体中sa_mask什么鬼?

2、如果要处理一个信号了,进程的pending位图里面对应信号是1,这个1是在信号处理前清零还是信号处理后清零?
你都能捕捉了,你在捕捉里面打印看看Pending表就知道了,如果信号处理方法中打印出来2号信号是0那么说明在信号处理前pending中信号就已经被清零了
在这里插入图片描述

在这里插入图片描述
所以结论:pending位图,什么时候从1->0. 执行信号捕捉方法之前,先清0,在调用

一个事实:当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来
的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止
也就说不允许同一个信号不断向我的进程发,让我进程不断忙于各种信号处理

如果没有这个事实的话
在信号捕捉方法中,也就是自定义动作函数中:
1.函数中可以陷入内核,因为函数中会被调度 或者 系统调用,而且这很正常我调个printf访问硬件你不给我系统调用吗
2.如果再来一个2号信号,这是可以的因为你处理前就把pending表2号由1变0,所以正在处理2号信号还能再接收2号信号
所以此时很尴尬我们正在对2号进行捕捉,又来一个2号信号,又陷入方法函数中继续再进行捕捉
相当于对handler方法不断的重复调用,如果一直这么调用那连信号捕捉我还返回不了了!
OS不允许你这么干。

那怎么证明信号捕捉时,2号信号确实是被屏蔽了呢?
我们让捕捉函数中死循环打印pending表,一直处理2号信号,此时我们在发送一次2号信号,如果被屏蔽就会看到2号信号由0置1
在这里插入图片描述
证明完成
所以OS不允许对同一种信号进行不断嵌套式的捕捉,它的捕捉层数只能是一层,任何一个信号都是如此

所以struct sigaction结构体中sa_mask是干什么的呢?
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需
要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
在这里插入图片描述

正在处理2号信号期间只有2号会被自动屏蔽。
如果我还想屏蔽更多信号呢?? ?那就利用这个sa_mask设置
在这里插入图片描述

在这里插入图片描述

最后说一下
所以处理一个信号期间,如果我再收到10个同样的信号,最终这个信号只会被记录一次,因为只有一个pending位图哦
在这里插入图片描述

在这里插入图片描述


可重入函数

在这里插入图片描述

图中有全局的链表,main函数中进行头插node1,头插Insert函数执行完1.p->next = head这句代码,突然来了信号你就去执行信号捕捉了。4.head = p 就没执行
先说可能性,我这里1和4没有所谓的系统调用啊,还是有可能,因为这里可能发生切换,代码任何地方都可能发生切换,把它切换下去再切换回来的时候就需要从内核返回到用户了,因为它要继续执行4。虽然概率很低但是就这么巧,所以有可能。
更巧的是,sighandler信号捕捉我也要插一个节点node2,也要执行insert,所以它也进入到insert里了把node2也头插,目前sighandler顺利执行完没人打扰

细节:此时同一个insert方法,第一次是main函数的执行流执行insert,当后面信号捕捉时候就已经变成了sighandler方法再去进入Insert了
也就是说一个insert方法在main函数这个执行流还没有结束的时候它又被(信号捕捉的执行流)重复进入了,我们把这种现象叫做函数被重复进入,简称函数被重入了

这不是贬义,也不是褒义,它是一个现状的说明,事实就是一个函数被重复进入了

所以此时sighandler把node2成功插入了,head指向node2
此时信号捕捉完成了,当然你要继续回到曾经你被中断的地方,继续向后运行,所以它又要执行
4.head = p,把node1的地址再赋给head变量,这样一弄就坏了,有个问题,此时node2节点此时没有任何指针指向它了 ,此时node2节点称之为节点发生了丢失,即因为节点丢失导致的内存泄漏问题。

总结:
1个概念
insert函数被main和handler执行流重复进入

1个问题
节点丢失,内存泄漏

如果一个函数,被重复进入的情况下,出错了,或者可能出错,这种函数就叫不可重入函数!
否则,叫做可重入函数
目前我们学到的大部分函数都是不可重入的!

今天链表头插函数insert就是不可重入函数,因为他可能出错。
可重入和不可重入并不是褒义或者贬义,它指的是函数的特点

那怎么办呢?
不要让这个函数重复进入不就完了吗。很简单
非得重复也有方法,后面再说。

今天最难理解的是我这也没创建多进程和线程啊,可是怎么被两个可执行流进入呢?
我怎么理解main和信号捕捉函数说它俩执行流?
说他是俩执行流其实它是假的,它还是一个进程
例如,如果我们今天main函数运行过程中根本没有收到过信号,所以你的sighandler方法根本就没有执行,反过来如果main函数最终收到了对应信号就会执行hander方法。
换言之main函数是一定会执行的,sighandler执行还是不执行取决于我最终有没有收到信号,
也就是说sighandler这个执行流和main函数没有半毛钱关系,要不然没有信号时怎么不执行呢?他和信号是否到来有关系
所以main函数和信号捕捉是两个不同的逻辑,系统设置时正常执行main函数来了信号之后本质是把Main函数暂停了然后去执行sighandler方法,他是在一个进程的上下文里执行的,但是确实属于两种不同的执行流。
你还可以这么理解,每一个函数都有自己栈帧,main函数有自己的栈帧,sighandler也有自己的栈帧,但是这两个函数栈帧没有调用和被调用的关系,他是互相并列的,取决于是否有信号到来我们才能执行sighandler方法。
换句话说,我们今天就认为他是两个执行流,第二个执行流取决于有没有信号就完了。

最后,你说我们的函数大部分都是不可重入的,为什么呢?
主要是因为c++ STL容易充斥大量的new,malloc操作这样的结构,malloc是使用全局链表来进行管理的,刚都说了链表插入本身就是不可重入的,你还拿链表做管理
当然不一定非得是链表,只要是链式结构,要扩容反正一定会充满大量的数据结构指针的变化
既是全局的又是链式结构所以STL大部分都是不可重入的

volatile 关键字

作用:保持内存可见性

今天站在信号角度我们来验证一下volatile保持内存可见性

先看一段代码,信号捕捉修改全局flag让main函数正常结束
在这里插入图片描述
在这里插入图片描述

编译器在编译这份代码时,handler和main是属于不同执行流的 ,编译器发现main函数中没有任何地方会修改flag,所以有可能编译器编译会对flag进行优化。
在优化条件下,flag变量可能被直接优化到CPU内的寄存器中,让CPU不用访问内存直接在寄存器中直接读取。
1.main函数中没有任何修改flag变量的操作
2.cpu会进行算数计算和逻辑计算,我们设置!flag让他在cpu进行逻辑计算
两个条件一结合,注定编译器有可能把变量优化到寄存器中

可是今天还没有优化,那怎么优化呢?
我们man g++ /-o1 查看编译器的优化级别,
在这里插入图片描述
所以在编译时可以带上-o1优化选项
在这里插入图片描述
此时优化后发信号就无法修改flag退出main函数了
在这里插入图片描述
我们看到结果catch a signal:2说明捕捉方法已经运行了,则flag已经设置为1 了
那main函数就应该退,可是他没退,为什么呢?

不管你怎么优化,对应的变量依旧要在内存中开辟空间。
也就是flag要在内存中存在的。

我们还学过一个关键字 register
比如register int a ;
这个register是一个建议性关键字,你能放在寄存器里就放
现在的问题是 它建议把变量放在寄存器里,是让你把变量在寄存器上开辟,还是把变量的内容放到寄存器里?
我们无法直接使用寄存器,别说你是C语言,
人家是建议性关键字,它的意思是变量永远还是要在内存里开辟的,只不过变量内容能放还是放到寄存器上。

现在flag一定会在内存中存在,我们以前怎么检测的,每次从内存中读取变量到寄存器中做逻辑检测,控制逻辑
在这里插入图片描述
现在优化后,对应第一次时把对应flag内容0放到寄存器里,然后cpu中做计算!0
以前要计算时必须每次都得从内存中读取到cpu寄存器然后进行检测
优化后检测main函数中只有读取没有修改,他也看不到handler中改了flag,它做不到因为是两个执行流,那么优化后每次都在cpu寄存器中!0直接用,因为这个工作不需要访问内存了,所以效率就高了
在这里插入图片描述

今天你信号捕捉修改了flag,可这和我寄存器优化后的一直都是!0有什么关系,
所以每次都是读取寄存器,不读取内存了。你也就无法退出了

通过cpu寄存器在cpu和内存这两个设备之中形成了一道寄存器屏障之后
它不访存了,也就是内存不可见了

因为优化,导致我们内存不可见了!
volatile int flag = 0; // volatile关键字:防止编译器过度优化,保持内存的可见性!

SIGCHLD信号

子进程退出的时候,不是静悄悄的退出。
子进程在退出的时候,会主动的向父进程发送SIGCHLD(17)信号
怎么证明??

子进程退出时,让父进程捕捉一下17号信号,如果父进程莫名收到个17号信号不就证明了。
在这里插入图片描述
在这里插入图片描述

所以子进程退出确实会给父进程发信号,所以我们在进行进程等待时,我们可以采用异步等待的方式,也就是基于信号来进行进程等待。

等待的好处是什么?
1.获取子进程的退出状态,释放子进程的僵尸(这是必须完成的)
⒉.虽然不知道父子谁先运行,但是我们清楚,一定是father最后退出!

今天我们采用基于信号的方式等待你还是得完成释放子进程的僵尸,所以还得调用wait/waitpid这样的接口
我的父进程主逻辑就不再调用waitpid这样的接口了,但father必须保证自己是一直在运行的,你一退我子进程不就孤儿了吗,那你还玩什么呢,今天父进程可以采用信号的方式可以忙着自己的事情,不要把自己投入到纯纯的等待中。

所以我们可以试着把子进程等待写入到信号捕捉函数中! 前提是父进程一直运行。
在这里插入图片描述
如果我们有10个子进程呢??
如果同时退出呢?
10个子进程同时退,每一个子进程都要向父进程发送17号信号,上面讲过我同时收到了10个SIGCHILD,我正在捕捉一个的时候父进程要把SIGCHILD信号就阻塞了,所以阻塞期间我正在处理其中一个,后来剩下九个也来了,此时屏蔽掉之后10个信号注定了7-8个信号都丢失了,最后也就意味着只有1~2个信号捕捉才会执行,也就回收1-2个进程,那剩下的进程不管了吗?

所以如果有多个子进程我们该如何正确的回收呢?
如果今天有10个进程同时退,我们要带上循环,循环回收,没有子进程退出了waitpid也就失败了循环也就结束了。

相当于多有少个子进程wait多少个。注意waitpid pid是-1,则是等待任意一个子进程
在这里插入图片描述
这种写法可以,但是如果退出一半,剩下5个子进程不退出,阻塞式等待不就卡在waitpid这里了,handler也就无法返回了

所以我们可以采用非阻塞等待
在这里插入图片描述
会有如下三种情况
1.等待成功,继续循环等待下一个
2.等待时子进程不退,waitpid返回0,循环结束,handler返回,这也没什么,下一次再有子进程退它会再发信号
3.子进程没了,等待失败,循环结束,handler返回
但都不会卡在waitpid里面导致handler无法返回

所以在这种情况下父子进程随便创建,子进程随便退,我基于信号自动都能回收,都能hold得住。
这就是,对于进程等待时基于信号的异步式的进程轮询等待方案。

最后再说一下
必须得等待吗??? 必须得调用wait吗??
我们知道子进程退出时会向父进程发送17号信号,linux可不可以让我不等呢,我不需要调用wait,你不是僵尸了直接让OS把子进程释放掉,我不关心子进程退出状况,也别让我父进程回收了,有没有更简单的方法呢?

要想不产生僵尸进程还有另外一种办法:父进程调 用signal将SIGCHLD的处理动作
置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。
在这里插入图片描述
我今天就是不关心子进程退出情况,我要是关心那还是得等。
这种情况父子进程都信马由缰的去跑,父进程不用照顾子进程,各退各的并发跑,但为了子进程不变孤儿,父进程尽量还是最后一个退出。

最后一个问题
默认父进程17号处理动作是Ign,那不就是忽略吗,你现在把17号signal(17, SIG_IGN);还是忽略,以前有僵尸,现在显式捕捉signal(17, SIG_IGN);忽略不僵尸了,怎么理解两个忽略?
在这里插入图片描述
17以前是默认动作,是SIG_DFL,只不过他的默认动作是IGN
你捕捉后改成忽略了!
handler中SIG_DEF是0,SIG_IGN是1,一旦是1,OS直接把子进程给回收了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值