Linux系统中对中断的处理(学习笔记)
参考资料:韦东山第三期
进程、线程、中断的核心:栈
ARM处理器程序运行的过程:
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它采用的指令比较简单,有以下特点:
1、对内存只有读写指令
2、对于数据的运算是在CPU内部实现的
3、使用RISC指令的CPU复杂度小一些,易于设计
以a = a+b为例,操作如下:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074811954-724125927.png)
代码就是机器指令
在有MMU的系统中,每个进程的空间都相互隔离,进程A无法修改进程B的代码、数据;在没有MMU的系统中,比如运行UCOS的单片机中,无法保证进程A不破坏进程B
只能在CPU切出去瞬间,将寄存器保存下来,保存在栈中,保存的这些值称为现场
过程如下:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074814005-530684909.png)
使用栈来保存现场是所有操作系统进程调度的核心
Linux中:资源分配的最小单位是进程,调度的最小单位是线程
也就是说,在一个进程中,可能有多个线程,这些线程公用打开的文件句柄,全局变量等等。而这些线程,之间又是互相独立的,“同时运行”,也就是说:每一个线程,都有自己独立的栈
Linux对中断的扩展:
Linux中,对中断处理有两个原则:
1、中断不能嵌套,也就是说中断不能被打断
2、中断的处理要越快越好
对于耗时的中断,可将中断分为上半部和下半部,用内核线程来处理中断
中断上半部:在关闭中断下处理紧急的事情,此时无法处理其他中断
中断下半部:在开中断下处理非紧急的事情,此时会被中断打断,是软件中断实现的
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074814192-871768560.png)
硬件中断数组
如果在中断下半部是再次被中断,这是第二次打断也不会被执行,如下:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074813042-1636873388.png)
所以,中断上半部对于中断下半部是多对一的关系。
那么键盘的过程中,有可能再次触发按键的中断,所以需要把所有的按键都读出来,读完为止
A上半部处理完成,打开中断,A进入中断下半部,此时B进程中断,这个过程中,A处理的是所有中断的下半部
下半部耗时如果不是太长:tasklet
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074813783-1458672992.png)
下半部耗时长且复杂:workqueue,worker内核线程
中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟中断的处理还没完,这期间APP是无法执行的。所以,如果中断要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和APP都一样竞争执行,APP有机会执行,系统不会卡顿
这个内核线程是系统帮忙创建的,一般是worker线程,可以用ps -A| grep kworker查找;
内核有一个work queue,只要把内核下半部函数work放入work queue。这样整个处理方法
线程化的中断threaded irq:
使用线程来处理中断,并不是什么新鲜事。使用work就可以实现,但是需要定义work、调用schedule_work,很麻烦
内核有相应的函数:
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074812640-1177625506.png)
可以只提供thread_fn,系统会为这个函数创建内核线程。发生中断时,内核线程就会执行这个函数
每一个中断都创建一个内核线程
软件上处理中断的过程与硬件上中断发生的过程刚好是反过来的
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074812651-213107186.png)
两个中断号,GIC呈现给CPU的A号中断,另外一个GPIO模块里面的B号中断
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074816776-992258009.png)
request_irq注册irq,内核就会帮创建一个irqaction结构体,这个结构体,会放有handler函数和线程处理函数。对于共享中断,每个外部设备的处理函数都会链在一起。这些外设的中断处理函数都会被执行,来确认是哪一个具体设备中断触发的
handler处理函数就是中断上半部,threaded_fn就是中断下半部
request_irq(irq, handler)函数中用到的irq,实际上是虚拟中断号
不同中断控制器都分配有一个irq_domain,解析interrupt-parent时生成
GPIO控制器里面有第一号中断,UART模块里面也有第一号中断。这两个“”第一号中断”是不一样的,它们属于不同的“域”--irq_domain
irq_domain里面有个.xlate函数用来解析设备树,.map函数会将hwirq转换成虚拟中断号
内核在处理设备树时,会使用irq_domain里面的map函数,建立hwirq和virq的关系,保存在linear_revmap
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074815941-1798358158.png)
读取GPIO寄存器得到hwirq,根据hwirq得到之前映射的irq
![0](https://img2024.cnblogs.com/blog/3261801/202403/3261801-20240326074815184-962747432.png)