軟中斷是如何實現的?

下面是一些我個人理解。

在非中斷線程化的 OS 中,如果把響應中斷的所有工作都在 ISR 中完成,系統
是無法忍受的,我們要做的是在 ISR 中儘量的減少代碼,只做一些必要性的工作,
如 in / out 操作,把一些其他不必要在 ISR 中工作放到其他地方,比如數據的
處理,這也就是軟中斷目的所在。其實即使在中斷線程化的 OS 當中(如:SOLARIS)
也一樣有軟中斷,但這裏的軟中斷含義和那種推遲調度不是一個概念了。以下僅針
對此帖題目舉例說明硬件中斷如何觸發軟中斷,關於如何註冊軟中斷和註冊步驟就不
說了。這裏所說的軟中斷是與題目相關的。


在 NT KERNEL 中把軟件運行環境又分爲三個軟件中斷 IRQL 等級,DISPATCH_LEVEL
(DPC_LEVEL),APC_LEVEL,PASSVIE_LEVEL。系統提供的 DPC 是爲了處理硬件中斷
後續工作提供的一種機制,DPC 與 Linux Kernel 提供的基於 tasklet 實現的 Bottom
Half 作用上有些相似,但又不一樣。在使用 DPC 處理硬件中斷後續的工作前,需顯示
調用相關的 kernel api 註冊 DPC。關於硬件中斷如何觸發 DPC 調用,可以看
HalEndSystemInterrupt() 這個函數,在這個函數中,首先確定沒有其他嵌套硬件
中斷後(沒有高於 DPC_LEVEL 的 IRQL),把 IRQL 降低,並判斷是否有 DPC 調用
(之前使用 IoRequestDpc() 向系統註冊的 DPC 回調函數,當把 DPC 回調函數插入
DPC LIST 後,系統調用 HalRequestSoftwareInterrupt() 設置當前 IRQL 爲
DPC_LEVEL,如果當前有硬件級的 IRQL 高於 DPC_LEVEL 則 pending DPC),
如果有則設置 IRQL 爲 DPC_LEVEL 並調用 KiDispatchInterrupt() 處理之前註冊
的 DPC 回調函數。KiDispatchInterrupt() 函數本身是在 IRQL == DPC_LEVEL 上
運行的,它同樣是處理線程調度的主函數。也就是說 DPC 調用點不光是在所有硬件
ISR 處理完成後調用 DPC。還有是處理任務調度時也會判斷是否需要執行 DPC。這
裏注意,DPC 與 DPC_LEVEL 不是一回事,DPC 回調函數運行級別在 DPC_LEVEL,
但並不是說所有 DPC_LEVEL 級別都是要運行 DPC 回調函數的。在非 SMP 環境下
spinlock 的實現就是簡單的提升當前 IRQL 爲 DPC_LEVEL,因爲在這個級別的
IRQL 下所有其他軟件級別運行的代碼都將被阻塞,所以說線程調度主函數運行在這個
級別是不會被任何線程搶佔的,另 DPC_LEVEL 雖然不是中斷上下文,但在這個級別
下,任何引起睡眠的機制都將導致系統崩潰。扯的有點遠了。再簡單說下 APC(
Asynchronous Procedure Call),故名思意,既然是“異步過程調用”那麼當前執
行環境很可能是任意線程上下文。其實到了 APC 這裏就跟硬件中斷扯不上邊了,
雖然是這樣系統還是把 APC 實現也定位爲一種軟中斷,APC 運行在 APC_LEVEL 級
別(同樣系統調用 HalRequestSoftwareInterrupt() 函數設置當前IRQL 爲
APC_LEVEL)。關於如何觸發 APC 可以去看一些讀/寫函數,當一個讀/寫函數設置了
異步標誌時,有幾種情況(因爲是異步的),首先說一下觸發條件,條件是:“系
統處理的當前線程是調用者線程且 IRQL 小於 APC_LEVEL 時從當前線程 APC 隊列裏
取出 APC 回調函數並把 IRQL 提升到 APC_LEVEL 進行調用”。繼續看這幾種情況,
如果系統當前處理線程不是調用者者線程,則清除 APC_LEVEL 標誌,並將此 APC(
回調函數)插入調用者線程的 APC 列然後返回。如果系統當前處理線程正是調用者
線程,那麼判斷當前 IRQL 是否高於 APC_LEVEL,如果高於則插入調用者線程 APC
隊列,此次 APC 中斷被記錄並返回。如果系統當前處理線程是調用者線程且 IRQL
小於 APC_LEVEL 則滿足調用條件,APC 回調函數立刻被調用。處理 APC 的調用點
是在 syscall 返回時檢查。


