深入淺出分析Linux裝置驅動程式中斷

來源:互聯網
上載者:User

一、前言

  Linux的中斷宏觀分為兩種:非強制中斷和硬中斷。聲明一下,這裡的軟和硬的意思是指和軟體相關以及和硬體相關,而不是軟體實現的中斷或硬體實現的中斷。

  非強制中斷就是"訊號機制"。軟中不是軟體中斷。Linux通過訊號來產生對進程的各種中斷操作,我們現在知道的訊號共有31個,其具體內容這裡略過,感興趣讀者可參看相關參考文獻[1]。一般來說,非強制中斷是由核心機制的觸發事件引起的(例如進程運行逾時),但是不可忽視有大量的非強制中斷也是由於和硬體有關的中斷引起的,例如當印表機連接埠產生一個硬體中斷時,會通知和硬體相關的硬中斷,硬中斷就會產生一個非強制中斷並送到作業系統核心裡,這樣核心就會根據這個非強制中斷喚醒睡眠在印表機任務隊列中的處理進程。

  硬中斷就是通常意義上的"中斷處理常式",它是直接處理由硬體發過來的中斷訊號的。當硬中斷收到它應當處理的中斷訊號以後,就回去自己驅動的裝置上去看看裝置的狀態寄存器以瞭解發生了什麼事情,並進行相應的操作。對於非強制中斷,我們不做討論,那是進程調度裡要考慮的事情。由於我們討論的是裝置驅動程式的中斷問題,所以焦點集中在硬中斷裡。我們這裡討論的是硬中斷,即和硬體相關的中斷。

  二、中斷產生

  要中斷,是因為外設需要通知作業系統她那裡發生了一些事情,但是中斷的功能僅僅是一個裝置警示燈,當燈亮的時候中斷處理常式只知道有事情發生了,但發生了什麼事情還要親自到裝置那裡去看才行。也就是說,當中斷處理常式得知裝置發生了一個中斷的時候,它並不知道裝置發生了什麼事情,只有當它訪問了裝置上的一些狀態寄存器以後,才能知道具體發生了什麼,要怎麼去處理。

  裝置通過中斷線向中斷控制器發送高電平告訴作業系統它產生了一個中斷,而作業系統會從中斷控制器的狀態位知道是哪條中斷線上產生了中斷。PC機上使用的中斷控制器是8259,這種控制器每一個可以管理8條中斷線,當兩個8259級聯的時候共可以控制15條中斷線。這裡的中斷線是實實在在的電路,他們通過硬體介面串連到CPU外的裝置控制器上。

  三、IRQ

  並不是每個裝置都可以向中斷線上發中斷訊號的,只有對某一條確定的中斷線勇有了控制權,才可以向這條中斷線上發送訊號。由於電腦的外部裝置越來越多,所以15條中斷線已經不夠用了,中斷線是非常寶貴的資源。要使用中斷線,就得進行中斷線的申請,就是IRQ(Interrupt Requirement),我們也常把申請一條中斷線成為申請一個IRQ或者是申請一個中斷號。

  IRQ是非常寶貴的,所以我們建議只有當裝置需要中斷的時候才申請佔用一個IRQ,或者是在申請IRQ時採用共用中斷的方式,這樣可以讓更多的裝置使用中斷。

  無論對IRQ的使用方式是獨佔還是共用,申請IRQ的過程都是一樣的,分為3步:

  1.將所有的中斷線探測一遍,看看哪些中斷還沒有被佔用。從這些還沒有被佔用的中斷中選一個作為該裝置的IRQ。

  2.通過中斷申請函數申請選定的IRQ,這是要指定申請的方式是獨佔還是共用。

  3.根據中斷申請函數的傳回值決定怎麼做:如果成功了萬事大吉,如果沒成功則或者重新申請或者放棄申請並返回錯誤。

  申請IRQ的過程,在參考書的配的原始碼裡有詳細的描述,讀者可以通過仔細閱讀原始碼中的short一例對中斷號申請由深刻的理解。

  四、中斷處理常式

  Linux中的中斷處理常式很有特色,它的一個中斷處理常式分為兩個部分:上半部(tophalf)和下半部(bottom half)。之所以會有上半部和下半部之分,完全是考慮到中斷處理的效率。

  上半部的功能是"登記中斷"。當一個中斷髮生時,他就把裝置驅動程式中中斷常式的下半部掛到該裝置的下半部執行隊列中去,然後就沒事情了--等待新的中斷的到來。這樣一來,上半部執行的速度就會很快,他就可以接受更多她負責的裝置產生的中斷了。上半部之所以要快,是因為它是完全屏蔽中斷的,如果她不執行完,其它的中斷就不能被及時的處理,只能等到這個中斷處理常式執行完畢以後。所以,要儘可能多得對裝置產生的中斷進行服務和處理,中斷處理常式就一定要快。

  但是,有些中斷事件的處理是比較複雜的,所以中斷處理常式必須多花一點時間才能夠把事情做完。可怎麼樣化解在短時間內完成複雜處理的矛盾呢,這時候Linux引入了下半部的概念。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的。

  下半部幾乎做了中斷處理常式所有的事情,因為上半部只是將下半部排到了他們所負責的裝置的中斷處理隊列中去,然後就什麼都不管了。下半部一般所負責的工作是察看裝置以獲得產生中斷的事件資訊,並根據這些資訊(一般通過讀裝置上的寄存器得來)進行相應的處理。如果有些時間下半部不知道怎麼去做,他就使用著名的鴕鳥演算法來解決問題--說白了就是忽略這個事件。

  由於下半部是可中斷的,所以在它運行期間,如果其它的裝置產生了中斷,這個下半部可以暫時的中斷掉,等到那個裝置的上半部運行完了,再回頭來運行它。但是有一點一定要注意,那就是如果一個裝置中斷處理常式正在運行,無論她是運行上半部還是運行下半部,只要中斷處理常式還沒有處理完畢,在這期間裝置產生的新的中斷都將被忽略掉。因為中斷處理常式是不可重新進入的,同一個中斷處理常式是不能並行的。

  在Linux Kernel 2.0以前,中斷分為快中斷和慢中斷(偽中斷我們這裡不談),其中快中斷的下半部也是不可中斷的,這樣可以保證它執行的快一點。但是由於現在硬體水平不斷上升,快中斷和慢中斷的運行速度已經沒有什麼差別了,所以為了提高中斷常式交易處理的效率,從Linux kernel 2.0以後,中斷處理常式全部都是慢中斷的形式了--他們的下半部是可以被中斷的。

  但是,在下半部中,你也可以進行中斷屏蔽--如果某一段代碼不能被中斷的話。你可以使用cti、sti或者是save_flag、restore_flag來實現你的想法。至於他們的用法和區別,請參看本文指定參考書中斷處理部分。

  進一步的細節請讀者參看本文指定參考書,這裡就不再所說了,詳細介紹細節不是我的目的,我的目的是整理概念。

  五、置中斷標誌位

  在處理中斷的時候,中斷控制器會屏蔽掉原先發送中斷的那個裝置,直到她發送的上一個中斷被處理完了為止。因此如果發送中斷的那個裝置載中斷處理期間又發送了一個中斷,那麼這個中斷就被永遠的丟失了。

  之所以發生這種事情,是因為中斷控制器並不能緩衝中斷資訊,所以當前一個中斷沒有處理完以前又有新的中斷到達,他肯定會丟掉新的中斷的。但是這種缺陷可以通過設定主處理器(CPU)上的"置中斷標誌位"(sti)來解決,因為主處理器具有緩衝中斷的功能。如果使用了"置中斷標誌位",那麼在處理完中斷以後使用sti函數就可以使先前被屏蔽的中斷得到服務。

 六、中斷處理常式的不可重新進入性

  上一節中我們提到有時候需要屏蔽中斷,可是為什麼要將這個中斷屏蔽掉呢?這並不是因為技術上實現不了同一中斷常式的並行,而是出於管理上的考慮。之所以在中斷處理的過程中要屏蔽同一IRQ來的新中斷,是因為中斷處理常式是不可重新進入的,所以不能並存執行同一個中斷處理常式。在這裡我們舉一個例子,從這裡子例中可以看出如果一個中斷處理常式是可以並行的話,那麼很有可能會發生驅動程式鎖死的情況。當驅動程式鎖死的時候,你的作業系統並不一定會崩潰,但是鎖死的驅動程式所支援的那個裝置是不能再使用了--裝置驅動程式死了,裝置也就死了。

  其中激發PS1的事件會使A1產生一個中斷,然後B1去讀R1中已有的資料,然後代碼C1向R2中寫資料。而激發PS2的事件會使A2產生一個中斷,然後B2刪除R1中的資料,然後C2讀去R2中的資料。

  如果PS1先產生,且當他執行到A1和B1之間的時候,如果PS2產生了,這是A2會產生一個中斷,將PS2中斷掉(掛到任務隊列的尾部),然後刪除了R1的內容。當PS2運行到C2時,由於C1還沒有向R2中寫資料,所以C2將會在這裡被掛起,PS2就睡眠在代碼C2上,直到有資料可讀的時候被訊號喚醒。這是由於PS1中的B2原先要讀的R1中的資料被PS2中的B2刪除了,所以PS1頁會睡眠在B1上,直到有資料可讀的時候被訊號喚醒。這樣一來,喚醒PS1和PS2的事件就永遠不會發生了,因此PS1和PS2之間就鎖死了。

  由於裝置驅動程式要和裝置的寄存器打交道,所以很難寫出可以重入的代碼來,因為裝置寄存器就是全域變數。因此,最簡潔的辦法就是禁止同一裝置的中斷處理常式並行,即裝置的中斷處理常式是不可重新進入的。

  有一點一定要清楚:在2.0版本以後的Linux kernel中,所有的上半部都是不可中斷的(上半部的操作是原子性的);不同裝置的下半部可以互相中斷,但一個特定的下半部不能被它自己所中斷(即同一個下半部不能並行)。

  由於中斷處理常式要求不可重新進入,所以程式員也不必為編寫可重新進入的代碼而頭痛了。以我的經驗,編寫可重新進入的裝置驅動程式是可以的,編寫可重新進入的中斷處理常式是非常難得,幾乎不可能。

  七、避免競爭條件的出現

  我們都知道,一旦競爭條件出現了,就有可能會發生死結的情況,嚴重時可能會將整個系統鎖死。所以一定要避免競爭條件的出現。這裡我不多說,大家只要注意一點:絕大多數由於中斷產生的競爭條件,都是在帶有中斷的核心進程被睡眠造成的。所以在實現中斷的時候,一定要相信謹慎的讓進程睡眠,必要的時候可以使用cli、sti或者save_flag、restore_flag。具體細節請參看本文指定參考書。

  八、實現

  如何?驅動程式的中斷常式,是各位讀者的事情了。只要你們仔細的閱讀short常式的原始碼,搞清楚編寫驅動程式中斷常式的規則,就可以編寫自己的中斷常式了。只要概念正確,在正確的規則下編寫你的代碼,那就是符合道理的東西。我始終強調,概念是第一位的,能編多少代碼是很其次的,我們一定要概念正確,才能進行正確的思考。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.