linux中斷處理過程

參考:http://www.uml.org.cn/embeded/201304021.asp

Linux中的中斷處理
 

發佈於2013-4-2

 

與Linux設備驅動中中斷處理相關的首先是申請與釋放IRQ的API request_irq()和free_irq(),

request_irq()的原型爲:

int request_irq(unsigned int irq,

void (*handler)(int irq, void *dev_id, struct pt_regs *regs),

unsigned long irqflags,

const char * devname,

void *dev_id);

irq是要申請的硬件中斷號;

handler是向系統登記的中斷處理函數,是一個回調函數,中斷髮生時,系統調用這個函數,dev_id參數將被傳遞;

irqflags是中斷處理的屬性,若設置SA_INTERRUPT,標明中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程 序不屏蔽;若設置SA_SHIRQ,則多個設備共享中斷,dev_id在中斷共享時會用到,一般設置爲這個設備的device結構本身或者NULL。

free_irq()的原型爲:

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

另外,與Linux中斷息息相關的一個重要概念是Linux中斷分爲兩個半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登記中斷",當一箇中斷髮生時,它進行相應地硬件讀寫後就把中斷例程的下半部掛到該設備的下半部執行隊列中去。因此,上半部 執行的速度就會很快,可以服務更多的中斷請求。但是,僅有"登記中斷"是遠遠不夠的,因爲中斷的事件可能很複雜。因此,Linux引入了一個下半部,來完 成中斷事件的絕大多數使命。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的,下半部幾乎做了中斷處理程序所有的事情,而且可以被新的 中斷打斷!下半部則相對來說並不是非常緊急的,通常還是比較耗時的,因此由系統自行安排運行時機,不在中斷服務上下文中執行。

原理解析:

1、 中斷概念

爲什麼需要中斷?

1)外設的處理速度一般慢於CPU

2)CPU不能一直等待外部事件

所以設備必須有一種方法來通知CPU它的工作進度,這種方法就是中斷。

2、 中斷實現

在Linux驅動程序中,爲設備實現一箇中斷包含兩個步驟:

1)向內核註冊中斷

2)實現中斷處理函數

3、 中斷註冊

request_irq用於實現中斷的註冊功能:

int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs *), unsigned long flags, const char *devname, void *dev_id)

返回0表示成功,或者返回一個錯誤碼

中斷註冊(參數)

1) unsigned int irq 中斷號。

2)void (*handler)(int,void *,struct pt_regs *) 中斷處理函數。

3)unsigned long flags 與中斷管理有關的各種選項。

4)const char * devname 設備名

5)void *dev_id 共享中斷時使用。

a)中斷註冊(中斷標誌)

在flags參數中,可以選擇一些與中斷管理有關的選項,如:

1)IRQF_DISABLED(SA_INTERRUPT)

如果設置該位,表示是一個“快速”中斷處理程序;如果沒有設置這位,那麼是一個“慢速”中斷處理程序。

2)IRQF_SHARED(SA_SHIRQ)

該位表明中斷可以在設備間共享。

b)快速/慢速中斷

這兩種類型的中斷處理程序的主要區別在於:快速中斷保證中斷處理的原子性(不被打斷),而慢速中斷則不保證。換句話說,也就是“開啓中斷”標誌位(處理器IF)在運行快速中斷處理程序時是關閉的,因此在服務該中斷時,不會被其他類型的中斷打斷;而調用慢速中斷處理時,其它類型的中斷仍可以得到服務。

c) 共享中斷

共享中斷就是將不同的設備掛到同一個中斷信號線上。Linux對共享的支持主要是爲PCI設備服務。共享中斷也是通過request_irq函數來註冊的,但有三個特別之處:

1)申請共享中斷時,必須在flags參數中指定 IRQF_SHARED位

2)dev_id參數必須是唯一的。

3)共享中斷的處理程序中,不能使用disable_irq(unsigned int irq) 爲什麼? 如果使用了這個函數,共享中斷信號線的其它設備將同樣無法使用中斷,也就無法正常工作了。

4、 中斷處理程序

什麼是中斷處理程序,有何特別之處?中斷處理程序就是普通的C代碼。特別之處在於中斷處理程序是在中斷上下文中運行的,它的行爲受到某些限制:

1) 不能向用戶空間發送或接受數據

2) 不能使用可能引起阻塞的函數

3) 不能使用可能引起調度的函數

5、 中斷處理函數流程

