如果讓內核定期對設備進行輪詢,以便處理設備,那會做很多無用功,因為外設的處理速度一般慢於CPU,而CPU不能一直等待外部事件。所以能讓設備在需要內核時主動通知內核,會是一個聰明的方式,這便是中斷。
在響應一個特定中斷時,內核會執行一個函數——中斷處理程序。中斷處理程序與其他內核函數的區別在於,中斷處理程序是被內核調用來響應中斷的,而它們運行於我們稱之為中斷上下文的特殊上下文中。
中斷處理程序就是普通的C代碼。特別之處在於中斷處理程序是在中斷上下文中運行的,它的行為受到某些限制:
1)不能向用戶空間發送或接受數據
2)不能使用可能引起阻塞的函數
3)不能使用可能引起調度的函數
Linux基礎篇之內存管理機制 http://www.linuxidc.com/Linux/2014-03/98293.htm
Linux內核——進程管理與調度 http://www.linuxidc.com/Linux/2014-08/105366.htm
Linux內核——內存管理 http://www.linuxidc.com/Linux/2014-08/105365.htm
設備驅動程序利用request_irq()注冊中斷處理程序,並激活給定的中斷線。返回0表示成功,或者返回一個錯誤碼。
卸載設備驅動程序時,需要注銷相應的中斷處理程序,並釋放中斷線,這時需要調用free_irq——如果在給定的中斷線上沒有中斷處理程序,則注銷響應的處理程序,並禁用其中斷線。
我們期望讓中斷處理程序運行得快,並想讓它完成的工作量多,這兩個目標相互制約,如何解決——上下半部機制。
我們把中斷處理切為兩半。中斷處理程序是上半部——接受中斷,他就立即開始執行,但只有做嚴格時限的工作。能夠被允許稍後完成的工作會推遲到下半部去,此後,在合適的時機,下半部會被開終端執行。上半部簡單快速,執行時禁止一些或者全部中斷。下半部稍後執行,而且執行期間可以響應所有的中斷。這種設計可以使系統處於中斷屏蔽狀態的時間盡可能的短,以此來提高系統的響應能力。上半部只有中斷處理程序機制,而下半部的實現有軟中斷實現,tasklet實現和工作隊列實現。
我們用網卡來解釋一下這兩半。當網卡接受到數據包時,通知內核,觸發中斷,所謂的上半部就是,及時讀取數據包到內存,防止因為延遲導致丟失,這是很急迫的工作。讀到內存後,對這些數據的處理不再緊迫,此時內核可以去執行中斷前運行的程序,而對網絡數據包的處理則交給下半部處理。
1) 如果一個任務對時間非常敏感,將其放在中斷處理程序中執行;
2) 如果一個任務和硬件有關,將其放在中斷處理程序中執行;
3) 如果一個任務要保證不被其他中斷打斷,將其放在中斷處理程序中執行;
4) 其他所有任務,考慮放置在下半部執行。
軟中斷作為下半部機制的代表,是隨著SMP(share memoryprocessor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了一定的機制)。軟中斷一般是“可延遲函數”的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因為要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,軟中斷執行中斷處理程序留給它去完成的剩余任務,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:
a)產生後並不是馬上可以執行,必須要等待內核的調度才能執行。軟中斷不能被自己打斷,只能被硬件中斷打斷(上半部)。
b)可以並發運行在多個CPU上(即使同一類型的也可以)。所以軟中斷必須設計為可重入的函數(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其數據結構。
tasklet是通過軟中斷實現的,所以它本身也是軟中斷。
軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須循環完所有的中斷類型,才能最終執行對應的處理函數。顯然當年開發人員為了保證輪詢的效率,於是限制中斷個數為32個。
為了提高中斷處理數量,順道改進處理效率,於是產生了tasklet機制。
Tasklet采用無差別的隊列機制,有中斷時才執行,免去了循環查表之苦。Tasklet作為一種新機制,顯然可以承擔更多的優點。正好這時候SMP越來越火了,因此又在tasklet中加入了SMP機制,保證同種中斷只能在一個cpu上執行。在軟中斷時代,顯然沒有這種考慮。因此同一種軟中斷可以在兩個cpu上同時執行,很可能造成沖突。
總結下tasklet的優點:
(1)無類型數量限制;
(2)效率高,無需循環查表;
(3)支持SMP機制;
它的特性如下:
1)一種特定類型的tasklet只能運行在一個CPU上,不能並行,只能串行執行。
2)多個不同類型的tasklet可以並行在多個CPU上。
3)軟中斷是靜態分配的,在內核編譯好之後,就不能改變。但tasklet就靈活許多,可以在運行時改變(比如添加模塊時)。
上面我們介紹的可延遲函數運行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致內核會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必須要運行在進程上下文中,例如訪問磁盤數據塊的函數。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。
上面我們介紹的可延遲函數運行在中斷上下文中,於是導致了一些問題,說明它們不可掛起,也就是說軟中斷不能睡眠、不能阻塞,原因是由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致內核會整個僵死。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。而且由於是串行執行,因此只要有一個處理時間較長,則會導致其他中斷響應的延遲。為了完成這些不可能完成的任務,於是出現了工作隊列,它能夠在不同的進程間切換,以完成不同的工作。
如果推後執行的任務需要睡眠,那麼就選擇工作隊列,如果不需要睡眠,那麼就選擇軟中斷或tasklet。工作隊列能運行在進程上下文,它將工作托付給一個內核線程。工作隊列說白了就是一組內核線程,作為中斷守護線程來使用。多個中斷可以放在一個線程中,也可以每個中斷分配一個線程。我們用結構體workqueue_struct表示工作者線程,工作者線程是用內核線程實現的。而工作者線程是如何執行被推後的工作——有這樣一個鏈表,它由結構體work_struct組成,而這個work_struct則描述了一個工作,一旦這個工作被執行完,相應的work_struct對象就從鏈表上移去,當鏈表上不再有對象時,工作者線程就會繼續休眠。因為工作隊列是線程,所以我們可以使用所有可以在線程中使用的方法。
Linux中的軟中斷和工作隊列是中斷上下部機制中的下半部實現機制。
1.軟中斷一般是“可延遲函數”的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進城切換,軟中斷不能被自己打斷,只能被硬件中斷打斷(上半部),可以並發的運行在多個CPU上。所以軟中斷必須設計成可重入的函數,因此也需要自旋鎖來保護其數據結構。
2.工作隊列中的函數處在進程上下文中,它可以睡眠,也能被阻塞,能夠在不同的進程間切換,以完成不同的工作。
可延遲函數和工作隊列都不能訪問用戶的進程空間,可延時函數在執行時不可能有任何正在運行的進程,工作隊列的函數有內核進程執行,他不能訪問用戶空間地址。