中断分类
1)根据中断来源,内部中断和外部中断,内部中断的中断源来自CPU内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助CPU内部的软件中断),外部中断的中断源来自CPU外部,由外设提出请求。
2)根据中断是否可以屏蔽,中断可分为可屏蔽中断与不可屏蔽中断(NMI),可屏蔽中断可以通过设置中断控制器寄存器等方法被屏蔽,屏蔽后,该中断不再得到响应,而不可屏蔽中断不能被屏蔽。
3)根据中断入口跳转方法,中断可分为向量中断和非向量中断。采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入该入口地址后,再通过软件判断中断标志来识别具体是哪个中断。也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
linux中断处理程序架构
Linux将中断处理程序分解为两个半部:顶半部(Top Half)和底半部(Bottom Half)。
顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,从而可以服务更多的中断请求。
底半部,需用它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
linux中断编程
申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);
irq是要申请的硬件中断号。handler是向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用这个函数,dev参数将被传递给它。rqflags是中断处理的属性,可以指定中断的触发方式以及处理方式。
request_irq()返回0表示成功,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id);
devm_开头的API申请的是内核“managed”的资源,一般不需要在出错处理和remove()接口里再显式的释放。
释放中断
void free_irq(unsigned int irq,void *dev_id);
参数与申请中断函数一样。
中断底半部处理机制
tasklet
方法一
struct tasklet_struct //定义tasklet结构体
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data); //初始化tasklet结构体
方法二
void my_tasklet_func(unsigned long); /* 定义一个处理函数 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); //一次性初始化tasklet
调度tasklet
在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:
tasklet_schedule(&my_tasklet);
tasklet使用模板
/* 定义 tasklet 和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中断处理底半部 */
void xxx_do_tasklet(unsigned long)
{
...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
tasklet_schedule(&xxx_tasklet);
...
}
/* 设备驱动模块加载函数 */
int __init xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ...
return IRQ_HANDLED;
}
/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
}
工作队列
工作队列的使用方法和tasklet非常相似,tasklet是基于软中断实现的,因此也运行于软中断上下文,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。
struct work_struct my_wq; /* 定义一个工作队列 */
void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */
通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:
INIT_WORK(&my_wq, my_wq_func); /* 初始化工作队列并将其与处理函数绑定 */
与tasklet_schedule()对应的用于调度工作队列执行的函数为schedule_work()
schedule_work(&my_wq); /* 调度工作队列执行 */
使用模板与工作队列类似。