Linux进程信号——信号的捕捉、保存、处理

1.3k words

信号的基本概念

  • 信号递达:实际处理信号的动作
  • 信号未决:信号从产生到递达之间的状态
  • 信号阻塞:不会被递达的信号
  • 信号忽略:递达的动作是忽略

阻塞和忽略的区别

阻塞指的是这个信号不会被递达,也就是不对其进行操作处理

忽略指的是这个信号可以递达,只是处理的动作是忽略

信号保存

在这里插入图片描述

在进程的PCB中有如下三个数据结构和信号相关

前两个是位图,后一个是数组

block位图

这个位图表示哪些信号被阻塞,0或1表示是否被阻塞,某一个位置表示对应的信号

pending位图

这个位图用来存储收到的信号,0或1表示是否收到,某一个位置表示对应的信号,这个位图也称之为信号集,也就是未决的情况

handler数组

这个数组是一个函数指针数组,里面的内容是函数指针,下标表示收到n号信号,调用的处理方法就是对应的函数指针

SIG_DFL宏代表这个函数是默认处理函数

SIG_IGN宏代表收到这个信号后,进行忽略这个信号

信号处理

img

这个函数可以手动更改handler数组,让进程在捕捉到对应信号的时候调用我们指定的函数处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
#include<signal.h>

using namespace std;

void func1(int signum)
{
cout<<"进程捕捉到"<<signum<<"号信号,PID为:"<<getpid()<<endl;
}

int main()
{
signal(SIGINT, func1);
while(1)
{
cout<<"进程PID为:"<<getpid()<<endl;
sleep(1);
}
return 0;
}

image.png

sigset_t

这个数据结构的本质是一个位图,其实就上面block位图和pending位图的数据结构

对于这个数据结构也有很多操作

sigemptyset

int sigemptyset(sigset_t *set)

这个函数是初始化set锁指向的信号集,对其置零,让其中不包含任何有效信号

sigfillset

int sigfillset(sigset_t *set)

这个函数是初始化全部置一,让其包含所有信号

sigaddset sigdelset

int sigaddset(sigset_t *set. int signo)

这两个是一对,分别对应添加和删除

需要注意的是,在使用sigset_t之前,一定要调用前面的任意一个初始化函数,让整个信号集处于确定的状态

sigismember

int sigismember(const sigset_t* set, int signo)

这个是用来判断是否有效,有效则返回1,无效返回0

sigprocmask

这个函数可以用于读取或更改阻塞信号集,也成为信号屏蔽字

int sigprocmask(int how, const sigset_t *set, sigset_t *oset)

成功返回0,出错返回-1

这里有几种情况

如果set和oset都不是空指针,这个函数就把原来的信号屏蔽字拷贝到oset中,然后按照how参数的规则进行修改

如果set非空而oset为空指针,这个函数就直接按照how进行修改而不拷贝

如果set为空指针而oset非空,则读取当前进程的信号屏蔽字

假设当前进程的信号屏蔽字是mask,我们期望更改的信号集是set

how的参数和功能如下

  • SIG_BLOCK:此时set信号集表示我们想要添加到信号屏蔽字中的信号,原理是使用位运算mask |= set;
  • SIG_UNBLOCK:此时set信号集表示我们希望从信号屏蔽字中接触阻塞的信号,使用位运算 mask = mask&~set;
  • SIG_SETMASK:此时set信号集表示我们想要变成的样子相当于直接赋值,mask = set;

捕捉信号

先说结论再说原理

当进程从内核态转换为用户态时,会自动进行信号的检测和捕捉处理

一般当代码进行执行的时候,操作系统是处于用户态的,但是执行到系统调用,或者出现异常中断时,操作系统会变成内核态

因为系统调用和异常处理的工作实际上是很底层的代码和函数,只有当操作系统处于内核态时才可以执行,此时这个进程的优先级非常高

最本质里面,其实就是在CPU中的CR3寄存器中,表示当前CPU处于什么状态,1表示内核态,3表示用户态,而这个寄存器对于用户也是不可见的,只由操作系统管理

也就是说当程序执行系统调用时会进入内核态

执行完系统调用时会回到用户态

在状态转换的时候,就进行信号的检测和处理

当这时有信号到来的时候,代码会跳转到信号处理的函数

当信号处理函数返回时还会执行特殊的系统调用,再回到内核态

大概流程如下图

在这里插入图片描述

Comments