1.信号是什么?
可以直接通过内核对进程进行操作的手段,信号被记录在PCB(进程控制块)里,当进程被杀死后,PCB也就是留下的尸体,包含了该进程的死亡信息
流程:
1.触发发送信号操作
2.由用户空间转为系统内核操作,对进程PCB施加信号
3.回到用户空间前会逐一检查各PCB中的信号
解释3(因为信号操作必定进入系统内核,所以在每次从系统内核回到用户空间时都会“顺便”检查是否有递达的信号,而不是触发一次信号就进一次内核,而是每次从内核切换出来时“顺便”检查,处理了一个信号,,,以此提高了效率)
2.信号种类
常规与实时,区别在后续有写,比如5,
TERM,CORE,STOP等等,分别有各自的功能
CORE:杀死程序的同时会执行内核转储,生成一个core.pid的文件,可用gdb打开,bt指令查看其程序终止原因
要想生成core文件,需要设置core文件大小,默认为0
3.发送信号指令
kill:向任意进程发送任意信号
reise:向自身发送信号
abort:特殊,结束自身进程,且无返回,但会进行内核转储
4.alarm秒级定时关闭进程
5.信号未决与阻塞
PCB中有信号表,包括未决,阻塞,触发函数,都是可以读取或改写的,以此来设置各种信号
信号产生后可以被进程所屏蔽,被屏蔽的信号不能被进程所接收到,也就不会执行
信号发送流程:
信号产生但被阻塞,此时属于信号未决,此时信号的未决标志位置1,阻塞标志位也置
每个进程的PCB都有信号未决集合和阻塞集合,集合里的序号与系统信号的序号所对应,对应序号置1,则代表该信号未决,阻塞。
捕鼠夹效应:信号被捕捉以后,如果没来得及处理(触发函数未进行),即便此时有新的信号到来,也不会被捕捉,导致非阻塞状态的信号丢失,类比捕鼠夹抓住老鼠后,只有处理了老鼠才能继续捕捉新的老鼠,常规信号多次未决只取一次,实时信号可以多次未决,按顺序生成一个未决序列
6.信号阻塞集合设置流程
先操作集合,再将集合传入对应函数,保留老集合用于复原
1.使用s_sigset类生成new,old两个集合,(s_sigset生成集合初始化都是当前进程的集合)
2.用函数将new初始化并传入想要改变的信号序号
3.将新集合与老集合进行操作,例如,增加,删除。。
4.老集合用于复原(需要的话)
7.信号未决集合的应用
自己man
8.信号捕捉操作
改变信号的触发操作
逻辑实现:对一个结构体设置
1.将自定义的函数接口传参
2.可以设置信号函数触发期间屏蔽其他信号,保证信号触发的完整性
3.标志位置0,具体情况自己man
解释2,信号函数触发时,如果有别的信号或者自己再次触发,会打断当前的函数操作,这显示不是想看到的,所以当进行信号函数处理时,可临时屏蔽其他信号,处理结束再重新开放。
方便的是,linux提供的结构体接口默认屏蔽自身信号,以及只需要传入需要屏蔽的信号集(s_sigset),阻塞和复原部分由结构体内部实现。
9.mysleep()的实现(仿sleep()
思路:利用alarm信号的定时触发信号功能和pause()的挂起等待信号功能,实现挂起等待定时发送的信号,也就是睡眠
1.信号捕捉操作,修改alarm的action,因为睡眠不需要退出进程,最好是一个空action,这样可以提高计时精度
2.接收alarm(0)的返回值并返回
3.复原alarm信号的action
解释2:alarm(0)会关闭所有的alarm并且返回剩余没有睡眠的时间
设想:一个外界信号被pause()所接收,也就是睡眠被惊醒,但alarm其实还在定时中,此时我们需要获得被惊醒后还未睡眠的时间,也就是alarm程序剩余的定时时间,定时相关的数据全由alarm负责,此时立马调用并接收alarm(0)的返回值就可以得到未睡眠时间,比如睡3s,睡1s被其他原因惊醒,将返回2s
10. mysleep()优化 -- sigsuspend()
时序要求严格时应当使用sigsuspend()而不是pause()
原因:
pause()在等待信号的过程中,cpu可能被其他进程占用,此时信号递达会记录在PCB中,然后当cpu从内核返回时,会至少处理一个信号,也就把pause()所等待的信号处理了,此时pause()就出现了“错过”的情况
设想解决方案:
在发出信号前,将正在等待的信号加入当前进程的信号屏蔽字,在pause()前复原信号屏蔽字
不可行原因:复原信号屏蔽字后依然可能出现cpu被占用导致的上述问题,解除阻塞和等待信号是两条命令,就依然有可能被“见缝插针”,必须将两个操作融合为一个原子操作
解决方案:
sigsuspend(解除屏蔽字的集合)函数是系统为这一问题提供的最优解,它的功能是临时解除屏蔽字的同时挂起进程等待信号,此时确定cpu已经被sigsuspend()所占用,不会再被其他进程抢占
实现逻辑:
发出信号前,先屏蔽待等待信号
向sigsuspend()传入待等待信号用于临时解除阻塞
复原信号函数
复原信号屏蔽字
两个复原顺序固定,因为如果先解除阻塞,可能会导致其他进程调用自定义的信号函数,这显然是不严谨的
注意:
两个复原顺序固定,因为如果先解除阻塞,可能会导致其他进程调用自定义的信号函数,这显然是不严谨的
sigsuspend()仅仅是临时解除阻塞,不要忘了复原信号屏蔽字
对信号屏蔽字操作时,尽量只对一个信号操作,不要改动其他信号,保持严谨
11.sigchild信号
子进程死亡时会发出sigchild信号
历史原因,unix子进程死亡会留下僵尸进程
仅限于linux中,有一个改进,如果用sigaction()将sigchild触发函数自定义为SIG_IGN,子进程死亡就不会留下僵尸进程,属于是“自己把自己给埋了”,不再需要父亲收尸
当然,可以使用sigaction()对其函数进行自定义,为我们所用,但是如果这样的话,就要在自定义函数中加入wait()用于收尸