Linux信号捕捉与穿插中断
首先先给出一张图,后面我们来具体解释它的含义。
我们知道信号的处理并不是立即处理的,而是在合适的时候进行处理。信号递达的方式有三种--默认、忽略和自定义捕捉。现在的问题在于信号的处理具体是在什么时候?
由上图我们可以看到处理信号的时候是在进程从内核态转化到用户态的时候。这里我们只考虑自定义捕捉因为其他的两种比较简单。
1. 内核如何实现信号的捕捉
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码 是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行 main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler 和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返 回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复 main函数的上下文继续执行了。
2. sigaction
#include <signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo 是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传 出该信号原来的处理动作。act和oact指向sigaction结构体。
在信号自定义处理的过程中一共要四次经过用户态-内核态之间的切换。
硬件中断
外部设备就绪后会给中断控制器发送中断,此时中断控制器会通知CPU,CPU得知中断后获取中断号,然后CPU保护现场,并根据中断号--就是函数指针数组的下标执行中断处理历程(函数),执行完毕后恢复现场,处理中断完毕,继续之前的工作。从此以后操作系统不再关注外设,而是外设准备好了会叫CPU。
这种中断的感觉实际上和信号很像。发中断--发信号;保存中断号--记录信号;中断号--信号编号;处理中断--处理信号(自定义捕捉)。
当中断没有到来的时候操作系统在做什么呢?我们从内核源代码的角度来看。内核中的代码有这么一句:for(;;) pause(); 因此当中断没有到来的时候操作系统什么都没做而是在等待中断。
实际上是有一个外部设备叫做时钟源,以固定的、特定的频率向CPU发送特定的中断。然后CPU根据得到的中断号索引函数指针数组执行相应的函数---进程调度函数schedule();这个函数首先将进程PCB里面的count成员减减(注意count就是时间片,count*时钟周期就是进程被调度的时间),如果count等于0的话那么就继续进行一次进程调度。可以观察的出来操作系统就是基于中断工作的软件。由外部设备触发的,中断系统运行流程叫做硬件中断。
有没有可能因为软件的原因也触发上面的逻辑呢?有可能!
比如此时进程正在执行自己的代码,此时CPU正在进行计算a/0,会导致溢出错误,此时CPU自己内部触发中断并得到中断号,CPU根据中断号索引函数指针数组--中断向量表IDT执行相应的函数,这种情况称做异常。
实际上我们可以通过软件的方式让CPU主动中断--在x86架构下对应的汇编指令是int,在x64架构下对应的指令叫做syscall,这称做软中断。