軟中斷和實時性
翻譯自:Software interrupts and realtime
Linux內核的軟中斷("softirq")機制有些奇怪,在早期的Linux和處理機制下比較晦澀,且僅有極少的內核開發人員會直接接觸軟中斷。然而它是內核的大多數重要處理的核心。在某些場景下,軟中斷會以一種不合時宜的方式出現。特別是內核的實時搶占補丁集經常會與軟中斷產生沖突,該補丁集的最新版本提供了一種解決產生軟中斷問題的方法,值得一看。
軟中斷介紹
在3.6.1-rt1補丁集的聲明中,Thomas Gleixner使用如下方式描述了軟中斷:
首先,它是大部分不相關任務聚合的產物,運行在隨機對任務施加/解除控制的上下文中
First of all, it's a conglomerate of mostly unrelated jobs, which run in the context of a randomly chosen victim w/o the ability to put any control on them.
軟中斷處理幾乎(但不等同)與硬中斷一樣重要。軟中斷的優先級比較高(但也有例外,見下文),但低於硬中斷,因此會搶占除硬中斷外的任何任務。
在很早以前,Linux存在32個硬中斷向量,並為每個向量分配一個設備驅動或相關的任務。大部分驅動在很早以前就已經跟軟中斷分進行了分離(驅動仍然會使用軟中斷,但需要通過中間APIs,如tasklets和timers)。當前內核中有10種軟中斷向量:2種用於微線程(tasklet)處理,2種用於網絡,2種用於塊層(block layer,塊設備使用的),2種用於定時器,調度器和read-copy-update(RCU)處理各使用了一種。內核通過CPU位掩碼來指定需要處理(任意時間可能發生的)軟中斷的CPU。例如,當一個內核子系統調用tasklet_schedule()
時,會在對應的CPU上設置TASKLET_SOFTIRQ
比特位,當軟中斷處理完畢后(開中斷),會運行微線程 (tasklet基於軟中斷)。
# cat /proc/softirqs
CPU0 CPU1
HI: 1 0 //高優先級的tasklet
TIMER: 104838818 108267618 //基於系統tick的定時器
NET_TX: 2 1 //數據發送
NET_RX: 11622033 2698 //數據接收
BLOCK: 37 6833945 //塊設備訪問
BLOCK_IOPOLL: 0 0 //
TASKLET: 9 46 //普通優先級的tasklet
SCHED: 61485884 65788587 //多CPU調度
HRTIMER: 0 0 //高精度定時器
RCU: 48876416 46889277
有兩種情況會引發軟中斷並搶占當前線程:一種是在處理完一個硬中斷時,中斷處理程序會觸發軟中斷(硬中斷之后會觸發軟中斷,用於處理硬中斷的信號或數據,如網卡報文等),為了某些目的(如減小延遲,優化緩存等) 需要盡快處理該軟中斷,這樣就能夠重新啟用硬中斷;另一種是內核代碼(在任何時候)可能會(通過調用如local_bh_enable()
或spin_unlock_bh()
函數,這兩個函數用於中斷保護,防止其他中斷混入處理,類似鎖機制)重新啟用軟中斷,這樣會導致積累的軟中斷在任意一個進程的上下文中運行,該進程也就是Thomas 所說的"隨機挑選的犧牲品"("randomly chosen victim")。
讀者可能會對系統上運行的ksoftirqd
感到疑惑,該進程主要用於在系統的軟中斷負載過高時降低軟中斷的處理。正規處理中,如果內聯的軟中斷進程代碼在循環處理10次之后,發現還需要處理更多的軟中斷(由於不斷產生中斷),此時中斷進程會喚醒合適的ksoftirqd
(每個CPU都有一個ksoftirqd
進程)進程並退出,后續由ksoftirqd
進程處理軟中斷。Ksoftirqd
可以被(硬件或軟件)中斷上下文之外的軟中斷打斷,這種處理是必要的,否則Ksoftirqd
在處理下一個軟中斷前可以運行任意時間。在老的內核中,Ksoftirqd
進程以最低的優先級運行,即對軟中斷的處理取決於該進程是系統上的最高優先級還是最低優先級。從2.6.23開始,Ksoftirqd
默認使用普通用戶優先級運行。
實時設置中的軟中斷
在一般的系統上,軟中斷機制已經足夠處理大部分情況,也不需要做過多改進。然而如The new visibility of RCU processing中描述,在3.7內核中, read-copy-update的任務已經移到其輔助線程中。在實時處理中,強制任意的進程做一些隨機工作的方式並不受歡迎,傳統的實時補丁會將所有的軟中斷隔離到獨立的線程中,每個線程都有各自的優先級。在這樣的處理下,如,當網絡需要實時響應時,該中斷處理的線程的優先級會提高;相反地,當網絡事件不那么緊急時,線程的優先級會降低。
從3.0實時補丁集開始,上面的處理方式無法繼續工作。如它無法與Per-CPU variables and the realtime tree很好地配合使用,正如Thomas所說, 采用軟中斷線程的方式會導致配置問題:
通常很難從一個實時系統中獲得合適的參數。將一些像軟中斷一樣晦澀的工作添加到系統設計人員的待做事項中並不是個好主意。
It's extremely hard to get the parameters right for a RT system in general. Adding something which is obscure as soft interrupts to the system designers todo list is a bad idea.
因此,從3.0開始,軟中斷的處理與主線內核的處理非常類似。使用這種方式改進了代碼質量並提升了非協調系統(通過消除切換到軟中斷線程的上下文)的性能,但也剝奪了傾向於對這種方式進行細微調整的能力(一些專注實時的開發者非常傾向於使用這種方式)。這樣也是一些用戶對這種改變抱怨的地方。
作為回應,3.6.1-rt1對軟中斷的處理又作了改動。現在,當一個線程觸發一個軟中斷時,內核會保存特定的中斷(如處理接收到的網絡報文時)。一旦線程退出,內核會禁用該軟中斷的上下文,並運行下一個軟中斷,使用這種方式可以減小處理軟中斷的延遲(由於會立即運行下一個軟中斷)。同樣重要的是,這種方式將軟中斷和產生該軟中斷的進程綁定到了一起。這樣產生網絡軟中斷的進程不會陷入處理其他進程的定時器的困境中,使得軟中斷處理本地化,消除由於處理其他進程的軟中斷造成的不確定性,並使得軟中斷能夠以一開始創建任務的進程的優先級運行。
但有一種例外:由硬中斷引發的軟中斷不能使用這種方式處理。由於無法將硬中斷與一個特定的線程進行關聯,因此不能使用對應的線程做必要的處理。這種情況下會將這些軟中斷交給ksoftirqd
進程處理。
Thomas暗示的下一步處理邏輯為,將一個禁用所有軟中斷的環境轉變為僅禁用特定軟中斷的環境。大多數禁用軟中斷的代碼僅關心某一特定的軟中斷處理,其他都允許正常運行。更進一步,Thomas補充到"最好的方式是完全擺脫軟中斷"。移除軟中斷機制已經在"待做"列表中存在了很長時間,但沒有人實際做這些工作。
實時補丁集的性質使得用戶對主線內核的缺陷感到痛苦,這導致來自實時社區的大量主線代碼修改和提升。目前,實時用戶已經有了一個改進的軟中斷機制,使其不必再進行底層調優。
TIPS:
- linux把中斷按照等級分為了top half和buttom half,在執行top half的時候是關中斷的,而在執行buttom half的時候是開中斷的(此時可以再次處理中斷)
- softirq在一個CPU上是串行的,一個tasklet本身就是串行的.softirq性能好,而一個tasklet不考慮在不同CPU上的並行場景,因此其在開發上比較便利。