【主要內容】
Linux設備驅動編程中的中斷與定時器處理
【正文】
一、基礎知識
1、中斷
所謂中斷是指CPU在執行程序的過程中,出現了某些突發事件急待處理,CPU必須暫停執行當前的程序,轉去處理突發事件,處理完畢后CPU又返回程序被中斷的位置並繼續執行。
2、中斷的分類
1)根據中斷來源分為:內部中斷和外部中斷。內部中斷來源於CPU內部(軟中斷指令、溢出、語法錯誤等),外部中斷來自CPU外部,由設備提出請求。
2)根據是否可被屏蔽分為:可屏蔽中斷和不可屏蔽中斷(NMI),被屏蔽的中斷將不會得到響應。
3)根據中斷入口跳轉方法分為:向量中斷和非向量中斷。向量中斷為不同的中斷分配不同的中斷號,非向量中斷多個中斷共享一個中斷號,在軟件中判斷具體是哪個中斷(非向量中斷由軟件提供中斷服務程序入口地址)。
二、Linux中斷處理程序架構
設備的中斷會打斷內核中正常調度和運行,系統對更高吞吐率的追求勢必要求中斷服務程序盡可能的短小(時間短),但是在大多數實際使用中,要完成的工作都是復雜的,它可能需要進行大量的耗時工作。
1、Linux中斷處理中的頂半部和底半部機制
由於中斷服務程序的執行並不存在於進程上下文,因此,要求中斷服務程序的時間盡可能的短。 為了在中斷執行事件盡可能短和中斷處理需完成大量耗時工作之間找到一個平衡點,Linux將中斷處理分為兩個部分:頂半部(top half)和底半部(bottom half)。
Linux中斷處理機制
頂半部完成盡可能少的比較緊急的功能,它往往只是簡單地讀取寄存器中的中斷狀態並清除中斷標志后進行“登記中斷”的工作。“登記”意味着將底半部的處理程序掛載到該設備的底半部指向隊列中去。底半部作為工作重心,完成中斷事件的絕大多數任務。
a. 底半部可以被新的中斷事件打斷,這是和頂半部最大的不同,頂半部通常被設計成不可被打斷
b. 底半部相對來說不是非常緊急的,而且相對比較耗時,不在硬件中斷服務程序中執行。
c. 如果中斷要處理的工作本身很少,所有的工作可在頂半部全部完成
三、中斷編程
1、申請和釋放中斷
在Linux設備驅動中,使用中斷的設備需要申請和釋放相對應的中斷,分別使用內核提供的 request_irq() 和 free_irq() 函數
a. 申請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 失敗返回 - 負值(絕對值為錯誤碼) */
b. 釋放IRQ
void free_irq(unsigned int irq, void *dev_id); /* 參數參見申請IRQ */
2、屏蔽和使能中斷
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);
3、底半部機制
Linux實現底半部機制的的主要方式有 Tasklet、工作隊列和軟中斷
a. 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);
b. 工作隊列 workqueue
工作隊列與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);
c. 軟中斷
軟中斷(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底半部機制的函數。
