【Linux】信号的捕捉
文章目录
- 信号的捕捉
- ∞图
- sigaction函数
信号的捕捉
当我们在执行主流程的时候,可能因为某些情况而陷入内核,当内核处理完毕准备返回用户态时,就需要进行信号pending的检查,(此时仍处于内核态,有权力查看当前进程的pending位图)
在查看pending位图时,如果发现有未决信号,并且该信号没有被阻塞,那么此时就需要该信号进行处理
- 如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,从主控制流程中上次被中断的地方继续向下执行即可
- 如果待处理信号是自定义捕捉的,即该信号的处理动作是由用户提供的,那么处理该信号时就需要先返回用户态执行对应的自定义处理动作,执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,继续执行主控制流程的代码
注意:handler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程
∞图
可以借助下图进行记忆待处理信号是自定义捕捉时的情况
其中:该图形与直线有几个交点就代表在这期间有几次状态切换,箭头的方向就代表着此次状态切换的方向,图形中间的圆点就代表着检查pending表
问:为什么识别到信号的处理动作是自定义时,不能直接在内核态执行用户空间的代码
理论上来说是可以的,因为内核态是一种权限非常高的状态,但是不能这样设计,
如果允许在内核态直接执行用户空间的代码,那么用户就可以在代码中设计一些非法操作,比如清空数据库等,虽然在用户态时没有足够的权限做到清空数据库,但是如果是在内核态时执行了这种非法代码,那么数据库就真的被清空了,因为内核态是有足够权限清空数据库的
换句话说:不能让操作系统直接去执行用户的代码,因为操作系统无法保证用户的代码是合法代码,即操作系统不信任任何用户
sigaction函数
捕捉信号除了用前面用过的signal函数,我们还可以使用sigaction函数对信号进行捕捉
#include<signal.h>
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact)
函数作用:
可以读取和修改与指定信号相关联的处理动作
参数解析:
signum代表指定信号的编号
若act指针非空,则根据act修改该信号的处理动作 若oldact指针非空,则通过oldact传出该信号原来的处理动作
这里的参数act和oldact都是结构体指针变量
struct sigaction
{
void(*sa_handler)(int);
void(*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void(*sa_restorer)(void);
};
成员解释:
1.sa_handler:
- 如果sa_handler为:SIG_IGN 传给sigaction函数:表示忽略信号
- 如果sa_handler为:SIG_DFL 传给sigaction函数:表示执行系统默认动作
- 如果sa_handler为:自定义的函数指针 : 表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数
注意: 所注册的信号处理函数的返回值为void,参数为int,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号.显然这是一个回调函数,不是被main函数调用,而是被系统所调用
2.sa_sigaction : 实时信号的处理函数
3.sa_mask
- 当某个信号的处理函数被调用,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止
- 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时,自动恢复原来的信号屏蔽字
注意:需要先对sa_mask使用sigemptyset函数
进行初始化
4.sa_flags : 包含一些选项,这里直接将sa_flags设置为0即可
函数返回值说明
该函数调用成功返回0,出错返回-1
小例子:用sigaction函数对2号信号进行了捕捉,将2号信号的处理动作改为了自定义的打印动作,并在执行一次自定义动作后将2号信号的处理动作恢复为原来默认的处理动作
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
struct sigaction act, oact;
void handler(int signo)
{
printf("get a signal:%d\n", signo);
sigaction(2, &oact, NULL);//恢复2号信号的默认处理动作!
}
int main()
{
//先把两个结构体变量的成员都初始化为0
memset(&act, 0, sizeof(act));
memset(&oact, 0, sizeof(oact));
act.sa_handler = handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);//初始化结构体变量act里的成员位图结构
sigaction(2, &act, &oact);//捕捉2号信号
while (1)
{
printf("I am a process\n");
sleep(1);
}
return 0;
}
第一次向进程发送2号信号,执行我们自定义的打印动作,然后2号信号的默认处理动作恢复,当我们再次向进程发送2号信号,就执行该信号的默认处理动作了,即终止进程