中斷(interrupt)是指CPU在執行程式的過程中,出現了某些突發事件急待處理,CPU必須暫停執行當前的程式,轉去處理突發事件,處理完畢後CPU又返回原程式被中斷的位置並繼續執行。中斷服務程式的執行並不存在於進程上下文,因此,要求中斷服務程式的時間儘可能地短。因此,Linux在中斷處理中引入了頂... ...
中斷(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由內核線程去執行,屬進程上下文,可阻塞和睡眠。
References
1. Linux Device Drivers
2. Linux設備驅動開發詳解(宋寶華第二版)
Copyright (C) 2016 archiexie@cnblogs. All Rights Reserved.