在之前接觸單片機時就接觸到了中斷這一個名詞,簡單了解,在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()
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = ;
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
switch (ret) {
case IRQ_WAKE_THREAD:
ret = IRQ_HANDLED;
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
代碼中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()
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)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
......
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(irq, desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(irq, desc);
......
return retval;
}
在代碼1081行中,中斷服務程式handler的位址被儲存在action的handler成員中。結合前面跟蹤的中斷調用流程,在将來中斷産生,将執行我們通過request_irq注冊的中斷服務程式。
一般來說,我們在驅動程式中的init函數中調用request_irq(),将我們編寫的中斷服務程式handler注冊到系統中;當中斷産生後,系統經過一系列的調用,最終将會執行我們的中斷服務程式handler,完成對中斷的響應。
在驅動程式的exit函數中調用free_irq(),将注冊到系統中的中斷服務程式handler移除。