“小王,醒醒,開始上課了,今天咱們開始講中斷,這可是進階東西,錯過不補哈”我使勁推著睡夢中的小王。
“嗯?感情好啊,快點,快點”小王一聽有新東西講,像打了雞血似的興奮,連我都懷疑起她是不是性格中喜新厭舊。
不管那麼多了,我講我的,她厭她的…
啥叫中斷?就是指cpu在執行過程中,出現了某些突發事件時CPU必須暫停執行當前的程式,轉去處理突發事件,處理完畢後CPU有返回原程式被中斷的位置並繼續執行。
中斷的分法不懂,分類就不同,向什麼內外部中斷,可/不可屏蔽中斷…等等亂七八糟一大堆,我這裡要說明的一點是按照中斷入口跳轉方法的不同,可分為向量中斷和非向量中斷。採用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行。不同的中斷號有不同的中斷地址(即入口)。而非向量中斷的多個中斷共用一個入口地址。進入後根據軟體判斷中斷標誌來識別具體是哪個中斷。也就是說,向量中斷是由硬體提供中斷服務程式入口地址,非向量中斷由軟體提供中斷服務程式入口地址。
我們在後邊會說到一個時鐘定時器,它也是通過中斷來實現的。它的原理很簡單,嵌入式微處理器它接入一個時鐘輸入,當時鐘脈衝到來時,就將目前的計數器值加1並和預先設定的計數值比較,若相等,證明計數周期滿,產生定時器中斷並複位目前計數器值。
Linux中斷處理架構
裝置的中斷會打斷核心中進程的正常調度和運行,會影響系統的效能。為了在中斷執行時間儘可能短和中斷處理需完成大量工作之間找到一個平衡點,Linux將中斷處理常式分解成兩個半部:頂半部和底半部。其中頂半部儘可能完成儘可能少的比較緊急的功能。而底半部幾乎做了中斷處理常式所有的事情,而且可以被新的中斷打斷。
在linux裝置驅動中,提供了一系列函數來協助裝置實現中斷的相關操作:
1)裝置申請中斷
int request_irq(unsigned int irq, //irq是要申請的中斷號
void (*handler)(int irq, void *dev_id, struct pt_regs * *regs),//回呼函數,中斷髮生時,系統會調用該函數,
unsigned long irqflags,
const char *devname,
void *dev_id);
其中irqflags是中斷處理的屬性,若設定為SA_INTERRUPT,則表示中斷處理常式是快速處理常式,它被調用時屏蔽所有中斷。若設定為SA_SHIRQ,則表示多個裝置共用中斷,dev_id在中斷共用時會用到,一般設定為這個裝置的裝置結構體或者NULL.
該函數返回0表示成功,返回-INVAL表示中斷號無效或處理函數指標為NULL,返回EBUSY表示中斷已經被佔用且不能共用。
2)釋放中斷
free_irq(unsigned int irq, void *dev_id);
3)使能和屏蔽中斷
void disable_irq(int irq); //這個會立即返回
void disable_irq_nosync(int irq);//等待目前的中斷處理完成再返回。
void enable_irq(int irq);
上述三個函數作用於可程式化中斷處理器,因此對系統內所有的CPU都生效。
void local_irq_save(unsigned long flags);//會將目前的中斷狀態保留在flags中
void local_irq_disable(void);//直接中斷
這兩個將屏蔽本CPU內的所有中斷。對應的上邊兩個中斷的方法如下
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
我們兩邊說了Linux系統中中斷是分為頂半部和底半部的,那麼在系統實現方面是具體怎樣實現的呢,這主要有tasklet,工作隊列,非強制中斷:
1)tasklet:使用比較簡單,如下:
void my_tasklet_function(unsigned long); //定義一個處理函數
DECLARE_TASKLET(my_tasklet, my_tasklet_function, data); //定義了一個名叫my_tasklet的tasklet並將其與處理函數綁定,而傳入參數為data
在需要調度tasklet的時候引用一個tasklet_schedule()函數就能使系統在適當的時候進行調度運行:tasklet_schedule(&my_tasklet);
2)工作隊列:使用方法和tasklet相似,如下:
struct work_struct my_wq; //定義一個工作隊列
void my_wq_func(unsigned long); //定義一個處理函數
通過INIT_WORK()可以初始化這個工作隊列並將工作隊列與處理函數綁定,如下:
INIT_WORK(&my_wq, (void (*)(void *))my_wq_func, NULL); //初始化工作隊列並將其與處理函數綁定
同樣,使用schedule_work(&my_irq);來在系統在適當的時候需要調度時使用運行。
3)非強制中斷:使用軟體方式類比硬體中斷的概念,實現宏觀上的非同步執行效果,tasklet也是基於非強制中斷實現的。
在Linux核心中,用softirq_action結構體表徵一個非強制中斷,這個結構體中包含非強制中斷處理函數指標和傳遞給函數的參數,使用open_softirq()可以註冊非強制中斷對應的處理函數,而raise_softirq()函數可以觸發一個中斷。
非強制中斷和tasklet仍然運行與中斷上下文,而工作隊列則運行於進程上下文。因此,非強制中斷和tasklet的處理函數不能休眠,但工作隊列是可以的。
local_bh_disable()和local_bh_enable()是核心用于禁止和使能非強制中斷和tasklet底半部機制的函數。
下邊咱們再來說說有關中斷共用的相關點:中斷共用即是多個裝置共用一根硬體中斷線的情況。Linux2.6核心支援中斷共用,使用方法如下:
*共用中斷的多個裝置在申請中斷時都應該使用SA_SHIRQ標誌,而且一個裝置以SA_SHIRQ申請某中斷成功的前提是之前該中斷的所有裝置也都以SA_SHIRQ標誌申請該終端
*儘管核心模組可訪問的全域地址都可以作為request_irq(….,void *dev_id)的最後一個參數dev_id,但是裝置結構體指標是可傳入的最佳參數。
*在中斷帶來時,所有共用此中斷的中斷處理常式都會被執行,在中斷處理常式頂半部中,應迅速根據硬體寄存器中的資訊比照傳入的dev_id參數判斷是否是被裝置的中斷,如果不是,應迅速返回。
結語:在這次講解中說了三種Linux系統中中斷的頂/底半部機制和中斷共用的先關內容,但礙於頁面空間的原因,沒有給出例子,我在下次部落格中會專門來對每個點給出典型的模版.