目录
2.1.3 9号信号SIGKILL(没有办法被捕捉的信号,杀死进程)
一.进程信号的理解
1.1 进程处理信号的过程
进程在收到某种信号的时候,并不是会立刻进行处理的,因为有可能进程在处理优先级更高的事情,会选择在合适的时间进行处理这个信号(后续会进行详细介绍)。没有被处理的信号会被暂时保存起来,保存在进程控制块task_struct中,因为信号本质上其实也就是数据,向进程控制块task_struct中写入数据只能是由操作系统进行写入.
1.2 信号经历的过程
信号从产生到被处理存在以下时间线:
1.2.1 产生信号的四种常见方式
1.外设设备的输入(常见诸如:键盘输入);
2.进程异常(进程异常通常是可以被反馈到硬件上面,从而给操作系统发送信号);
3.系统调用(调用系统级别的接口函数,也可以产生函数);
4.通过软件条件是可以产生信号的.
在Linux下,输入kill -l指令可以观察到对应的信号信息,我们常见的信号通常包括1-31,这个联系到我们以前学过的知识,用位图存储相当方便,用对应的位置代表第几号信号,0/1则代表存在还是不存在,比如我们在收到了2号信号之后,就可以对应类似于0100 0000 0000 0000 0000 0000 0000 0000这样进行保存。
struct task_struct
{
uint32_t sigmet_t;//0000 0000 0000 0000 0000 0000 0000 0000
}
1.2.2 信号的处理与捕捉
从信号的捕捉到处理,这个涉及到对应的三张表,block表(也称之为信号屏蔽字),pending表,handler表(信号的捕捉方式,有对应三种方式:1.默认;2.忽略;3.自定义).
二.信号处理函数以及信号总览
信号的处理函数signal函数,signal函数就是建立起某个信号和赌赢处理函数的连接,只有真正接受到对应的信号的时候才会执行对应的处理函数.
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
第一个参数,对应是信号的序号,上面已经列出了Linux下的信号序号,对应既可以用序号,也可以使用信号名;
第二个参数,是接收到信号以后处理信号的函数指针;(三种方式:默认,忽略,自定义);
返回值:调用成功,就会返回上一次信号处理函数被调用的返回值;
调用失败,则会返回SIG_ERR,因为存在无法找到对应的函数的情况,所以就无法创建起信号和信号处理函数之间的关联;
signal(6,signalhandler);
//建立起6号信号与signalhandler函数之间的关联
//等实际收到6号信号的时候,就会对应执行signalhandler捕捉函数
2.1 信号总览
向命令行输入kill -l对应就可以看到在Linux系统下已经定义好的信号:
目前,暂且我们只需要了解前31个信号,我们下面会以代码的方式进行说明一下相应的信号.
2.1.1 2号信号 SIGINT
2号信号的作用是终止进程,快捷方式是Ctrl+C.
Linux系统测试:
//信号捕捉函数
void signalhandler(int signo)
{
printf("process %d receive No.%d signal\n",getpid(),signo);
//退出码设置成2
exit(2);
}
int main()
{
//捕捉2号信号
signal(2,signalhandler);
while(1)
{
sleep(1);
printf("process %d is running\n",getpid());
}
return 0;
}
程序运行如下:
刚开始,我们不发送指定的信号,进程一直在运行.
摁下Ctrl+C之后,向系统发送2号信号,进程退出,并且捕捉到2号信号,调用相应的函数,从结果上面来看我们发送的就是2号信号.
2.1.2 3号信号 SIGQUIT
相同的原理,捕获3号信号,快捷键就是Ctrl + \.
2.1.3 9号信号SIGKILL(没有办法被捕捉的信号,杀死进程)
9号信号的作用就是:杀死进程的同时无法被捕捉.
2.1.4 11号信号 SIGSEGV
11号信号时在进程崩溃的时候发送的.
我们在进行观察信号的代码的时候,首先让进程崩溃,观察程序.
很明显,进程崩溃了,所示如下图:
我们使用signal函数试试,看看接收到的是几号信号。
因此,我们可以得出一个结论,当进程崩溃退出的时候,实际上是接受到了第11号信号!
2.2 信号捕捉的过程
2.2.1 没有成功递达信号
没有成功递达信号的方式有两种:1.没有收到信号;2.信号被阻塞;进入内核态,访问操作系统的代码,对应的pending(信号未决)表为0,代表没有信号进行传递,直接返回用户态,执行下一句代码.
2.2.2 成功递达信号
假如现在发送来一个信号,这里假如收到的是3号信号,pending表和block表其实就是一个位图结构,对应的几号位置代表几号信号,0/1则代表信号是否被接受到;在进程的task_struct表中先看pending表是否为1,在看block表(也称之为信号屏蔽字)是否可以被递达,然后成功递达到handler表中。handler表相当于一个函数指针数组,默认存放函数指针,有三种处理方式:
1.默认
2.忽略
3.自定义处理
收到对应的函数处理方式之后,切换回用户态进行处理.
在处理完对应的函数之后,不是直接就去执行用户代码的下一句了!!!因为在这里同处用户态的代码,不能够直接进行转换,要想返回到用户态时,是从内核态执行相应的底层接口函数sys_sigreturn(),从而进行返回到用户态上面去.
下面,以下面的一张图来进行解释,Linux下信号处理的过程:
1.首先执行用户层代码,然后进入内核区执行OS代码(用户态->内核态)
2.进入内核态后,要进行监测是否收到信号,通过访问进程的task_struct的三张表{pending(信号未决表),block(信号屏蔽字),handler(信号处理表)};如果pending表为0代表没收到信号,返回用户态执行下一句代码,并且pending表为1,block表也为1代表信号被阻塞不能够被正常接受,也是会返回到代码段中执行下一句代码;如果成功检测到有信号发出并成功递达,执行对应的信号处理函数.(内核态->用户态).
3.执行完对应的信函处理函数之后,不能够直接返回代码段,调用系统接口sys_sigreturn()函数(用户态->内核态)
4.最后,返回代码段继续执行下一句代码.(内核态->用户态)
图中的红色圆圈处则代表进程状态改变的节点
2.3 信号捕捉函数补充
当信号的信号处理函数被执行的时候,该信号的block表对应的位置是会被设置成1,这样就会保证如果该类信号再次产生,会被阻塞到当前处理结束为止,等到处理结束之后,则会恢复原来的信号屏蔽字.
sigaction函数:检查或者修改与指定信号相关联的处理动作(可以同时两种操作)
1.第一个参数是信息序号(我们已经列出信号列表)
2.第二个参数是输入型参数,我们需要在处理对应的捕捉函数的同时,还可能暂时屏蔽掉其他的信号,可以将这些参数传入结构体中
3.第三个参数是输出型参数,也是一个结构体,用于输出我们上一次是如何被设置的,如果我们不关心的话,可以直接设置成NULLPTR。
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction结构体介绍:
struct sigaction {
void (*sa_handler)(int);
//void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
//int sa_flags;
//void (*sa_restorer)(void);
}
这里的结构体名称只是恰好和函数名一致,两者没有任何的联系,既要能设置handler表的指针,又要能够暂时性地屏蔽掉其他的信号,所以Linux把其需要传入的参数设置成了一个结构体sigaction.我们只需要重点关注上面两个参数就可以.
测试代码:使用sigaction函数,处理函数的同时,屏蔽2号信号.
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//信号处理函数
void sighandler(int signo)
{
while(1)
{
sleep(1);
printf("process %d receive signal No.%d\n",getpid(),signo);
}
}
int main()
{
struct sigaction iact;
memset(&iact,0,sizeof(iact));
iact.sa_handler= sighandler;
//屏蔽2号信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set,2);
iact.sa_mask=set;
//当有3号信号递达时,执行处理函数
sigaction(3,&iact,NULL);
//阻断进程退出
while(1)
{
printf("No.%d process wait signal\n",getpid());
sleep(1);//阻断进程退出
}
return 0;
}
程序运行如下:
此时,没有3号信号被递达系统执行运行代码,等待信号.
3号信号被递达,调用信号处理函数
键盘输入2号信号,信号被阻塞.
进程退出.
三.信号总结
用一张图来表示我们学到的所有内容:
本期博客就到此为止,如有疏漏和错误,请读者多多指出,谢谢大家!