linux的中断
Linux的中断架构
设备的中断打断内核进程中的正常调度和执行,对于系统性能和吞吐率的更高追求必然要求中断服务程序尽可能短小精悍。但现实情况是,在大多数真实系统中,当中断到来时,中断服务程序需要进行大量的耗时处理。
linux内核实现上,为了在短小精悍与大量耗时处理中间找到一个平衡点,将中断处理程序分解为了两部分:顶半部(TOP Half)和底半部(Bottom Half)。
顶半部用于完成尽量少的紧急功能,包括读取寄存器中的中断状态和进行中断登记操作;而底半部则完成相应中断的具体功能及相关耗时工作。
这种分解操作能够使得中断服务能够支持更多的中断请求,在linux下,查看/proc/interrupts文件可以获得系统中断的统计信息。
linux中断编程
申请和释放中断
在linux设备驱动中,使用中断的设备需要申请和释放对应的中断,其接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| /* irq: 要申请的硬件中断号 * handler: 向系统登记的中断处理函数(顶半部) * flags: 中断处理的属性linux/interrupt.h中IRQF_* * name: 中断名 * 返回值:0表示成功 **/ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev); /* devm_*类型的API申请的资源是内核管理的资源,一般不需要显示释放 */ int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id);
/* 释放irq */ void *free_irq(unsigned int, void *);
|
使能和屏蔽中断
1 2 3 4 5 6 7 8 9 10 11
| /* 屏蔽中断irq,立即返回 */ extern void disable_irq_nosync(unsigned int irq); /* 屏蔽中断irq,等待当前中断处理完毕后返回 */ extern void disable_irq(unsigned int irq); /* 使能中断irq */ extern void enable_irq(unsigned int irq);
/* 使能所有中断 */ local_irq_enable() /* 屏蔽所有中断 */ local_irq_disable()
|
底半部机制
linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq。
Tasklet
tasklet的执行上下文是软中断,执行时机通常是顶半部返回的时候。要使用tasklet机制我们只需要定义tasklet及其处理函数,并将两者关联即可。相关代码为:
1 2 3 4 5 6
| void tasklet_handler(unsigned long); /* 定义一个tasklet结构tasklet,与tasklet_handler(data)相关联 */ DECLARE_TASKLET(tasklet, tasklet_handler, data); ... /* 需要调度tasklet时,进行调度 */ tasklet_schedule(&tasklet);
|
因此一个完整的tasklet使用模版如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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相似,它的执行上下文时内核线程因此可以调度和睡眠。其使用模版如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| struct work_struct xxx_wq; void xxx_do_work(struct work_struct *work);
/* 中断处理底半部 */ void xxx_do_work(struct work_struct *work) { ... }
/* 中断处理顶半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id) { ... schedule_work(&xxx_wq); ... return IRQ_HANDLED; }
/* 设备驱动加载模块 */ int __init xxx_init(void) { ... /* 申请中断 */ result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ... /* 初始化工作队列 */ INIT_WORK(&xxx_wq, xxx_do_work); ... return IRQ_HANDLED; }
/* 设备驱动卸载模块 */ void __exit xxx_exit(void) { ... /* 释放中断 */ free_irq(xxx_irq, xxx_interrupt); ... }
|
软中断
软中断也是一种传统的底半部处理机制,它的执行时机通常时顶半部返回的时候,tasklet是基于软中断实现的,因此tasklet的执行上下文是软中断的上下文。
软中断在内核中相关的结构体及接口如下所示:
1 2 3 4 5 6 7 8 9 10 11
| struct softirq_action { void (*action)(struct softirq_action *); };
void do_softirq(void); void softirq_init(void); /* 注册一个软中断的处理函数 */ void open_softirq(int nr, void (*action)(struct softirq_action *)); /* 触发一个软中断 */ void raise_softirq(unsigned int nr);
|
一般在驱动编写中,不宜直接使用软中断,如果需要应用软中断的机制,直接采用tasklet即可。
在系统中包含硬中断,软中断和信号。其中硬中断是外部设备对CPU的中断,软中断时中断底半部的一种处理机制,而信号则是系统对某个进程的中断。
系统调用则是通过软中断陷入内核实现的,此时的软中断是指软件指令引发的中断,与这里提到的软中断有区别。
threaded_irq
线程化irq涉及到如下两个接口:
1 2 3 4 5 6 7
| int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id);
|
这两个是在申请中断的同时让内核为相应中断号分配一个对应的内核线程。当中断的顶半部handler执行结束时,返回值时IRQ_WAKE_THREAD,内核会调度对应的内核线程将执行thread_fn。
中断共享
多个设备共享一根硬件中断线在实际系统中广泛存在。中断共享的方法如下:
- 共享中断的多个设备在申请中断时都采用IRQF_SHARED标志。
- 申请中断时通过dev_id参数来区分共享同一个中断的多个设备
- 中断到来时,遍历执行所有共享此中断的中断处理程序。直到返回IRQ_HANDLED,顶半部通过dev_id参数与寄存器信息比较是否为本设备中断,若不是,返回IRQ_NONE。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com