signal 信号处理的一系列操作总结

时间:2021-06-14 23:30:41
现在编写的代码都是按顺序依次执行的,多核编程才会考虑程序的并发

关于signal
IF 同时设定:
signal(SIGINT,SIG_IGN)
signal(SIGINT,SIG_DFL)
不可能同时执行,因为:

内核中有一个函数指针
void(*p) (int),
p(int )
注册了某个动作以后,函数指针的值就被改变了,当信号来时会直接调用函数指针,即调用函数,  即直接调用p(int),当信号为int时直接传进去,是内核去执行这个函数的 
例如这里SIG_IGN就忽略这个信号,其原理是:
内核(注意是内核中,用户只能操作阻塞集合)中有一个集合,该集合中每个位置放着一个信号,即 | 信号|..,当信号来时,SIGINT对应是位置为空,即这个动作对应的函数指针为空(因为SIG_IGN),故p(int ) 什么都不需要执行(判断为空,直接跳出,逻辑中什么都不做)。signal(SIGINT,SIG_IGN)函数做该设定,相当于对p赋值(指针值)。但代码的执行是很快的,所以中间要加sleep或while(2)让其卡住,否则没有时间去输入这个信号,
........................
........................ 
要么在这里给自己发一个信号
kill(getpid,SIGINT)

在信号中,中间的缓冲时间(如sleep,while)是为了有时间给当前进程发信号(中间要做些操作)看预期效果而用的,不然是不会加时间的

signal(SIGINT,SIG_DFL)而同时又设定这个, 即对函数指针p重新赋值,这时就执行中断该进程,即放弃CPU,清理资源的动作,即内核中2号信号(ctrl+c)默认的默认动作
 
加while(2): 让进程一直running,等待接收信号

关于挂起
前面的signal、sigaction这么多信号处理的,即我们可以设置信号来时去改变内核中函数指针
p  (void(*p) (int), p(int ))
即我们设置了一个函数,然后把这个函数通过注册,其实就是传给signal或sigaction,让其去帮我们改变内核中的函数指针p,让p成为所写函数的地址,当信号来时,就会跳转到我们写的函数的位置中,因为函数地址是我们传给它的

所谓阻塞:

用户态,内核态
内核中信号集合:|信号 | 信号 | 信号 信号 |
其默认信号位全为0:000000000
当信号来时,由于每个进程中都有一个信号集合,每个位置都有一个信号挂着,而集合中的信号是由线程帮我们检测的。
(1)If当前进程正在执行,当在用户态和内核态(即系统调用)进行切换时,线程会去检测现在有没有挂信号,例如,for循环加到1000,因为进程是轮转时间片,是要不停地被调度的,当进程一旦被其它线程调度,就会存在该状态切换
(2)If当前进程处于sleep(睡觉),当信号来时,调度线程会将其从睡眠状态拉回来,因为这个时候需要响应信号了.这时该跳转到哪就去哪,因为CPU的执行就类似于一个指针,一句句往下执行,在ESP寄存器中存着,这时ESP寄存器就会被 置成这个信号本身的信号处理流程的地址,从而去执行信号处理流程中的函数 

内核中实际上就是一个指针,一个队列

当信号来了,当拿这个信号来走这个信号处理流程函数,当拿到了要走流程函数(信号处理函数)之前,已经将放置该信号的位置置空了
正常流程:信号来了-->信号处理(把信号标示置空)

If我们想做两件事:
1.希望信号在处理流程函数中不被打断,因为即使在处理流程里边,同样是要从内核态和用户态中来回切换的,因为在处理流程中可以写用户态,如调用printf打印到屏幕,而屏幕是要调操作系统函数才能输出到屏幕中去,这时是涉及到内核态和用户态切换的,这时再来信号时,每次还是要到信号集合中检测的,例如在处理2号信号时,3号信号会打断2号的信号处理,故阻塞的目的是为了保证程序执行的连续性,让程序按自己的预期往下执行,所以:
1.在信号处理流程中阻塞信号,是采用sigaction中给我们提供的 sa_mask 来做这个事

内核中信号集合:|信号 | 信号 | 信号 信号 |
其默认信号位全为0:010000000
当接收到信号时,置为1,例如2号,只有当进入信号流程函数后才会重新置为0
阻塞信号集合(屏蔽集合)( 另外一个集合,与内核集合不在同个队列,这个集合就放要阻塞的信号 ):| | | | | | | |
其默认位全为1:101111111
如果某个信号被阻塞,则变成0,例如2号
当2号信号到来时,这个时候内核信号集合与阻塞信号集合相与,结果为fault,这时不执行信号处理流程函数(这时内核中被置位的位置也一直为1 ),但也会跳转到内核中的函数 void(*p) (int), p(int ),只是函数中为NULL,什么都不做就出去了,这个时候用sigpending来检测信号是否挂起,即首先检测(阻塞集合该位)是不是屏蔽,然后去读内核中的信号集合中该位是否为1(故如果无阻塞,是不可能被挂起的,因为信号来了就直接被系统执行,这时也被置零) ,所以要先发信号过来,这是内核信号集合该位会被置1(因为默认为0)
所以针对sigaction sa_mask用这个函数就必须将其写到信号流程函数中去,因为sigaction所谓的阻塞,只能在阻塞信号处理流程中不要让别的信号过来,该(阻塞)屏蔽集合的有效期就是信号处理流程函数,它保护的就是这一段函数不被其它信号打断,所以有sigpending时要先做两件事:
a. sigaction注册2号信号处理动作,设置3号在sa_mask中
b 接收到2号信号,进入2号信号的处理流程(信号处理流程依然属于进程的执行流程的),在信号处理函数中,必须同时发送3号信号,在信号处理流程中,检测挂起(sigpending )一定要先sleep,否则检测不到,因为发的时候,sigpending已经检测了,所以要先sleep,这时阻塞集合:1101111
内核集合:00100000,这时才能检测3号信号被挂起,即要在信号流程函数中检测挂起