1. 程式人生 > >9.Linux核心設計與實現 P91---中斷和中斷處理程式 (轉)

9.Linux核心設計與實現 P91---中斷和中斷處理程式 (轉)

      中斷還是中斷,我講了很多次的中斷了,今天還是要講中斷,為啥呢?因為在作業系統中,中斷是必須要講的..

       那麼什麼叫中斷呢, 中斷還是打斷,這樣一說你就不明白了。唉,中斷還真是有點像打斷。我們知道linux管理所有的硬體裝置,要做的第一件事先是通訊。然後,我們天天在說一句話:處理器的速度跟外圍硬體裝置的速度往往不在一個數量級上,甚至幾個數量級的差別,這時咋辦,你總不能讓處理器在那裡傻等著你硬體做好了告訴我一聲吧。這很容易就和日常生活聯絡起來了,這樣效率太低,不如我處理器做別的事情,你硬體裝置準備好了,告訴我一聲就得了。這個告訴,咱們說的輕鬆,做起來還是挺費勁啊!怎麼著,簡單一點,輪訓(polling)可能就是一種解決方法,缺點是作業系統要做太多的無用功,在那裡傻傻的做著不重要而要重複的工作,這裡有更好的辦法---中斷,這個中斷不要緊,關鍵在於從硬體裝置的角度上看,已經實現了從被動為主動的歷史性突破。

       中斷的例子我就不說了,這個很顯然啊。分析中斷,本質上是一種特殊的電訊號,由硬體裝置發向處理器,處理器接收到中斷後,會馬上向作業系統反應此訊號的帶來,然後就由OS負責處理這些新到來的資料,中斷可以隨時發生,才不用操心與處理器的時間同步問題。不同的裝置對應的中斷不同,他們之間的不同從作業系統級來看,差別就在於一個數字標識-----中斷號。專業一點就叫中斷請求(IRQ)線,通常IRQ都是一些數值量。有些體系結構上,中斷好是固定的,有的是動態分配的,這不是問題所在,問題在於特定的中斷總是與特定的裝置相關聯,並且核心要知道這些資訊,這才是最關鍵的,不是麼?哈哈.

       用書上一句話說:討論中斷就不得不提及異常,異常和中斷不一樣,它在產生時必須要考慮與處理器的時鐘同步,實際上,異常也常常稱為同步中斷,在處理器執行到由於程式設計失誤而導致的錯誤指令的時候,或者是在執行期間出現特殊情況,必須要靠核心來處理的時候,處理器就會產生一個異常。因為許多處理器體系結構處理異常以及處理中斷的方式類似,因此,核心對它們的處理也很類似。這裡的討論,大部分都是適合異常,這時可以看成是處理器本身產生的中斷。

       中斷產生告訴中斷控制器,繼續告訴作業系統核心,核心總是要處理的,是不?這裡核心會執行一個叫做中斷處理程式或中斷處理例程的函式。這裡特別要說明,中斷處理程式是和特定中斷相關聯的,而不是和裝置相關聯,如果一個裝置可以產生很多中斷,這時該裝置的驅動程式也就需要準備多個這樣的函式。一箇中斷處理程式是裝置驅動程式的一部分,這個我們在linux裝置驅動中已經說過,就不說了,後面我也會提到一些。前邊說過一個問題:中斷是可能隨時發生的,因此必須要保證中斷處理程式也能隨時執行,中斷處理程式也要儘可能的快速執行,只有這樣才能保證儘可能快地恢復中斷程式碼的執行。

       但是,不想說但是,大學第一節逃課的情形現在仍記憶猶新:又想馬兒跑,又想馬兒不吃草,怎麼可能!但現實問題或者不像想象那樣悲觀,我們的中斷說不定還真有奇蹟發生。這個奇蹟就是將中斷處理切為兩個部分或兩半。中斷處理程式上半部(top half)---接收到一箇中斷,它就立即開始開始執行,但只做嚴格時限的工作,這些工作都是在所有中斷被禁止的情況下完成的。同時,能夠被允許稍後完成的工作推遲到下半部(bottom half)去,此後,下半部會被執行,通常情況下,下半部都會在中斷處理程式返回時立即執行。我會在後面談論linux所提供的是實現下半部的各種機制。

       說了那麼多,現在開始第一個問題:如何註冊一箇中斷處理程式。我們在linux驅動程式理論裡講過,通過一下函式可註冊一箇中斷處理程式:

?
1int request_irq(unsigned int irq,irqreturn_t (*handler)(int, void *,struct pt_regs *),unsigned long irqflags,const char * devname,void *dev_id)

