Linux 中斷


1介紹

我們知道,處理器的處理速度比硬件來說要快上N個數量級,那麼由處理器向硬件發出請求並等待迴應的辦法顯然是不可取的,在這期間處理器浪費了大量的時間。這些時間應該被用來處理其他的事務。輪詢可能是解決辦法之一,但顯然這樣的辦法也會讓處理器做大量的無用功。

最好的辦法,就是讓硬件在需要的時候才向內核發出信號,然後處理器去響應硬件的請求。這就是中斷機制。

1.1什麼是中斷

當硬件需要和處理器通信時,會產生一個電信號(即中斷信號),併發往處理器,處理器收到後,會告訴OS,然後由OS進行後續處理。

每一種硬件設備都有其專門的中斷值,OS得以通過這個來進行區分,到底是哪個設備發生了中斷。這些中斷值又被稱爲中斷請求(IRQ

當然有的IRQ是動態分配的。其實OS關注的並不是某個設備一定要產生某個特定的IRQ,而是一個特定的IRQ要和一個特定的設備有映射的關係,而且OS需要知道這種關係。這就表明,即便是IRQ在多個設備之間進行共享也是可以的,只要OS能夠知道這些映射關係,並且能夠有辦法區分某一個時刻產生中斷的設備是哪一個即可。

1.2中斷處理程序

中斷處理程序,顧名思義,自然就是發生中斷時,需要調用的處理函數。

特點:1、不是每設備一個處理程序,而是每中斷一個處理程序[微軟用戶1]

2、運行於特殊的上下文中:中斷上下文

3、一般的中斷處理程序,都會關中斷,考慮到中斷隨時都有可能發生,處理程序應當儘可能的高效

4、一般中斷處理程序都是要和硬件打交道的

1.3中斷上下文

1.3.1回憶進程上下文

1、對內核而言,處於進程上下文表明內核處於這樣一種模式:進程在執行(系統調用或者運行內核線程)

2、可以通過current宏關聯當前進程

3、進程以進程上下文的形式關聯內核,使得在進程上下文中可以睡眠,也可以調用調度程序。

1.3.2中斷上下文

中斷上下文,則和進程上下文幾乎完全相反:和進程無關、和current宏無關à不能睡眠、不能調用會導致睡眠的函數

另外,處於中斷上下文的代碼,應當迅速、簡潔,儘量把工作放到下半部中去完成。

關於中斷使用的堆棧2.6的內核之前,中斷沒有自己的堆棧,而是與被中斷的內核線程共享該線程的堆棧(2頁)。2.6之後,內核增加了一個選項:每個內核線程只提供一頁內存,這減輕了內存的壓力,也同時促使中斷被分離了出來:每處理器一頁,稱爲中斷棧

1.4中斷的實現機制

Linux中,中斷的處理機制是依賴於體系結構的(處理器、中斷控制器、體系結構的設計、機器本身)。下圖是中斷的路由

1.5關於中斷的下半部

我們爲什麼需要下半部?因爲我們要把中斷處理中需要做的工作區分開來:中斷處理程序中,只處理那些有嚴格時間限制的工作,比如復位硬件,對中斷進行應答等。而那些可以拖到後面做的,或者說有可能睡眠的處理,都應當放到下半部去處理

這樣做的目的很顯然,就是讓中斷處理程序儘可能的簡潔明快

在適當的時機,下半部會開中斷執行

2中斷控制

爲什麼要控制中斷?控制中斷的原因,說到底還是爲了要進行同步。

通過禁止中斷,可以確保該中斷不會搶佔當前運行的代碼。禁止中斷還可以禁止內核搶佔。

需要注意的是,中斷都是對每處理器而言(中斷堆棧),也就是說,禁止中斷並不能夠保證自己使用的數據不會被其他處理器的併發進程所訪問到。因此如果在使用某些全局的數據時,需要考慮對其進行加鎖保護。

即:鎖提供機制,防止來自其他處理器(當然也可以是本處理器)的併發訪問,中斷提供機制,防止來自其他中斷處理程序的併發訪問。

2.1禁止和激活中斷

這裏需要注意的是,內核提供兩類接口:禁止/激活中斷,保存/恢復中斷狀態。前者比較傻瓜,會無條件的禁止/激活中斷,這需要使用者對當前的中斷狀態十分確定。而後者則相對更容易使用,免去了判斷

另外關於cli()sti(): 2.5版本之前的內核,提供“禁止所有處理器上的中斷”這樣的功能,現在已經去掉了,需要開發人員用鎖來避免併發。這麼做的好處是,是的代碼更加流線化,不會簇擁成團。而且使用粒度更細的鎖,會比全局鎖要效率高。

2.2禁止指定中斷線

我們當然不用禁止全局的中斷,有時候禁止某一條中斷線就可以了,這是指:凡是在該中斷線上產生的中斷,都將不會報告給處理器。

一般對於有多箇中斷處理程序共享的中斷線,並不建議使用這個功能,因爲這會導致這條線上的其他的設備無法傳遞中斷。

2.3中斷系統的狀態

有時候我們需要判斷代碼所處的狀態:是否在中斷上下文中。因爲有些操作是只有在進程上下文中才能夠進行的,比如睡眠。

系統提供接口,來判斷中斷是否被禁止、是否處於中斷上下文、是否正在執行中斷處理程序。

3下半部

一般的中斷處理都會分爲兩個部分,前面講到的中斷處理程序只是所謂的上半部,這是系統處理中斷不可或缺的一部分。但是由於中斷處理程序本身的侷限,僅靠上半部是無法高效的處理系統的所有中斷的,這就需要下半部來提供支持。

3.1爲什麼需要下半部

3.1.1上半部的侷限

1、中斷處理程序異步執行,且有可能打斷其他代碼,包括其他的中斷處理程序

2、中斷處理程序執行時,會禁止該中斷同級的其他中斷,甚至禁止全局所有中斷

3、往往需要對硬件進行操作

4、中斷處理程序在中斷上下文中運行,有很多限制

以上幾點,就要求中斷處理程序不僅要簡潔、高效,而且對於阻塞這種行爲也是不能支持的,這就導致上半部的限制很多。

3.1.2使用下半部

1、處理中斷時,工作推後到下半部的原因,就是爲了突破上面提到的侷限性,要儘可能的將那些可以推後執行的工作都放到下半部,提高系統的響應速度

2、下半部何時被調用?這個跟中斷處理選擇用何種下半部機制有關,可以是推後一段時間,也可以是通過定時器

3、下半部的特點:在進行處理的時候,隨時都有可能響應中斷

4、除了Linux很多其他的操作系統也採用了同樣的機制

3.2下半部介紹

1、下半部的實現方式,在2.6的內核中,有3種:軟中斷、tasklet、工作隊列。軟中斷用的比較少,只有有限的幾種使用場景,更多的是使用tasklet

2、內核定時器,同樣也能夠將工作推後一段時間(精確的)進行

3.3軟中斷

3.3.1軟中斷的實現

1、軟中斷是在內核編譯期間靜態分配的,不能夠動態裁剪。系統定義了一個32個元素的數組,但是目前只用到了寥寥幾個。多數還是通過tasklet來實現

2、軟中斷不會搶佔相同處理器上的其他軟中斷,軟中斷會被中斷給打斷

3、軟中斷的執行:中斷處理程序在返回之前,將對應的軟中斷進行標記(觸發軟中斷),然後系統會在合適的時機檢查該標記,並執行軟中斷(從中斷處理程序返回時、在ksoftirqd內核線程中、顯示檢查與處理軟中斷的代碼)。

3.3.2軟中斷的使用

1、軟中斷是給系統中對時間要求最嚴格,以及最重要的下半部來使用。目前只有兩個子系統在使用:網絡子系統以及SCSI子系統

2、執行軟中斷處理程序的時候,能夠相應中斷,但自己不能睡眠

3、軟中斷雖然可以禁止本處理器上的同類軟中斷,但是對不同處理器的同類軟中斷是沒有限制的(這就意味着潛在的數據併發訪問)。因此我們需要考慮同步(加鎖、或者使用每處理器數據

4、對於軟中斷,只有執行頻率很高,連續性要求很高的情況下,才考慮使用

3.4Tasklet

3.4.1tasklet的實現

1、tasklet的實現是基於軟中斷的,所以其本身也是軟中斷

2、tasklet可以靜態定義,也可以動態創建

3.4.2tasklet的調度

1、已觸發的tasklet,分爲兩類:普通優先級 / 高優先級的軟中斷(前面提到過)

2、Tasklet調度函數首先檢查狀態,若已經被調度過一次,則立刻返回

3、保存中斷狀態,禁止本地中斷

4、設置tasklet需要處理的標記並開中斷

5、在合適的時候,執行do_softirq(),通過內部調用,獲取對應狀態的tasklet鏈表,對每一個tasklet進行處理:如果是RUN狀態,則跳過[微軟用戶2],否則標記爲RUN並執行之,執行後取消RUN的狀態標記

總的來說:每一個tasklet都重複設置HI_SOFTIRQ / TASKLET_SOFTIRQ2個軟中斷進行,當一個tasklet被調度時,某一類軟中斷被喚醒並被特殊的處理函數進行處理,該函數處理執行所有被觸發的tasklet(不同類型的tasklet可以同時執行,同類的則只能有一個)

3.4.3tasklet的使用

1、tasklet的使用比較簡單,步驟就是創建(靜/動)、註冊自己的處理函數、調度、或者從待運行鏈表中刪除

2、注意你的處理函數中,不應該使用信號量,不應該進行睡眠

3、注意對和其他類型tasklet、軟中斷等進行了共享的數據進行加鎖保護

3.4.4關於ksoftirqd內核線程

問題描述:軟中斷與tasklet(實際上也是軟中斷),被觸發的頻率實際上是相當高的,而且軟中斷自己還會重新觸發軟中斷(比如網絡子系統),這麼一來就很有可能導致用戶空間甚至得不到處理器時間,長時間處於飢餓狀態

兩個簡單的方案一是只要還有被觸發的待處理的軟中斷,本次執行就要負責處理,中途觸發的軟中斷也要處理。二是不處理重新觸發的軟中斷,等到下一次執行。

上述兩個方案的不足:前者在系統負載很高的時候,軟中斷會被不停的觸發,以至於用戶空間幾乎都得不到響應;後者雖然保證了用戶進程不會捱餓,但是卻犧牲了軟中斷的性能,在系統空閒時沒有充分利用系統資源

ksoftirqd/n 線程:這組內核線程是上述兩種方案的最後折中產物,該線程每處理器一個,並以最低nice值運行。內核在每次執行軟中斷時,都不會處理重新觸發的軟中斷,該工作由ksoftirqd線程進行。當負載較高時,其他線程由於優先級較高,自然能夠獲取處理器資源;相反當系統較空閒時,ksoftirqd線程能被立刻調度,那麼軟中斷也就可以被立刻執行。下面是ksoftirqd

3.5工作隊列

3.5.1原理與實現

1、工作隊列是內核提供的第三種下半部機制,與前兩種有所不同,最主要的區別就是工作隊列將工作推後給一個內核線程events/n去執行。à意味着可以進行睡眠,意味着當需要信號量、阻塞、大量內存時,工作隊列應該是你的首選

2、每個處理器都會有一個工作者線程,也同時對應一個workque_struct,用戶將需要進行處理的工作掛到工作隊列中,當線程被調用,則會判斷工作隊列是否爲空,爲空則睡眠,否則執行隊列中的任務

3、工作者隊列一般只有系統默認的event類型,對應到系統中是每個處理器一個events線程。如果要增加工作隊列類型,那麼會導致增加處理器個數個內核線程,所以增加類型一定要慎重

3.5.2使用工作隊列

1、可以通過靜態方式(DECLARE_WORK)或者動態方式(INIT_WORK)定義自己的工作隊列

2、處理函數雖然運行在進程上下文,但不應該訪問用戶空間的數據

3、默認情況下,處理函數允許響應中斷不持有任何鎖可以在需要的時候進行睡眠

4、調度工作隊列時,可以簡單指明需要調度,也可以具體指明經過多少時鐘節拍後再進行調度。同時,內核也提供了刷新工作隊列的接口(睡眠等待所有工作完成),有時候會需要這樣的刷新操作來確保不再有待處理的工作

5、如果有需要,可以定義新的工作隊列類型

3.6下半部之間加鎖

1、不同類tasklet之間需要考慮數據的同步

2、軟中斷之間需要考慮數據同步

3、進程上下文與下半部共享數據,在訪問數據前,要禁止下半部處理,並獲取鎖

4、中斷上下文與下半部共享數據,在訪問數據前,要禁止中斷,並獲取鎖

5、工作隊列由於可能睡眠,也需要考慮數據同步

4實現自己的中斷

我們可以在系統中添加屬於自己的中斷處理,包括中斷處理程序以及可能需要的下半部。

4.1註冊中斷處理程序

在編寫中斷處理程序之前,需要申請中斷線,並且註冊自己的處理函數。這裏需要注意的有:中斷線是可以多個設備共享的,當共享中斷線時,通過設備id來進行區分;初始化硬件和註冊中斷處理函數的順序要對,避免在硬件初始化之前就開始執行中斷處理程序。

4.2編寫中斷處理程序

1、通常標記爲static

2、至少也要通知硬件,中斷已經收到

3、對於較複雜的設備,可能需要發送、接受數據,甚至做一些擴展工作

4、擴展工作應當儘量放到下半部

5、一般會在處理程序中先關中斷,所以可以不用考慮重入(中斷的嵌套,只會是不同類型的中斷)

6、如果訪問其他共享數據,比如和其他高優先級中斷處理程序共享的數據時,要考慮同步問題

7、多個設備可以共享中斷線,當產生中斷時,內核會依次調用註冊在該中斷線上的所有處理函數。這時會根據設備id進行區分

4.3下半部的選擇

簡單來說,這麼進行選擇:

是否需要睡眠?如果需要,那麼就只有選擇工作隊列

否則就使用tasklet

如果需要很好的性能支持,那就考慮軟中斷

5關於異常

異常通常由處理器產生,產生時必須考慮與處理器的時鐘進行同步。實際上異常也常常被稱爲同步中斷

處理器在執行到錯誤指令(比如0),或者發生特殊情況(比如缺頁)時,必須要靠內核來進行處理,處理器就會產生一個異常

前面幾節對硬件產生的異步中斷的討論,大多也適用於異常


[微軟用戶1]一個設備可以有多箇中斷處理程序

[微軟用戶2]同一時間內,同樣的tasklet只有一個能夠執行


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