天天看點

Linux Kernel Interrupt 分析

在之前接觸單片機時就接觸到了中斷這一個名詞,簡單了解,在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移除。

繼續閱讀