Linux中信号认识及处理和硬件中断与软中断的讲解
对于信号和硬件中断他们有着相同的联系,具体咱们下面讲解,首先咱们从Linux中信号开始学习。
一、认识信号(全是干货)
对于信号来说,Linux中有很多的信号,具体如下图:
一般咱们使用的信号只有前31个,他们的作用基本上都是结束/kill/暂停掉这个进程。
那具体信号怎么产生呢,很简单,具体如:
1、由键盘产生,例如:大家熟知的 ctrl + c 结束掉一个正在运行的程序。
2、由指令产生,例如:对于一个进程可以通过 kill -9 + 进程的pid 来终止掉这个特定的进程。
3、由系统调用产生,例如:在一个程序中,大家通过调用 Linux 中的系统调用接口来让OS帮我们获取系统调用所获取的结果,就像read接口一样,OS帮我们从内存中读取信息放到read的结果中。
4、由软件条件产生,例如:在一个程序中,我们定义了一个 alarm 定时时钟当他到达时间之后他会给我们发送一个alarm的信号告诉我们定时已经结束。
5、由异常产生,例如:在一个程序中,类似于出现 ÷ 0, 野指针等条件,OS会直接杀掉我们的程序,这也是因为程序出现异常之后,发送信号给OS。
以上就是信号的认识以及产生条件,接下来我们说如何捕捉信号并且处理信号。
二、捕捉以及处理信号
捕捉信号,我们需要知道一个Linux提供给我们的函数来进行捕捉:
这个函数用来捕捉信号,第一个参数 signum 用来表示捕捉哪一个信号,具体要捕捉那个信号就如认识信号里面的那个图一样,举个例子,比如我们要捕获 SIGINT 信号,我们可以直接输入 SIGINT 也可以输入 2 号信号,因为 SIGINT 是一个宏 代表的就是 2,后面的sighangler_t 代表的就是一个函数,表示我们捕捉了这个信号之后的处理函数,这个函数可以自定义实现,也可以使用默认的处理方法,也可以使用忽略的处理方法。具体使用方法如下:
1.使用自定义函数来捕捉信号并且处理:
void handler14(int signu)
{cout << "我捕获了14号信号" << " signu: " << signu << endl;// 这里不退出 会出现死循环的情况// 因为出错的标记位 被置为了 1 然后主进程死循环 所以每次进入主进程 每次都要检查一下出错标记位// 每次都被 signal 捕获 每次都进入自定义的函数指针exit(0);
}
int main()
{// alarm 在时间结束之后需要给OS发送14号信号 让OS杀掉进程alarm(10);// 这里配合 signal 结合起来 更会好玩一点 到时间了 不会直接让OS杀死 而是执行自己的操作signal(SIGALRM, handler14);while(true){cout << "I'm aliving..." << endl;sleep(1);}return 0;
}
2.使用忽略(SIG_IGN)处理操作来捕捉信号并且处理:
int main()
{// 忽略对捕捉的 SIGINT信号进行操作signal(SIGINT, SIG_IGN);while(true){cout << "我在执行" << endl;sleep(1);}// 这个程序调用的结果就是无法被 CTRL + c 来杀掉这个程序 因为忽略了这个捕捉信号之后的操作return 0;
}
3.使用默认(SIG_DFT)处理方法来捕捉信号并且处理:
int main1()
{// 这个SIGINT默认的处理方法就是杀掉进程signal(SIGINT, SIG_DFT);while(true){cout << "我在执行" << endl;sleep(1);}// 所以当 使用 CTRL + c 操作后就可以终止掉这个进程return 0;
}
三、进程地址空间中的三张表(pending,block,函数指针表)
对于一个进程,它拥有三张关于信号的表,如下:
1.block表(信号屏蔽表)
block表是一个位图的结构体,结构体类型为 sigset_t 类型,对于block表,他的作用顾名思义就是屏蔽信号,比如说这个 block 表中 SIGINT的信号对应的位图为 1,那就是屏蔽了这个信号,当我们在触发 ctrl + c 给这个程序的时候就会发送这个类型的信号给这个进程,但是这个进程已经被屏蔽了,因此不会触发终止进程的操作。
这里有很多对block表中的位图进行操作的函数,如下:
这些函数都是在内存中对位图进行操作,要想真正使用对位图操作的表,就要用到下面这个函数,把自己操作的位图的表写入到内核中,才可以进行使用:
这个函数就是把我们上面操作的 sigset_t 的结构体写入到内核之中被使用起来,第一个参数how的意思是我们要选择的写入的方式,库中给我们提供了三种方式:
第一个 SIG_BLOCK 的操作意思就是把我们自己操作的 sigset_t 的结构体中位置为1的信号写入到内核之中,也就是加入屏蔽的信号。
第二个 SIG_UNBLOCK 的意思就是 删除之前内核中屏蔽的信号,然后再重新放入新的自己添加的未被删除的信号。
第三个 SIG_SETMASK 的意思就是把之前屏蔽的信号都被清空,然后把自己操作的 sigset_t 直接写入到内核中。
第二个参数 set 就是自己操作的位图结构,搭配第一个参数 how 我们就可以对这个block进行操作。第三个参数是一个输出型参数,用来获取老的block的位图结构。
2.pending表
pending表也是一个位图结构,他的数据结构类型也是 sigset_t 类型的位图,他位图的每一个位置代表是否收到这个信号,如果收到这个信号他的对应位置的bit位就被置为1,代表收到这个信号,在处理这个信号之前他对应位置的bit位就为0。
3.函数指针表
这个表的结构是一个函数指针数组,它里面每个位置储存的就是每个信号对应的默认处理方法函数,例如SIGINT代表的就是终止进程的方法,如果我们不设置对应信号的方法,他就使用默认方法。
重点:当处理一个信号的时候,为了防止多次对一个信号进行捕捉执行它所对应的函数,造成信号处理嵌套,在处理这个信号的时候它对应的block表的位置置为1,等处理完这个信号之后就会把这个信号对应的block表的位置置为0,进行下一次处理。
四、信号捕捉及其流程
这里分为两个区域,上面的是用户态,下面的是内核态,为了保证安全的访问内核,每次访问都要进行身份认证。CPU中有一个寄存器来进行身份判断,CPL寄存器,当寄存器的值为0就代表内核,3代表用户。
在处理信号的过程中:
1.当handler为默认状态的时候,直接释放资源,进程结束。(因为在内核态)
2.当handler为忽略状态的时候,直接将pending的1置为0,返回用户态的当时执行的代码出,不进行处理。
3.当handler为自定义的时候,进程由用户态->内核态->用户态->内核态->用户态,一共经历了四次转变。
五、硬件中断
1.首先列出一张图硬件中断的操作过程图:
信号处理的过程类似于硬件中断的过程,这里给大家讲解一下硬件中断的过程,硬件中断顾名思义是由硬件给OS发送中断(也就是一个高电平),交由OS处理,OS在获取到中断之后,进行处理,在进行处理之前,先要保存CPU中寄存器里面的内容压入到当前的内核栈中保存起来,然后通过硬件中断发送过来的特定的位置的高电平来查找中断向量表(这个表是操作系统开机就会自己创建的一个中断向量表,也就是处理中断的函数指针数组),找到特定的中断处理方法,然后进行处理,处理完毕之后在恢复CPU当时压入内核栈中的寄存器信息,这个中断就算处理完毕。
eg:时钟中断,操作系统通过时钟中断每隔一个时钟中断就给操作系统发送中断,告诉操作系统该进行进程调度啦,操作系统还是通过查找中断向量表找到时钟中断的处理方法(进程调度),来处理进程。
2.软中断
能让操作系统进行中断的可不止硬件中断,还有软中断,顾名思义软中断不适用硬件所触发的,软中断是通过调用Linux中的系统调用来引起的软中断,例如在程序中调用系统调用,read,write..,调用这些系统函数就会触发软中断,通过查中断向量表来确定是系统调用,然后再通过系统调用号来确定是哪一个系统调用,如下图:
六、总结
信号和中断的区别
1、信号是纯软件的行为,类似于模拟中断的行为。
2、硬件中断是纯硬件的行为。