有關這個中斷的一些引數說明,我就不說了,一旦註冊了一箇中斷處理程式,就肯定會有釋放中斷處理,這是呼叫下列函式:

?
1void free_irq(unsigned int irq, void *dev_id)

這裡需要說明的就是要必須要從程序上下文呼叫free_irq().好了,現在給出一個例子來說明這個過程,首先宣告一箇中斷處理程式:

?
1static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)

注意:這裡的型別和前邊說到的request_irq()所要求的引數型別是匹配的,引數不說了。對於返回值,中斷處理程式的返回值是一個特殊型別,irqrequest_t,可能返回兩個特殊的值:IRQ_NONE和IRQ_HANDLED.當中斷處理程式檢測到一箇中斷時,但該中斷對應的裝置並不是在註冊處理函式期間指定的產生源時,返回IRQ_NONE;當中斷處理程式被正確呼叫,且確實是它所對應的裝置產生了中斷時,返回IRQ_HANDLED.C此外,也可以使用巨集IRQ_RETVAL(x),如果x非0值,那麼該巨集返回IRQ_HANDLED,否則,返回IRQ_NONE.利用這個特殊的值,核心可以知道裝置發出的是否是一種虛假的(未請求)中斷。如果給定中斷線上所有中斷處理程式返回的都是IRQ_NONE,那麼,核心就可以檢測到出了問題。最後,需要說明的就是那個static了,中斷處理程式通常會標記為static,因為它從來不會被別的檔案中的程式碼直接呼叫。另外,中斷處理程式是無需重入的,當一個給定的中斷處理程式正在執行時,相應的中斷線在所有處理器上都會被遮蔽掉,以防止在同一個中斷上接收另外一個新的中斷。通常情況下,所有其他的中斷都是開啟的,所以這些不同中斷線上的其他中斷都能被處理,但當前中斷總是被禁止的。由此可見,同一個中斷處理程式絕對不會被同時呼叫以處理巢狀的中斷。      

       下面要說到的一個問題是和共享的中斷處理程式相關的。共享和非共享在註冊和執行方式上比較相似的。差異主要有以下幾點:

1.request_irq()的引數flags必須設定為SA_SHIRQ標誌。
2.對每個註冊的中斷處理來說,dev_id引數必須唯一。指向任一裝置結構的指標就可以滿足這一要求。通常會選擇裝置結構,因為它是唯一的,而且中
   斷處理程式可能會用到它,不能給共享的處理程式傳遞NULL值。
3.中斷處理程式必須能夠區分它的裝置是否真的產生了中斷。這既需要硬體的支援,也需要處理程式有相關的處理邏輯。如果硬體不支援這一功能,那中
   斷處理程式肯定會束手無策,它根本沒法知道到底是否與它對應的裝置發生了中斷,還是共享這條中斷線的其他裝置發出了中斷。

       在指定SA_SHIRQ標誌以呼叫request_irq()時,只有在以下兩種情況下才能成功:中斷當前未被註冊或者在該線上的所有已註冊處理程式都指定了SA_SHIRQ.A。注意,在這一點上2.6與以前的核心是不同的,共享的處理程式可以混用SA_INTERRUPT.  一旦核心接收到一箇中斷後,它將依次呼叫在該中斷線上註冊的每一個處理程式。因此一個處理程式必須知道它是否應該為這個中斷負責。如果與它相關的裝置並沒有產生中斷,那麼中斷處理程式應該立即退出,這需要硬體裝置提供狀態暫存器(或類似機制),以便中斷處理程式進行檢查。毫無疑問,大多數裝置都提這種功能。

       當執行一箇中斷處理程式或下半部時,核心處於中斷上下文(interrupt context)中。對比程序上下文,程序上下文是一種核心所處的操作模式,此時核心代表程序執行,可以通過current巨集關聯當前程序。此外,因為程序是程序上下文的形式連線到核心中,因此,在程序上下文可以隨時休眠,也可以排程程式。但中斷上下文卻完全不是這樣,它可以休眠,因為我們不能從中斷上下文中呼叫函式。如果一個函式睡眠,就不能在中斷處理程式中使用它,這也是對什麼樣的函式能在中斷處理程式中使用的限制。還需要說明一點的是,中斷處理程式沒有自己的棧,相反,它共享被中斷程序的核心棧,如果沒有正在執行的程序,它就使用idle程序的棧。因為中斷程式共享別人的堆疊,所以它們在棧中獲取空間時必須非常節省。核心棧在32位體系結構上是8KB,在64位體系結構上是16KB.執行的程序上下文和產生的所有中斷都共享核心棧。
       下面給出中斷從硬體到核心的路由過程(截圖選自liuux核心分析與設計p61),然後做出總結:

中斷處理路由