1 异常处理体系结构
说到“异常”,得先理解Linux内核的异常有哪些,如下表所示
异常的处理体系初始化由两个函数完成:trap_init() 和 init_IRQ()
因为中断是异常的一种,且不同板子的中断设置不同,因此有个单独的初始化函数init_IRQ()
下面先看看trap_init 如何完成“异常”的初始化,并搭建异常整个处理体系。
trap_init的主要工作就是复制“异常向量”到0xffff,0000 且复制异常向量的处理函数到0xffff,0000 + 0x200
上面的vector_stubs是个宏,解释如下
OK, 那分支表的处理函数怎样处理的呢?看下面的举例
备注:上图截取自韦东山老师的《嵌入式Linux应用开发完全手册》第20章
OK,最后梳理下ARM架构下的Linux内核异常处理体系结构,如下图
2 中断处理体系结构
好了,有了上面的理解,自然会想各异常对应的总入口函数究竟如何处理异常的?对吗
这里继续以“asm_do_IRQ”这个中断总入口函数为例,因为我们这里就要讲解“中断处理体系“”了呀
对于“中断处理体系结构”,必须清楚支撑/构成这一体系的三大结构体, 还是看图:)
上面的中断处理流程真是这样吗? 跟进去看看
的确如此,但问题来了,谁初始化了irq_desc数组呢?即上面说到的那三个结构体呢?
还记得开头说的那句,中断处理体系因为跟开发板息息相关,因此抽象出中断处理的共性,用init_IRQ()进行初始化
现在看看init_IRQ如何初始化
最后,梳理下ARM架构的Linux内核中断处理体系结构,如下
中断处理流程如下:
(1)发生中断时,CPU执行异常向量vector_irq代码
(2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ
(3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq
(4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等
(5)handle_irq 逐个调用用户在action链表中注册的处理函数
可见,中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员
用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项
3 编写中断相关的驱动
3.1 用户注册中断处理函数
有了前面对中断处理体系的理解,不难发现,要编写中断相关的驱动只需三步走,首先用户即驱动程序
通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action
链表中添加一个表项。
request_irq函数原型如下
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
3.2 中断的处理过程
对于中断这种异常,对应的中断总入口函数是asm_do_IRQ,前面提到 里面调用了irq_desc[irqno].handle_irq指向的函数
而这个函数会先调用硬件相关的函数去使能/关闭中断,再逐一调用用户注册的处理函数,即执行action->handler指向的函数。
那如何action链表有多个时,是如何调用处理的呢?
目标很明确:既然同一中断号对应不同处理函数,那首先需辨别是哪个中断源触发的,其次再调用对应的函数。
具体来讲:
以外部中断EINT8-ENIT23为例,看下图
3.3 卸载中断处理函数
调用 free_irq 函数卸载,函数原型如下
void free_irq(unsigned int irq, void *dev_id)
从所需参数可看出,卸载时需根据irq找到 irq_desc[]数组某项,然后根据dev_id找到action链表中某一节点并卸载即可
因此,对于同一中断有不同处理函数,即action链表不止一个节点时,需保证dev_id唯一才能卸载,而这要求
在注册中断处理函数时指定,确保dev_id的唯一性
因此,卸载过程包含两步:
1.根据 irq、dev_id找到action链表某项,并删除
2.如果aciton链表只有一项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或IRQ_DESC[IRQ].CHIP->DISABLE
来关闭中断。(对于不止一个链表节点,删除某节点时当然不能关闭啦,其它处理函数还要用呢)
另外,注意:中断是很珍贵的资源,不用时记得卸载掉。