首頁 > 軟體

Linux裝置驅動之中斷處理

2020-06-16 17:26:28

中斷(interrupt)是指CPU在執行程式的過程中,出現了某些突發事件急待處理,CPU必須暫停執行當前的程式,轉去處理突發事件,處理完畢後CPU又返回原程式被中斷的位置並繼續執行。
中斷服務程式的執行並不存在於進程上下文,因此,要求中斷服務程式的時間盡可能地短。因此,Linux在中斷處理中引入了頂半部和底半部分離的機制。頂半部處理緊急的硬體操作,底半部處理不緊急的耗時操作。
tasklet和工作佇列都是排程中斷底半部的良好機制,tasklet基於軟中斷實現,原子操作,速度快,常用,但不可阻塞或睡眠。


中斷分類

  • 根據中斷的來源,可分為內部中斷和外部中斷

            內部中斷的中斷源來自CPU內部(軟體中斷指令、溢位、除法錯誤等,例如,作業系統從使用者態切換到核心態需借助CPU內部的軟體中斷),外部中斷的中斷源來自CPU外部,由外設提出請求。
  • 根據中斷是否可以遮蔽分為可遮蔽中斷與不遮蔽中斷(NMI)

            可遮蔽中斷可以通過遮蔽字(MASK)被遮蔽,遮蔽後,該中斷不再得到響應,而不遮蔽中斷不能被遮蔽。
  • 根據中斷入口跳轉方法的不同,分為向量中斷和非向量中斷

            採用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行。不同中斷號的中斷有不同的入口地址。非向量中斷的多個中斷共用一個入口地址,進入該入口地址後再通過軟體判斷中斷標誌來識別具體是哪個中斷。也就是說,向量中斷由硬體提供中斷服務程式入口地址,非向量中斷由軟體提供中斷服務程式入口地址。

Linux中斷處理(Interrupt Handling)架構

裝置的中斷會打斷核心中進程的正常排程和執行,系統對更高吞吐率的追求勢必要求中斷服務程式盡可能的短小精悍。為了在中斷執行時間盡可能短和中斷處理需完成大量工作之間找到一個平衡點, Linux 將中斷處理程式分解為兩個半部:頂半部(top half)和底半部(bottom half)。

  • top half

            頂半部完成盡可能少的比較緊急的功能,它往往只是簡單地讀取暫存器中的中斷狀態並清除中斷標誌後就進行“登記中斷”的工作。“登記中斷”意味著將底半部處理程式掛到該裝置的底半部執行佇列中去。這樣,頂半部執行的速度就會很快,可以服務更多的中斷請求。軟體上一般採用handler中斷響應程式實現。
  • bottom half

            底半部由頂半部排程而來進行延後處理,幾乎做了中斷處理程式所有的事情,而且可以被新的中斷打斷,這也是底半部和頂半部的最大不同,因為頂半部往往被設計成不可中斷。底半部則相對來說並不是非常緊急的,而且相對比較耗時,不在硬體中斷服務程式中執行。軟體上一般採用tasklet或工作佇列機制。

Tip:儘管頂半部、底半部的結合能夠改善系統的響應能力,但是,僵化地認為 Linux 裝置驅動中的中斷處理一定要分兩個半部則是不對的。如果中斷要處理的工作本身很少,則完全可以直接在頂半部全部完成。


Linux中斷程式設計

申請和釋放(Installing an Interrupt Handler)

    #include <linux/interrupt.h>

    int /* 返回 0 -- OK, -EINVAL -- irq/handler invalid, -EBUSY -- 中斷被占用不能共用 */
    request_irq(
            unsigned int irq,       /* 要申請的硬體中斷號 */
            irq_handler_t handler,  /* 向系統登記的中斷處理常式(頂半部)*/
            unsigned long flags,    /* 中斷處理的屬性,可以指定中斷的觸發方式以及處理方式等 */
            const char *devname,    /* used in /proc/interrupts */   
            void *dev_id            /* 傳遞給handler的引數 */
            );

    void free_irq(unsigned int irq,void *dev_id);

Tip: 如果中斷確定不被共用可將其安裝在初始化中,否則應安裝在開啟函數中。interrupt.h有irq_handler_t的定義及flags的詳細註釋

使能和遮蔽 (Enabling and Disabling Interrupts)

  • Disabling a single interrupt

    #include <asm/irq.h>
    
    /* disable並等待指定的中斷被處理完,如果呼叫執行緒佔有interrupt handler需要的資源如spinlock那麼就會死鎖 */
    void disable_irq(int irq); 
    
    /* disable並立即返回,有可能產生競態 */
    void disable_irq_nosync(int irq);
    
    void enable_irq(int irq);
  • Disabling all interrupts

    #include <asm/system.h>
    
    void local_irq_save(unsigned long flags); /* save flags then disable local all */
    void local_irq_disable(void); /* disable local all directly */
    
    void local_irq_restore(unsigned long flags);
    void local_irq_enable(void);

底半部機制

  • tasklet

    void my_tasklet_func(unsigned long); /*定義一個處理常式*/
    
    /* 定義一個tasklet結構my_tasklet,並與my_tasklet_func(data)處理常式相關聯 */
    DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
    
    /* 在需要排程tasklet的時候參照一個tasklet_schedule()函數就能使系統在適當的時候進行排程執行 
       一般在top half即中斷響應函數中呼叫 */
    tasklet_schedule(&my_tasklet);
  • 工作佇列(workqueues)
    與tasklet類似:

    void my_wq_func(unsigned long); /*定義一個處理常式*/
    
    struct work_struct my_wq; /*定義一個工作佇列*/
    /* 初始化工作佇列並將其與處理常式係結 */
    INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL);
    
    schedule_work(&my_wq);/*排程工作佇列執行*/

Tip: tasklet在中斷上下文中執行,不可阻塞或睡眠;而workqueues由核心執行緒去執行,屬進程上下文,可阻塞和睡眠。

本文永久更新連結地址http://www.linuxidc.com/Linux/2016-12/138134.htm


IT145.com E-mail:sddin#qq.com