在之前接触单片机时就接触到了中断这一个名词,简单理解,在CPU正常运行程序A时,突然插入了程序B,程序B就可以理解为一个中断。在linux kernel中也提供中断机制,用于处理上述突发事件。
一、中断入口
在linux kernel亦有异常向量表,以linux-2.6.32.50中的ARM架构为例,在arch/arm/kernel/entry-armv.S中存在如下的异常向量表。
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
当中断产生,将进入vector_irq 中进行中断处理,对应的代码如下,其中,vector_stub为宏定义。
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
对于管理模式与用户模式中发生的中断,其对应的处理函数不同,分别为__irq_svc与__irq_usr。
二、中断的调用过程
以__irq_usr为例。
__irq_usr => irq_handler => asm_do_IRQ => generic_handle_irq(irq) => generic_handle_irq_desc() => __do_IRQ(irq) => handle_IRQ_event()
370 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
371 {
372 irqreturn_t ret, retval = IRQ_NONE;
373 unsigned int status = 0;
374
375 if (!(action->flags & IRQF_DISABLED))
376 local_irq_enable_in_hardirq();
377
378 do {
379 trace_irq_handler_entry(irq, action);
380 ret = action->handler(irq, action->dev_id);
381 trace_irq_handler_exit(irq, action, ret);
382
383 switch (ret) {
384 case IRQ_WAKE_THREAD:
389 ret = IRQ_HANDLED;
395 if (unlikely(!action->thread_fn)) {
396 warn_no_thread(irq, action);
397 break;
398 }
399
408 if (likely(!test_bit(IRQTF_DIED,
409 &action->thread_flags))) {
410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
411 wake_up_process(action->thread);
412 }
413
414 /* Fall through to add to randomness */
415 case IRQ_HANDLED:
416 status |= action->flags;
417 break;
418
419 default:
420 break;
421 }
422
423 retval |= ret;
424 action = action->next;
425 } while (action);
426
427 if (status & IRQF_SAMPLE_RANDOM)
428 add_interrupt_randomness(irq);
429 local_irq_disable();
430
431 return retval;
432 }
代码中380行执行中断服务程序,该中断服务程序是由我们编写并且注册系统中。通过这样的一个过程,完成了对外部硬件中断的响应。
三、中断服务程序注册
linux kernel中提供了一组函数用于完成对中断服务程序的注册与释放,request_irq()与free_irq()。
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
该函数有五个参数,其说明如下:
irq—要分配的设备号
handler—指向实际的中断服务程序的函数指针
typedef irqreturn_t (*irq_handler_t)(int, void *);
flags—中断处理程序的标志。定义在include/linux/interrupt.h
name—中断名
dev—用于共享中断线,是handler的参数。如果不用共享中断线,该参数可设为NULL
中断服务程序的注册过程如下:
request_irq() => request_threaded_irq()
1028 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1029 irq_handler_t thread_fn, unsigned long irqflags,
1030 const char *devname, void *dev_id)
1031 {
1032 struct irqaction *action;
1033 struct irq_desc *desc;
1034 int retval;
1035
......
1081 action->handler = handler;
1082 action->thread_fn = thread_fn;
1083 action->flags = irqflags;
1084 action->name = devname;
1085 action->dev_id = dev_id;
1086
1087 chip_bus_lock(irq, desc);
1088 retval = __setup_irq(irq, desc, action);
1089 chip_bus_sync_unlock(irq, desc);
1090
......
1093
1113 return retval;
1114 }
在代码1081行中,中断服务程序handler的地址被保存在action的handler成员中。结合前面跟踪的中断调用流程,在将来中断产生,将执行我们通过request_irq注册的中断服务程序。
一般来说,我们在驱动程序中的init函数中调用request_irq(),将我们编写的中断服务程序handler注册到系统中;当中断产生后,系统经过一系列的调用,最终将会执行我们的中断服务程序handler,完成对中断的响应。
在驱动程序的exit函数中调用free_irq(),将注册到系统中的中断服务程序handler移除。