对信号和进程之间的理解:
1.进程必须具备识别信号的功能,即使信号没有产生,也具备这种功能。
2.进程即使没有收到信号,也知道这些信号怎么处理。
3.当进程收到一个信号的时候,可能并不会立即处理这个信号。
4.一个进程在信号产生,到信号被处理,就一定会有时间窗口,所以进程还有具有保存信号的能力。
看一下所有的信号列表(kill -l):
代码来演示信号的作用:
演示前先认识一个接口:singal
可以自定义捕捉的动作。
代码如下:
对应运行之后:
每一次捕捉我都按了一次ctrl +c
这次是在另一个窗口输入了kill -2 指令,也捕获到了。
ctrl+c的本质是被进程解释成收到了2号信号。
而在捕捉1-34号之间所有信号的时候:
只有9和19两个信号不能被捕捉。
1.ctrl+c为什么能够杀掉前台进程呢?
首先需要明白什么是前台进程,什么是后台进程:
在linux中,一个终端,一般会有一个bash,每个登录,只允许一个进程是前台进程,可以允许多个进程是后台进程,大部分可执行文件执行起来是后台文件。
可以看到要转成前台文件在后面加&
对应这里ctrl+c可以终止进程。
其实就是因为:
ctrl+c的本质是被进程解释成收到了2号信号,而键盘输入首先是被前台进程收到的。
2.键盘数据是如何输入给内核的,ctrl+c如何变成信号的?
对应键盘中输入的数据会直接给到os内核缓冲区,对应os就会判断输入的是数据还是
控制,如果是控制(ctrl +c),那就直接将其转换为2信号发送给进程。
而在硬件的层面对应会产生硬件中断来中断正在进行工作的硬件。
我们学习的信号,就是用软件的方式,对进程模拟硬件中断。
2.信号的产生:
1.键盘组合键:
对应kill中的2号信号;
对应kill中的3号信号,验证一下:
2.kill 命令
对应可以使用kill -数字 + 进程来对进程发送信号。
3.系统调用:
kill接口:
对应使用:
对应就简单是实现了一个自己的kill命令。
对应kill之后直接停止了。
raise接口:(用的少)
对应的使用:
结果:
对应只对自己所在的进程起作用,参数里写是几对应就发送四几个信号
abort接口:
对应在信号中有一个叫SIGABRT:
对应代码验证:
结果:
功能就是直接发送六号信号。
信号一定是先发给os的,os是进程的管理者。
4.异常
对应代码演示:
1.除0错误:
对应结果:
对应在错误信号里寻找:
捕获信号之后:
结果:
2.野指针问题:
对应的代码:
对因的结果:
在信号中找:
那么为什么异常会给进程发信号呢?
对应产生异常之后会异常有限发送给操作系统,操作系统会向解析信号并发送给进程。
这种除零保存值发生在cpu的运算器在中的,而CPU也属于硬件,也要被操作系统管理,所以信号还是会发送给操作系统的:
5.软件条件:
alarm接口:
对应的参数是秒数:
代码演示:
对应的结果:
可以看到捕获到了。
对应剩余时间的概念:
结果:
对应kill一次之后会显示下一次alarm的剩余时间。
对应信号的产生就有这5中方法。
看一下Core Dump:
对应的是一个进程停止信号:
这是将进程等待的一个图,其中被信号所杀是会有异味是core dump 标志。
我们需要自己开启coredump:ulimit -a-c
通过进程等待的代码来看一下对应的标志:
对应的结果:
kill -2 的结果:
对应的core dump没有改变。
再看对应kill -8 的结果:
对应的curedump为1了。
那么这个curedump有什么作用呢?
方便事后调试:(先运行,运行之后再调试)
在makefile中加上-g选项
对应debug下多出来了一个很大对文件,core.15894
打开系统的corecomp功能,
一旦进程异常退出,OS会将进程在内存中的运行信息
给转存到进程的当前目录中,形成core.pid信息:
所谓的核心转储(core dump).
3.信号的发送:
信号只有31个,难道是巧合吗?
不是,信 号是存在位图中的。
所以os也是进程的管理者,只有它才有资格修改进程的task_struct的内部属性。
4.信号的保存:
首先来谈信号的存储:
对应如图所示:
对应除实体信号之外只有31个信号,存储在一张位图中:
对应每一个信号都要有一种对应的处理方法,对应存在函数指针数组中
就是上图中的第三个数组。
一次简绍
1.block:
表示是阻塞的标志位,对应在这个位图中被标志位1后就会在产生该信号是无法正常递达,因为被阻塞了。
注意: 阻塞和忽略的不同:
阻塞:
只要一个信号被阻塞就不会被递达。
忽略:
信号的递达是成功的,只是拒绝的接受。
2.pending:
这里的状态是信号从产生到递达之间的状态,称为信号未决。
只要有信号的出现就会在pending位图里面置1.
3.handler:对每个信号的处理方法,上图只有两个剩下的需要自定义。
对应的图解:
信号集操作函数:
sigprocmask:
sigpending:
对应的代码实现:
结果:
代码主要分为三步:
1.对信号2屏蔽:
2.重复打印当前进程pending:
3.解除阻塞:
对应使用oset。
如果将所有信号都屏蔽了呢?
对应发现9和19号无论如何都不能被屏蔽
5.信号的捕捉处理:
什么时候处理的?
当进程从内核态返回到用户态的时候,进行信号的检测和处理。
什么是用户态和内核态?
在CPU中有一个叫ecs的寄存器,里面存有两位二进制数,对应的1就是用户态,3就是内核态。
操作系统本质:
基于时钟中断的一个死循环
下来看一张图,对应用户捕捉代码进行的转换:
我们再来认识一个信号捕捉的函数接口:
sigaction:
有三个参数:
第一个与signal函数的参数一样,其他两个参数都是对应的结构体指针,我们来看一下这个结构体:
我们只看里面的第一个属性和第三个属性,其他的不看
代码来验证这两个属性:
对应的结果:
如果想屏蔽更多信号,就要用到第三个属性:
对应的代码:
结果:
必须要打印说出来pending表才利于观察。
对应的结论:
当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来
的信号屏蔽字 , 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止 。
可重入函数:
对应的状况:
1.main 函数调用 insert 函数向一个链表 head 中插入节点 node1, 插入操作分为两步 , 刚做完第一步的 时候。
2.因 为硬件中断使进程切换到内核 , 再次回用户态之前检查到有信号待处理 , 于是切换 到 sighandler 函 数。
3.sighandler 也调用 insert 函数向同一个链表 head 中插入节点 node2,
4.sighandler 返回内核态 , 再次回到用户态就从 main 函数调用的 insert 函数中继续 往下执行 , 先前做第一步 之后被打断 , 现在继续做完第二步。
5.结果是: main 函数和 sighandler 先后 向链表中插入两个节点 , 而最后只 有一个节点真正插入链表中了。
如果一个函数被重复进入的情况下,出错了,就是不可冲入函数。
否则,就是可重入。
不可重入函数的条件:
1.调用的malloc,new获胜者free的
2.调用了标准io库的。
volatile:
先写一段代码 :
MAKEFILE:
结果:
可以看到在优化情况下对应捕捉到2号信号后,对应的flag置为1,但是main中的循环还是不结束,很明显while循环中的flag和handler中的flag已经不是同一个flag了,这就出现的二义性的问题了。
原因:
可以看到在main函数中的flag是没有被修改的,而!运算
一直在cpu中计算,这里编译器会对flag产生优化,直接让
flag待在寄存器中,方便CPU的计算:
当我们不想要这种优化的时候,同时为了避免二义性,就可以加关键字volatile。
来避免这种优化:
SIGCHLD信号:
子进程在终止时会给父进程发送SIGCHLD信号:
属于第17号信号。
代码验证:
结果:
可以看到被捕捉到了。
这样我们就可以实现异步控制等待子进程:
对应的结果:
其实该信号的默认处理动作是忽略,这样可以使父进程专心做自己的工作:
但是这样也有弊端,我发看到子进程的退出信息。
等待的好处:
1.获取子进程的退出状态,释放子进程的僵尸
2.虽然不知道父子谁先运行,但父进程一定是最后退出的
所以在是有信号捕获子进程退出的时候,最好用等待的方式来进行退出。