【主要內容】編程
Linux設備驅動編程中的中斷與定時器處理架構
【正文】函數
所謂中斷是指CPU在執行程序的過程當中,出現了某些突發事件急待處理,CPU必須暫停執行當前的程序,轉去處理突發事件,處理完畢後CPU又返回程序被中斷的位置並繼續執行。atom
1)根據中斷來源分爲:內部中斷和外部中斷。內部中斷來源於CPU內部(軟中斷指令、溢出、語法錯誤等),外部中斷來自CPU外部,由設備提出請求。spa
2)根據是否可被屏蔽分爲:可屏蔽中斷和不可屏蔽中斷(NMI),被屏蔽的中斷將不會獲得響應。設計
3)根據中斷入口跳轉方法分爲:向量中斷和非向量中斷。向量中斷爲不一樣的中斷分配不一樣的中斷號,非向量中斷多箇中斷共享一箇中斷號,在軟件中判斷具體是哪一個中斷(非向量中斷由軟件提供中斷服務程序入口地址)。指針
設備的中斷會打斷內核中正常調度和運行,系統對更高吞吐率的追求勢必要求中斷服務程序儘量的短小(時間短),可是在大多數實際使用中,要完成的工做都是複雜的,它可能須要進行大量的耗時工做。rest
因爲中斷服務程序的執行並不存在於進程上下文,所以,要求中斷服務程序的時間儘量的短。 爲了在中斷執行事件儘量短和中斷處理需完成大量耗時工做之間找到一個平衡點,Linux將中斷處理分爲兩個部分:頂半部(top half)和底半部(bottom half)。code
Linux中斷處理機制blog
頂半部完成儘量少的比較緊急的功能,它每每只是簡單地讀取寄存器中的中斷狀態並清除中斷標誌後進行「登記中斷」的工做。「登記」意味着將底半部的處理程序掛載到該設備的底半部指向隊列中去。底半部做爲工做重心,完成中斷事件的絕大多數任務。
a. 底半部能夠被新的中斷事件打斷,這是和頂半部最大的不一樣,頂半部一般被設計成不可被打斷
b. 底半部相對來講不是很是緊急的,並且相對比較耗時,不在硬件中斷服務程序中執行。
c. 若是中斷要處理的工做自己不多,全部的工做可在頂半部所有完成
在Linux設備驅動中,使用中斷的設備須要申請和釋放相對應的中斷,分別使用內核提供的 request_irq() 和 free_irq() 函數
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) /* 參數: ** irq:要申請的硬件中斷號 ** handler:中斷處理函數(頂半部) ** irqflags:觸發方式及工做方式 ** 觸發:IRQF_TRIGGER_RISING 上升沿觸發 ** IRQF_TRIGGER_FALLING 降低沿觸發 ** IRQF_TRIGGER_HIGH 高電平觸發 ** IRQF_TRIGGER_LOW 低電平觸發 ** 工做:不寫:快速中斷(一個設備佔用,且中斷例程回調過程當中會屏蔽中斷) ** IRQF_SHARED:共享中斷 ** dev_id:在共享中斷時會用到(中斷註銷與中斷註冊的此參數應保持一致) ** 返回值:成功返回 - 0 失敗返回 - 負值(絕對值爲錯誤碼) */
void free_irq(unsigned int irq, void *dev_id); /* 參數參見申請IRQ */
void disable_irq(int irq); //屏蔽中短、當即返回 void disable_irq_nosync(int irq); //屏蔽中斷、等待當前中斷處理結束後返回 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void enable_irq(int irq); //使能中斷
全局中斷使能和屏蔽函數(或宏)
屏蔽:
#define local_irq_save(flags) ... void local irq_disable(void );
使能:
#define local_irq_restore(flags) ... void local_irq_enable(void);
Linux實現底半部機制的的主要方式有 Tasklet、工做隊列和軟中斷
Tasklet使用簡單,只須要定義tasklet及其處理函數並將兩者關聯便可,例如:
void my_tasklet_func(unsigned long); /* 定義一個處理函數 */ DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/* 定義一個名爲 my_tasklet 的 struct tasklet 並將其與 my_tasklet_func 綁定,data爲傳入 my_tasklet_func的參數 */
只須要在頂半部中電泳 tasklet_schedule()函數就能使系統在適當的時候進行調度運行
tasklet_schedule(struct tasklet *xxx_tasklet);
tasklet使用模版
/* 定義 tasklet 和底半部函數並關聯 */ void xxx_do_tasklet(unsigned long data); DECLARE_TASKLET(xxx_tasklet, xxx_tasklet_func, data); /* 中斷處理底半部 */ void xxx_tasklet_func() { /* 中斷處理具體操做 */ } /* 中斷處理頂半部 */ irqreturn xxx_interrupt(int irq, void *dev_id) { //do something task_schedule(&xxx_tasklet); //do something
return IRQ_HANDLED; } /* 設備驅動模塊 init */ int __init xxx_init(void) { ... /* 申請設備中斷 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL); ... return 0; } module_init(xxx_init); /* 設備驅動模塊exit */ void __exit xxx_exit(void) { ... /* 釋放中斷 */ free_irq(xxx_irq, NULL); }
module_exit(xxx_exit);
工做隊列與tasklet方法很是相似,使用一個結構體定義一個工做隊列和一個底半部執行函數:
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
struct work_struct my_wq; /* 定義一個工做隊列 */ void my_wq_func(unsigned long); /*定義一個處理函數 */
經過INIT_WORK()能夠初始化這個工做隊列並將工做隊列與處理函數綁定(通常在模塊初始化中使用):
void INIT_WORK(struct work_struct *my_wq, work_func_t); /* my_wq 工做隊列地址 ** work_func_t 處理函數 */
與tasklet_schedule_work ()對應的用於調度工做隊列執行的函數爲schedule_work()
schedule_work(&my_wq);
工做隊列使用模版
/* 定義工做隊列和關聯函數 */ struct work_struct xxx_wq; void xxx_do_work(unsigned long); /* 中斷處理底半部 */ void xxx_work(unsigned long) { /* do something */ } /* 中斷處理頂半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id) { ... schedule_work(&xxx_wq); ... return IRQ_HANDLED; } /* 設備驅動模塊 init */ int __init xxx_init(void) { ... /* 申請設備中斷 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL); /* 初始化工做隊列 */ INIT_WORK(&xxx_wq, xxx_do_work); ... return 0; } module_init(xxx_init); /* 設備驅動模塊exit */ void __exit xxx_exit(void) { ... /* 釋放中斷 */ free_irq(xxx_irq, NULL); } module_exit(xxx_exit);
軟中斷(softirq)也是一種傳統的底半部處理機制,它的執行時機一般是頂半部返回的時候,tasklet的基於軟中斷實現的,所以也運行於軟中斷上下文。
在Linux內核中,用softirq_action結構體表徵一個軟中斷,這個結構體中包含軟中斷處理函數指針和傳遞給該函數的參數。使用open_softirq()函數能夠註冊軟中斷對應的處理函數,而raise_softirq()函數能夠觸發一個軟中斷。
struct softirq_action { void (*action)(struct softirq_action *); };
void open_softirq(int nr, void (*action)(struct softirq_action *)); /* 註冊軟中斷 */ void raise_softirq(unsigned int nr); /* 觸發軟中斷 */
local_bh_disable() 和 local_bh_enable() 是內核中用於禁止和使能軟中斷和tasklet底半部機制的函數。