void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{/* 判斷是否是本設備產生了中斷(爲什麼要做這樣的檢測?) */

value = inb(short_base);

if (!(value & 0x80)) return;/* 清除中斷位(如果設備支持自動清除,則不需要這步) */

outb(value & 0x7F, short_base);/* 中斷處理,通常是數據接收 */

。。。。。。。。。/* 喚醒等待數據的進程 */

ake_up_interruptible(&short_queue);

6、 釋放中斷

當設備不再需要使用中斷時(通常在驅動卸載時), 應當把它們返還給系統,使用:void free_irq(unsigned int irq, void *dev_id)

中斷處理(下半部)--推後處理的部分

Linux中斷下半部處理有三種方式:軟中斷、tasklet、工作隊列。

一、最簡單的中斷機制

最簡單的中斷機制就是像芯片手冊上講的那樣,在中斷向量表中填入跳轉到對應處理函數的指令,然後在處理函數中實現需要的功能。類似下圖:

這種方式在原來的單片機課程中常常用到,一些簡單的單片機系統也是這樣用。
它的好處很明顯,簡單,直接。

二、下半部

中斷處理函數所作的第一件事情是什麼?答案是屏蔽中斷(或者是什麼都不做,因爲常常是如果不清除IF位,就等於屏蔽中斷了),當然只屏蔽同一種中斷。之所以要屏蔽中斷,是因爲新的中斷會再次調用中斷處理函數,導致原來中斷處理現場的破壞。即,破壞了 interrupt context。

隨着系統的不斷複雜,中斷處理函數要做的事情也越來越多,多到都來不及接收新的中斷了。於是發生了中斷丟失,這顯然不行,於是產生了新的機制:分離中斷接收與中斷處理過程。中斷接收在屏蔽中斷的情況下完成;中斷處理在時能中斷的情況下完成,這部分被稱爲中斷下半部。

從上圖中看,只看int0的處理。Func0爲中斷接收函數。中斷只能簡單的觸發func0,而func0則能做更多的事情,它與funcA之間可以使用隊列等緩存機制。當又有中斷髮生時,func0被觸發,然後發送一箇中斷請求到緩存隊列,然後讓funcA去處理。
由於func0做的事情是很簡單的,所以不會影響int0的再次接收。而且在func0返回時就會使能int0,因此funcA執行時間再長也不會影響int0的接收。

三、軟中斷

下面看看linux中斷處理。作爲一個操作系統顯然不能任由每個中斷都各自爲政,統一管理是必須的。

我們不可中斷部分的共同部分放在函數do_IRQ中,需要添加中斷處理函數時,通過request_irq實現。下半部放在do_softirq中,也就是軟中斷,通過open_softirq添加對應的處理函數。

四、tasklet

舊事物跟不上歷史的發展時,總會有新事物出現。隨着中斷數的不停增加,軟中斷不夠用了,於是下半部又做了進化。軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須循環完所有的中斷類型,才能最終執行對應的處理函數。顯然當年開發人員爲了保證輪詢的效率,於是限制中斷個數爲32個。

爲了提高中斷處理數量,順道改進處理效率,於是產生了tasklet機制。

  • Tasklet採用無差別的隊列機制,有中斷時才執行,免去了循環查表之苦。
  • CDMA因爲頻譜重疊問題,必須降功率。而功率降低後,又發現原來功率低了有助於環保。
  • Tasklet作爲一種新機制,顯然可以承擔更多的優點。正好這時候SMP越來越火了,因此又在tasklet中加入了SMP機制,保證同種中斷只
  • 在一個cpu上執行。在軟中斷時代,顯然沒有這種考慮。因此同一種中斷可以在兩個cpu上同時執行,很可能造成衝突。

總結下tasklet的優點:

(1)無類型數量限制;

(2)效率高,無需循環查表;

(3)支持SMP機制;

五、工作隊列

前面的機制不論如何折騰,有一點是不會變的。它們都在中斷上下文中。什麼意思?說明它們不可掛起。而且由於是串行執行,因此只要有一個處理時間較長,則會導致其他中斷響應的延遲。爲了完成這些不可能完成的任務,於是出現了工作隊列。工作隊列說白了就是一組內核線程,作爲中斷守護線程來使用。多箇中斷可以放在一個線程中,也可以每個中斷分配一個線程。工作隊列對線程作了封裝,使用起來更方便。
因爲工作隊列是線程,所以我們可以使用所有可以在線程中使用的方法。

Tasklet其實也不一定是在中斷上下文中執行,它也有可能在線程中執行。

假如中斷數量很多,而且這些中斷都是自啓動型的(中斷處理函數會導致新的中斷產生),則有可能cpu一直在這裏執行中斷處理函數,會導致用戶進程永遠得不到調度時間。

爲了避免這種情況,linux發現中斷數量過多時,會把多餘的中斷處理放到一個單獨的線程中去做,就是ksoftirqd線程。這樣又保證了中斷不多時的響應速度,又保證了中斷過多時不會把用戶進程餓死。

問題是我們不能保證我們的tasklet或軟中斷處理函數一定會在線程中執行,所以還是不能使用進程才能用的一些方法,如放棄調度、長延時等。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章