一、Linux中中断原理
1、中断的分类
在Linux操作系统中,中断的分类是非常复杂的。根据不同的角度,可以将中断分为不同的类型。
根据中断的来源,中断可分为内部中断和外部中断,内部中断的中断源来自CPU内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助CPU内部的软件中断) ,外部中断的中断源来自CPU外部,由外设提出请求。
根据是否可以屏蔽中断分为可屏蔽中断与不屏蔽中断 (NMI) ,可屏蔽中断可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应,而不屏蔽中断不能被屏蔽。
根据中断入口跳转方法的不同,中断分为向量中断和非向量中断。采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
2、Linux中断处理程序架构
设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点, Linux将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half) 。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。 “登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
这样,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
3、Linux中断实现过程
3.1、中断信号线(IRQ)
中断信号线是对中断输入线和终端输出线的统称。终端输入线是指接受中断信号的引脚,中断输出线是指发送中断信号的引脚。
3.2、中断控制器
中断控制器位于ARM处理器核心和中断源之间,外部中断源将中断发到中断控制器,中断控制器根据优先级进行判断,然后通过引脚将中断请求发送给ARM处理器核心。
3.3、申请和释放中断
在Linux设备驱动中,使用中断的设备需要申请和释放对应的中断,分别使用内核提供的request_irq()和free_irq()函数。
1.申请IRQ
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
在Linux2.6中,该函数有<kernel/irq/Manage.c>实现。
irq 是要申请的硬件中断号。
handler 表示要注册的中断处理函数指针,是一个回调函数,中断发生时,系统调用这个函数来处理中断,dev_id参数将被传递给它。
irqflags 是中断处理的属性,若设置了 SA_INTERRUPT,则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了SA_SHIRQ,则表示多个设备共享中断。
devname 表示设备的名字,该名字会在/proc/interrupts中显示。
dev_id 这个指针是为共享中断线而设立的。如果不需要中断共享中断线,那么只要将该指针设为 NULL即可。 很多资料中都建议将设备结构指针作为dev_id参数
request_irq() 返回0表示成功,否则返回对应错误的负值,返回-INVAL 表示中断号无效或处理函数指针为NULL,返回-EBUSY 表示中断已经被占用且不能共享。
2.释放IRQ
当设备不需要中断线时,需要释放中断线,中断信号线是非常紧缺的。
与request_irq()向对应的函数为free_irq(),free_irq()的原型如下:
void free_irq(unsigned int irq,void *dev_id);
free_irq()中参数的定义与request_irq()相同。
3.4、底半部机制
Linux系统实现底半部的机制主要有tasklet、工作队列和软中断(后续补充)
3.5、