在 Linux Kernel 裏引入 softirq 機制後,使用 tasklet 來實現 Bottom Half。
tasklet 也是 softirq 的一種應用,在使用 Bootom Half(tasklet)處理硬件
中斷後續工作前,需顯示調用相關的 kernel api 註冊 Bootom Half(tasklet)。
關於硬件中斷如何觸發 softirq,可以看 do_IRQ() 這個函數,這個函數在處理
完設備驅動程序調用 request_irq() 註冊的 ISR 後,會接着調用 irq_exit(),在
irq_exit() 中會檢測是否註冊了 softirq 回調函數,且在確定所有硬件中斷都返
回後調用 do_softirq(),在這個函數當中會遍歷軟中斷向量表,並調用相應的回
調函數,被調用的函數就是 tasklet 註冊的回調函數,也就是你註冊的 Bottom
Half。上面只是針對硬件中斷而言,其實處理軟中斷的調用點,不光是在硬件中斷
後觸發,還有一些地方也調用了 do_softirq()。如在任務調度中 schedule() 函
數裏也會判斷是否需要執行軟中斷。還有在 syscall 調用時也有判斷是否需要執
行軟中斷的地方。軟中斷的運行環境同樣是中斷上下文,在中斷上下文中任何引起
睡眠的機制都將導致系統崩潰。


在 SOLARIS Kernel 中,有很大一部分硬件中斷被線程化,如磁盤和網絡中斷,包
括時鐘中斷。實現中斷上下文可阻塞。被線程化的中斷,與普通線程共用一個
dispatcher。中斷線程使用區別於 TS/IA/RT 調度等級的優先級,即全局優先級最
高的等級。我個人認爲在這種響應硬件中斷時可阻塞的中斷線程化的處理機制中無
需提供像 DPC,Bottom Half 這種機制,這也是跟以上兩種非中斷線程化 OS 主要
區別。以上兩種 OS 的中斷處理機制,是爲了不讓在 ISR 中處理過多耗時操作,
而提供了一個系統回調函數接口,把那些相對耗時的處理操作放在回調函數中延遲
調用。而現在中斷線程化並可阻塞了,我完全可以創建一箇中斷線程,所有處理工
作由系統負責,這樣就無需再提供這種回調函數的接口了。既然不需要 DPC /
tasklet 的Bootom Half 的這種推遲機制那麼在 SOLAIRS 內核中軟中斷的實現自
然也就不一樣,它本身也是作爲一種中斷線程形式存在。關於軟中斷(softint)
的調用可以查看 interrupt.s 的代碼。在中斷處理的入口中首先判斷了是否爲軟
中斷調用 T_SOFTINT。如果是,則跳轉到 dosoftint() 去執行,首先會判斷如果
有比自身更高級的 pending 軟中斷,則直接返回。如果沒有則判斷當前處理線程
優先級比自身高的話馬上返回。如果可以處理當前軟中斷則先 lock BUS 防止其他
CPU 重入這個軟中斷,設置當前 IPL 爲軟中斷級。然後比較當前要中斷的線程是
否是一箇中斷線程,如果不是則再繼續判斷是否要設置時間片。如果需要則進行設
置。如果當前是中斷線程,則沒有以上的步驟。然後就是中斷當前線程,並使用被
中斷線程的 LWP。接下來保存被中斷的線程,切換到新軟中斷線程運行,繼續使用
av_dispatch_softvect() 函數來執行被註冊到 struct autovec 結構裏的軟中斷
回調函數。等到調用完成後,退出軟中斷線程返回。以上步驟是在沒有更高級的硬
件中斷線程在運行。如果當前有更高級別的硬件中斷正在執行的話,那麼會在處理
完硬件中斷後無條件的跳轉到軟中斷處理中執行。注意,這裏沒有判斷。從以上流
程可以看出軟中斷是有優先級的,即可以進行搶佔。軟中斷的調用是無條件調用的,
即我可以直接顯示的調用。這樣我們可以看出系統在處理軟中斷時,從處理機制來
講是與硬中斷平級的,當然有優先級之分。這也意味着與 DPC 和 Bootom Half 這
種被動調用機制有這明顯的不同。之所以設計成可以直接顯示調用,應該是爲了某
種性能調優而考慮的。在 SOLAIRS 的解釋中,軟中斷是爲一些僞設備所提供的。

 

出處:http://freebsd.chinaunix.net/bbs/viewthread.php?tid=749509&extra=page%3D5%26amp%3Bfilter%3Ddigest&page=5

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