在arm多核处理器中最常用的中断处理器GIC(generic interrupt controller),如图所示:
SGI(Software Generated Interrupt):软件产生的中断,可以用于多核的核间通信,一个CPU可以通过写GIC的寄存器给另外一个CPU产生中断。多核调度用的IPI_WAKEUP、IPI_TIMER、IPI_RESCHEDULE、IPI_CALL_FUNC、IPI_CALL_FUNC_SINGLE、IPI_CPU_STOP、IPI_IRQ_WORK、IPI_COMPLETION都是由SGI产生的。
PPI(Private Peripheral Interrupt):某个CPU私有外设的中断,这类外设的中断只能发给绑定的那个CPU.
SPI(Shared Peripheral Interrupt):共享外设的中断,这类外设的中断可以路由到任何一个CPU。
linux内核为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部(Top Half)和底半部(Bottom Half)。
其中上半部一般被设置为不可中断,下半部被设置为可中断。在/proc/interrupts虚拟文件中统计了系统中断在哪个cpu执行的信息。Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq。
在中断下半部使用tasklet的时候,等到中断处理函数退出的时候,tasklet绑定的tasklet_schedule()函数会在系统适当的时候调度运行自己定义的tasklet函数。 tasklet使用模板(来源于宋宝华老师的设备驱动书籍内容):
1/* 定义 tasklet 和底半部函数并将它们关联 */ 2void xxx_do_tasklet(unsigned long); 3DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0); 4 5/* 中断处理底半部 */ 6void xxx_do_tasklet(unsigned long) 7{ 8 ... 9} 10 11/* 中断处理顶半部 */ 12irqreturn_t xxx_interrupt(int irq, void *dev_id) 13{ 14 ... 15 tasklet_schedule(&xxx_tasklet); 16 ... 17} 18 19/* 设备驱动模块加载函数 */ 20int __init xxx_init(void) 21{ 22 ... 23 /* 申请中断 */ 24 result = request_irq(xxx_irq, xxx_interrupt, 25 0, "xxx", NULL); 26 ... 27 return IRQ_HANDLED; 28} 29 30/* 设备驱动模块卸载函数 */ 31void __exit xxx_exit(void) 32{ 33 ... 34 /* 释放中断 */ 35 free_irq(xxx_irq, xxx_interrupt);36 ... 37}工作队列的使用方法和tasklet非常相似,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。 使用模板(来源于宋宝华老师的设备驱动书籍内容):
1/* 定义工作队列和关联函数 */ 2struct work_struct xxx_wq; 3void xxx_do_work(struct work_struct *work); 4 5/* 中断处理底半部 */ 6void xxx_do_work(struct work_struct *work) 7{ 8 ... 9} 10 11/* 中断处理顶半部 */ 12irqreturn_t xxx_interrupt(int irq, void *dev_id) 13{ 14 ... 15 schedule_work(&xxx_wq); 16 ... 17 return IRQ_HANDLED; 18} 19 20/* 设备驱动模块加载函数 */ 21int xxx_init(void) 22{ 23 ... 24 /* 申请中断 */ 25 result = request_irq(xxx_irq, xxx_interrupt, 26 0, "xxx", NULL); 27 ... 28 /* 初始化工作队列 */ 29 INIT_WORK(&xxx_wq, xxx_do_work); 30 ...31} 32 33/* 设备驱动模块卸载函数 */ 34void xxx_exit(void) 35{ 36 ... 37 /* 释放中断 */ 38 free_irq(xxx_irq, xxx_interrupt); 39 ... 40}在内核中,除了可以通过request_irq()、devm_request_irq()申请中断以外,还可以通过 request_threaded_irq()和devm_request_threaded_irq()申请。
共享中断编程模板(来源于宋宝华老师的设备驱动书籍内容):
/* 中断处理顶半部 */ 2irqreturn_t xxx_interrupt(int irq, void *dev_id) 3{ 4 ... 5 int status = read_int_status(); /* 获知中断源 */ 6 if(!is_myint(dev_id,status)) /* 判断是否为本设备中断 */ 7 return IRQ_NONE; /* 不是本设备中断,立即返回 */ 8 9 /* 是本设备中断,进行处理 */ 10 ... 11 return IRQ_HANDLED; /* 返回 IRQ_HANDLED 表明中断已被处理 */ 12} 13 14/* 设备驱动模块加载函数 */ 15int xxx_init(void) 16{ 17 ... 18 /* 申请共享中断 */ 19 result = request_irq(sh_irq, xxx_interrupt, 20 IRQF_SHARED, "xxx", xxx_dev); 21 ... 22} 23 24/* 设备驱动模块卸载函数 */ 25void xxx_exit(void) 26{27 28 29 30 31} ... /* 释放中断 */ free_irq(xxx_irq, xxx_interrupt); ...软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。 内核定时器使用模板(来源于宋宝华老师的设备驱动书籍内容):
1/* xxx 设备结构体 */ 2struct xxx_dev { 3 struct cdev cdev; 4 ... 5 timer_list xxx_timer; /* 设备要使用的定时器 */ 6}; 7 8/* xxx 驱动中的某函数 */ 9xxx_func1( ... ) 10{ 11 struct xxx_dev *dev = filp->private_data; 12 ... 13 /* 初始化定时器 */ 14 init_timer(&dev->xxx_timer); 15 dev->xxx_timer.function = &xxx_do_timer; 16 dev->xxx_timer.data = (unsigned long)dev; 17 /* 设备结构体指针作为定时器处理函数参数 */ 18 dev->xxx_timer.expires = jiffies + delay; 19 /* 添加(注册)定时器 */ 20 add_timer(&dev->xxx_timer); 21 ... 22} 23 24/* xxx 驱动中的某函数 */ 25xxx_func2( ... ) 26{ 27 ... 28 /* 删除定时器 */ 29 del_timer (&dev->xxx_timer); 30 ... 31} 32 33/* 定时器处理函数 */ 34static void xxx_do_timer(unsigned long arg) 35{ 36 struct xxx_device *dev = (struct xxx_device *)(arg); 37 ... 38 /* 调度定时器再执行 */ 39 dev->xxx_timer.expires = jiffies + delay; 40 add_timer(&dev->xxx_timer); 41 ... 42}