信号
一、信号是什么?
信号是什么?举个栗子,比如红绿灯,上下课的铃声等等,都是一个信号,向人们传递一个信息。在Linux下,信号是一个软件中断,通知进程发生了某个事件,中断当前进程正在执行的操作,去处理这个事件,信号就代表着事件。
信号有多种,各自表示不同的事件。
-
信号的生命周期:
信号的产生,在进程中注册,信号的销毁,信号的处理![在这里插入图片描述]
-
信号的种类:
在此之前,我们先了解下什么是可靠信号,什么是非可靠信号?
比如上课老师布置了两次作业,但是最终你处理了两次完成作业这个过程,这就是可靠信号,但是非可靠信号,相当于一个坏学生,只做了一次作业,另外一次作业丢失。所以我们用命令kill -l来查看信号种类:1-31 可靠信号 不会造成事件的丢失
34-64 非可靠信号 可能会造成事件的丢失
二、信号的相关概念
- 信号递达:实际执行处理信号的动作 信号在进程中注册:操作系统修改进程pcb中的一个信号标志位
- 信号未决:信号从产生到递达之间的状态称为信号未决,简而言之,未决就是一种状态,等待被处理
- 屏蔽(阻塞)信号:进程暂时不接受某些信号,如果在屏蔽器件向进程发送了被屏蔽的信号,该信号不会被进城捕获;一段时间后如果进程解除该信号的屏蔽,该信号将被捕获到
- 忽略信号:进程已经被抵达给目标进程,但是目标进程不处理,直接丢弃
三、信号的产生
(1)硬件产生
- ctrl+c实质上是产生了一个SIGINT信号,终止进程;
- ctrl+\产生SIGQUIT信号,用于终止进程并且core dump(核心转储文件,相当于临终遗言,存储运行数据,方便事后调试 );
- ctrl+z产生SIGTSTP信号,终止前台进程。
(2)软件产生 - 调用系统函数kill signum pid:给指定进程发送一个指定的信号
- raise函数:给当前进程发送一个信号
- abort函数:给自身发送一个SIGABRT信号,引发进程非正常终止
- alarm(int seconds):表示调用alarm这个函数时,等待seconds秒后,给自己发送一个信号,相当于一个定时器
四、信号的注册
信号在进程中的注册说白了就是操作系统修改进程pcb中的一个信号标志位
在pcb中有一个位图,位图是一个一个的小格子,初始状态都为0,如下图所示,表示没有信号的到来,一旦操作系统给某一进程发送一个信号,比如操作系统发送一个2号信号,那么此时就会将2号格子置为1,表示当前进程收到了2号信号,同时修改了标志位。
而这里所说的pending就是我们之前讲过的未决信号集合。
至此我们已经知道,向进程中注册信号其实就是修改位图标志位,告诉进程你有这个事件到来了,那如果有这种情况:系统发送一个二号信号,我们把二号信号的标志位设为1,在这个信号没有被处理之前,他的标志位都是1,那再来一个相同的二号信号该怎么办呢?这时候我们就需要知道可靠信号与非可靠信号是如何区分这种情况的。
-
非可靠信号:如果当前未决信号集合中指定信号已经注册,则什么都不做。也就是说同一时间只有一个节点
-
可靠信号:不管当前信号是否已经注册,都会去修改位图,添加一个新的sigqueue节点。
举个小小的栗子简单理解下:如果我们有一件物品要放进柜子,对于非可靠信号,他会说柜子已经存了物品了,所以就不再放物品进去。而对于可靠信号,不管里面有没有物品,我都要放进去,然后把标志位设置为1。
五、信号的注销
信号的注销其实就是删除当前信号的一个sigqueue节点,并且修改位图
那对于可靠信号和非可靠信号,同样有两种方式。
- 非可靠信号:删除节点后,直接将位图置为0
- 可靠信号:删除节点后,判断是否还有相同节点,若没有,则将位图置为0
六、信号的处理
信号的处理也叫信号的递达,就是我们已经收到这个信号。
信号的处理方式有三种:默认处理方式,忽略处理方式,自定义处理方式
用man signal命令查看,发现有一个自定义的回调函数,接下来是函数signal的两个参数:
signal(int signum,sighandler_t handler)
第一个参数:就是我们之前说的1-64号信号编号
第二个参数:回调函数中有两个已经定义好的参数:SIG_IGN:将这个信号的处理方式修改为忽略处理,SIG_DFL:将处理方式修改为默认的处理方式。
而这个回调函数在第二行可以看见被定义的是没有返回值,但是有一个int型参数的回调函数。下面演示一下signal函数的应用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
signal(2,SIG_IGN);
while(1)
{
printf("----------\n");
sleep(10);
}
return 0;
}
我们来看看运行结果截图:
可以看到的是编译成功,但是按住ctrl+c并没有停止运行,那这里是因为我们的signal函数的第二个参数SIG_IGN是表示忽略掉2号信号的处理方式。那按住crtl+cj就不会其作用了,要想停止运行可以按住ctrl+\
现在我们来定义回调函数sigcb,使得二号信号和三号信号任意一个信号到来,都会触发回调sigcb函数
再看一下运行结果:
解释一下运行结果,当我们ctrl+c时候,系统接收一个2号信号,ctrl+\时候收到一个3号信号。那这里有一个疑问明明先开始我们在休眠,为什么ctrl+c或者ctrl+\的时候会立即打印"------"? 这是因为信号会打断当前的操作,当事件执行完了,又会继续回去执行休眠。
信号的修改处理方式,下面介绍一种更加高大上的函数sigaction,这个接口是我们的重点的介绍,signal这个接口也是利用我们的sigaction接口实现的。用man手册查看:
-
第一个参数:signum信号ID
-
第二个参数:struct sigaction *act表示信号的新动作,是一个结构体,那这里可以看看这个结构体里面包含什么
struct sigaction
{
void (*sa_handler)(int);//类似于调用signal函数
void (*sa_sigaction)(int, siginfo_t *, void *);//信号捕获函数,可以获取其他信息
sigset_t sa_mask;//执行信号捕获函数期间要屏蔽的其他信号集合
int sa_flags;//影响信号行为的特殊标志
void (*sa_restorer)(void);//没有使用,作为了解
}
- 第三个参数:signal *oldact用于获取signum信号原有的动作。(其实就是备份)
在原有的代码基础上,稍微改善下,来看看这个函数的应用
int main()
{
signal(2,sigcb);
// signal(3,sigcb);
struct sigaction newact;
struct sigaction oldact;
newact.sa_handler=sigcb; //设置自定义回调函数
newact.sa_flags=0; //默认使用sa_handler回调函数
sigemptyset(&newact.sa_mask); //清空临时要阻塞的信号集合
sigaction(2,&newact,&oldact);
while(1)
{
printf("----------\n");
sleep(10);
}
}
其实运行效果跟我们上一次的运行效果一样。
总结
以上就是关于信号的相关概念,写者也是根据开头的流程信号的产生,信号的注册,信号的销毁和信号的处理来说明的,可能其他博主写的流程会比我的划分的更精细,希望广大读者给与建议,指出错误,一定虚心采纳,共同进步。
下一篇是自定义信号的捕捉流程,多多期待。