我们要从三个方面讲解信号
我们生活中的信号有哪些?
红绿灯
闹钟
转向灯
狼烟
这些都是生活中的信号
1.我们为什么认识这些信号?
怎样才算认识信号?
例如: 红灯 + 停、绿灯 + 行
2.我们在我们的脑中能够识别这些信号
3.如果某个特定信号没有产生,但是我们依旧知道应该如何处理这个信号
4.我们在收到这个信号的时候,可能不会立即处理这个信号
例如:你的快递到菜鸟驿站了,但是你可能有自己的事情,并不会第一时间去拿这个快递
5.信号本身在我们无法立即被处理的时候,但是一定要被临时记住
什么是Linux下的信号?
本质是一种通知机制,用户 or 通过发送一定的信号,通知进程某些事件已经发送,进程可以在后续进行处理。
结合进程产生信号结论
a.进程处理信号,必须具备信号“识别的能力”
(看到信号+处理动作)
b.凭什么进程能够“识别信号”?
程序员在写代码的时候就已经解决了这个问题!
c.信号产生是随机的,进程可能正在忙自己的事情,所以信号的后续处理,可能不是立即处理的。
d.进程会临时的记录下对应的信号,方便后续处理。
e.在什么时候处理呢?合适的时候(之后再讲解怎样算合适的时候)
f.一般而言,信号的产生相对于进程而言是异步的。
例子:
你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”
。
在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:
1.执行默认动作(幸福的打开快递,使用商品)
2.执行自定义动作(快递是口红,你要送给你的女朋友)
3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话
我们写个循环的代码
当代码运行的时候,我们只有Ctrl C才能让它停下来
其实Ctrl C的本质是发送2号信号
测试
修改代码:因为发送信号需要pid,所以我们修改一下代码,获取一下进程pid
创建两个窗口:
窗口一运行代码:
窗口二发送2号信号:
结果:
信号处理的常见方式:
a.默认(进程自带的)
b.忽略(信号处理的一种方式)
c.自定义(捕捉信号,执行程序员写好的动作)
常见信号:
1-31:普通信号
34-64:实时信号
因为操作系统大部分是分时操作系统
,也就是信号产生后不一定立即被执行,分时操作系统的信号是1-31
实时操作系统
是信号产生后一定立即被执行,实时操作系统的信号是34-64
每个信号(普通信号)的含义:
1.如何理解组合建变成信号?
2.如何理解信号被进程保存?
3.如何理解信号发送的本质?
1的答案
:键盘的工作方式是通过中断方式进行的,OS当然也能识别组合键,例如:Ctrl C
2的答案
:进程必须具有保存信号相关的数据结构(位图,unsigned int)
那么该位图在哪?
进程的PCB内部保存该位图字段
3的答案
:因为信号位图是在task_struct中---->而task_struct内核数据结构----->由OS管理
信号发送的本质:OS向目标进程写信号,OS直接修改进程PCB中的指定的位图结构,最后完成“发送”信号的进程。
组合键执行流程
4.OS解释组合键---->查找进程列表----->找到前台运行的进程----->OS写入对应的信号到进程内部的位图结构中!
信号捕捉初识
参数解释:
signum
:要捕捉的信号
handler
:回调函数,通过回调的方式,修改对应的信号捕捉方法
代码:
运行结果:
我们发现确实捕捉到了Ctrl C发送的2号信号,但是我们发现Ctrl C不能终止进程了。
如果我们不要自己写的回调函数呢?
运行结果:
我们发现进程直接退出
通过上面我们发现signal函数,仅仅是修改进程对于特定信号的后续处理动作,不是直接调用对应的处理动作。
如果后续没有任何SIGINT信号的产生,catchsing会不会被调用?永远也不会被调用!
核心转储
Core Dump
首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c1024
开启核心转储:
核心转储的作用:当进程出现某种异常的时候,是否由OS将当前进程在内存中的相关核心数据,转存到磁盘中。
Term不会发生核心转储
Core会发生核心转储
代码:
运行结果:
我们可以看到进程发送了核心转储,退出信号为8,为Action:core,所以发生了核心转储。
发送核心转储就会创建core.id(进程id)文件
那么这个core.id有什么用呢?
在调式代码的时候,会自动定位到错误位置。
为什么生产环境一般要关闭core_dump(核心转储功能)?
因为成本高,一般服务器都是成百上千个集群一起的,如果出错,人力排查的时间或者其它成本就会很高。
通过系统调用发送信号:
参数解释:
pid
:进程id
sig
:要发送的信号
代码:
运行测试:
打开两个窗口,一个窗口运行进程,一个窗口执行我们的代码杀掉进程
其它接口:
raise
:自己向自己发送信号
abort
:自己向自己发送6号信号
abort的作用和exit一样,通常用来终止进程。
如何理解系统调用发送信号?
用户调用系统接口----->执行OS对应的系统调用代码----->OS提取参数,或者设置特定数值------>OS向目标进程写信号----->修改对应进程的信号标记位------>进程后续会处理信号------>执行对应的处理动作!
由软件条件产生信号
管道如果读端不光不读数据,而且还关闭了,写端一直在写,那么写就没有任何意义,OS会自动终止对应的写端进程,通过发送信号的方式。SIGPIPE(13号信号)大家自行去验证
alarm
:
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
代码:
运行:
我们看到count加到快2万的时候就停止了。
但是我们发现设了一个闹钟,这个闹钟一旦触发,就自动移除了。并不能连续定时。
那么该如何解决呢?在回调函数中再设计alarm
代码:
运行结果:
之前我们看到count就加到2万左右,但是CPU的计算就这么低吗?
1.因为我们一直cout,IO的效率非常低
2.我们的服务器在远端,所以还要再带上网络所用的时时间
测试计算机算力(一秒内):
代码:
运行结果:
如何理解软件条件给进程发送信号?
a.OS先识别到某种软件条件触发或者不满足
b.OS构建信号,发送给指定的进程
硬件异常产生信号
代码:
运行结果:
为什么除0错误后会一直循环呢?
1.进行计算的是CPU这个硬件
2.CPU内部是有寄存器的,状态寄存器有相对应的状态标记位
,其中有溢出标记位
,OS会自动进行计算完毕后检测,如果溢出标记位
是1,OS里面识别到有溢出问题,立即只要找到当前谁在运行,直接提取PID,OS完成信号发送的过程,进程会在合适的时候,进程处理。
3.一旦出现硬件异常,进程一定会退出吗?
不一定,一般默认是退出,但是我们即便不退出也做不了什么!
4.为什么会死循环?
因为寄存器中的异常一直没有被解决!
11号信号
:是段错误主要用来解决----->野指针问题、越界问题
代码:
运行结果:
那么为什么会循环报错呢?
1.
问题都必须通过地址找到目标位置。
2.
我们语言上的地址全部都是虚拟地址。
3.
将虚拟地址转化成物理地址
4.
页表 + MMU
野指针,越界问题----->一定是非法地址------->MMU转化的时候,一定会报错!
所有的信号,有它的来源,但最终全部都是别OS识别、解释、并发送的!
总结思考一下
上面所说的所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
信号的处理是否是立即处理的?在合适的时候
信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?