中断概念与分类

CPU在执行指令时,收到某个中断信号转而去执行预先设定好的代码,执行完毕后再返回到原指令流中继续执行。

根据中断的来源,中断可分为硬件中断和软件中断。

硬件中断包括外设异步通知,如网络收包中断;CPU之间同步消息,也就是IPI中断。软件中断包括CPU异常,如除0异常、缺页异常;系统调用,某些系统调用依靠中断实现。缺页异常的处理可以参考:进程是如何使用内存的

根据中断是否可以屏蔽,中断可分为可屏蔽中断与不可屏蔽中断( NMI ),可屏蔽中断可以通过设置中断控制器寄存器等方法被屏蔽,屏蔽后,该中断不再得到响应,而不可屏蔽中断不能被屏蔽。

根据中断入口跳转方法的不同,中断可分为向量中断和非向量中断。采用向量中断的CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入该入口地址后,再通过软件判断中断标志来识别具体是哪个中断。

Linux 中断处理程序架构

同步中断(由指令引发的中断)可以与进程一起参与进程调度,简单短小的异步中断可以立即进行完全处理,而比较耗时的异步中断因为会屏蔽中断信号,影响系统对新的中断信号的响应及时性,需要采取立即预处理+延迟处理的方式,减少中断屏蔽时间,也就是把中断处理分为上半部( Top Half )和下半部( Botom Half )。

db7e0690c1d36e2d83dd41525f814c6e.png中断流程 

上半部一般为硬中断(hardirq),下半部(softirq)就有多种处理方法了,包括、tasklet,workqueue、threaded_irq。按照是否可屏蔽来分类,上半部为屏蔽中断,下半部为可屏蔽中断。

中断流程

第一步,CPU收到中断信号后会把ESP、CS等能恢复当前执行状态流的寄存器数据保存到内核栈上。

不管是x86架构,还是arm架构的CPU,基本都是采取双堆栈的设计形式,如stm32的MSP和PSP栈。因此CPU会判断当前状态,先将用户栈切换为内核栈,然后再保存数据。

第二步,获取中断向量号。

每一个中断信号都有一个中断向量号,中断向量号是一个整数。CPU收到一个中断信号会根据这个信号的中断的向量号去查询中断向量表,再调用向量表相应的处理函数。

中断向量号与中断信号的对应关系:对于CPU异常来说,其向量号是由CPU架构标准规定的。对于外设来说,其向量号是由设备驱动动态申请的。对于IPI中断和指令中断来说,其向量号是由内核规定的。

第三步,根据中断向量号从中断向量表中找到对应的门描述符,对描述符做一番安全检查之后,CPU就开始执行中断处理函数(就是门描述符中的段偏移)。中断处理函数的最末尾执行IRET指令,这个指令会根据前面保存在栈上的数据跳回到原来的指令继续执行。

如果是异常,异常处理程序会向进程发送信号 SIGXXX,记录在进程的PCB结构里,在中断程序返回前调用进程对该信号自定义的处理程序,否则调用内核预定义的程序。

中断来源

上面根据中断的来源区分为硬件中断和软件中断,下面分别整理一下。

硬件中断

对于跑在stm32上面的小系统,外设可以直接连接到CPU引脚上,程序初始化时设定好GPIO的处理函数即可;但是对于linux系统,CPU无法预先为所有外设设计和预留接口,因此需要一个中断控制器PIC接收外设中断信号,并转发给CPU。该类中断通常为共享中断,由软件标示符区分不同来源。

软件中断

软件中断包括CPU异常和系统调用中断。其中CPU异常主要分为三类:错误类异常,陷阱类异常和终止类异常。

1错误类异常 Fault

该类异常会先压栈,然后跳到异常处理函数中,执行完异常处理函数后恢复到程序原位置重新执行该指令。如果还有错误,还会再重复该流程。

例如内存缺页异常就是错误类异常,CPU遇到缺页异常时会跳转到异常处理,将缺少的内存页从磁盘中置换到物理内存,再恢复重新执行内存访问指令。

2 陷阱类异常 Trap

该异常也会压栈,处理完之后会执行原指令的下一条指令。程序调试时的断点就是通过trap实现。

3 终止类异常 Abort

该类异常说明系统遇到了严重的错误,如硬件错误和系统表中包含非法值或不一致的状态等,该类异常会导致系统崩溃,如常见的页错误。

linux系统中,系统调用也可采用软件中断的形式实现,如int 0x80指令。

中断处理

软件中断和硬件中断均需要通过CPU的向量表进行中断函数定位才能执行对应的中断函数。软件中断过来的直接调用中断函数即可,而硬件中断对应的中断函数叫硬中断,因为会屏蔽中断,需要短小精悍尽快执行,一般仅仅做一个标记,同时使用信号量等通知,在中断下半部进行具体的处理过程。

硬中断

上面讲过,外设中断会调用同一个handler中断函数,在该函数中根据irq描述符再调用不同的处理函数。而irq描述符和对应处理函数都是PIC控制器在初始化时设置好的。而IPI和per CPU的中断则是向量中断,没有二次跳转。

软中断

在执行完ISR后(ISR是硬中断处理函数,由驱动在初始化时注册),一般会做一个标记,然后使用信号量等进行同步执行中断下半部。

中断下半部包括tasklet、softirq、threaded_irq、workqueue。

tasklet在SMP系统上不会并发执行,可以被禁用软中断的形式禁用掉。而softirq可以在多CPU上并发执行,提升了中断并发性。

中断线程threaded_irq、工作队列workqueue与普通线程调度就完全一样了,里面可以休眠、阻塞,同时被系统调度。而softirq和tasklet的优先级是高于线程的。

中断嵌套与同步

软中断可以抢占线程,硬中断可以抢占软中断也可以抢占线程,而返回来则不能抢占。所以如果低等级执行流代码和高等级执行流代码有同步问题的话,就需要禁用高等级执行流。

对于软中断,只能整体禁用,且只能禁用本地CPU的,无法按类型禁用软中断。

最后梳理一下可重入函数与线程安全的关系。

当程序运行到某一个函数的时候,可能因为硬件中断或者异常而使得在用户正在执行的代码暂时中断转而进入内核,这个时候如有一个信号需要被处理,而处理的这个信号的时候又会重新调用(重入)刚才中断的函数,如果函数内部有一个全局变量需要被操作,那么,当信号处理完成之后重新返回用户态恢复中断函数的上下文再次继续执行的时候,对同一个全局变量的操作结果可能就不如预期。这个时候对于需要同步执行的代码块就需要使用可重入锁进行保护,总结就是:重入函数只访问自己的局部变量或参数,这样不同的执行流使用的数据都是来自于自身的栈空间,互不影响。

Logo

更多推荐