在本章中,我們會簡單的看看用於中止、未定義指令和SVC指令的中斷處理程序,看看Linux內核是如何處理中斷的。復位處理程序在第15章啓動代碼裏將會深入涉及。
14.1 中止處理程序
中止處理程序的代碼在系統之間可能是極爲不同的。在很多嵌入式系統中,一個中止表示一個未期望的錯誤,處理程序會記錄任何特徵信息,報告錯誤,讓應用(或系統)優雅地退出。
在使用MMU支持虛擬內存的系統中,中止處理程序可以加載需要的虛擬頁到物理內存。實際上,它試圖修復引起最初中止的原因,然後返回到中止的指令並重新執行。第十章內存管理單元給出了Linux如何做的的進一步信息。
CP15寄存器提供引起中斷所訪問的內存地址(錯誤地址寄存器,Fault Address Register)和引起中斷的原因(錯誤狀態寄存器,Fault
Status Register)。原因可能是缺少訪問權限,一個外部中止或一個頁表翻譯錯誤。另外,鏈接寄存器(帶-8或-4的調整,取決於中止是否由一個指令獲取或一個數據訪問引起)在中止異常之前給出執行指令的地址。通過檢查這些寄存器,執行的最後指令和系統中可能的其它事情(例如,頁表入口),中斷處理程序可以確定需要採取的措施。
14.2 未定義指令處理
如果處理器試圖執行一條帶如ARM架構規範描述的UNDEFINED的操作碼的指令,未定義指令異常被獲取,或者當一條協處理指令被執行但是沒有協處理器識別它是一條可執行的指令。
在一些系統中,代碼包含協處理器的指令(例如一個VFP協處理器),但是當前系統沒有相應的VFP硬件是可能的。另外,也可能是VFP硬件不能處理特定指令,希望調用軟件來模擬。或者是,VFP硬件被禁止,我們可以獲取到異常從而使能並執行指令。
這樣的模擬器通過未定義指令向量調用。我們檢查引起異常的指令操作碼,決定採取的措施(例如,在軟件中執行合適的浮點運算)。在一些情況中,這樣的處理程序可能需要被菊鏈在一起(例如,可能有多個協處理器來模擬)。
如果沒有軟件可以使用未定義的或協處理器指令,異常的處理程序應該記錄合適的調試信息並殺死由於未期望事件導致失敗的應用程序。
在一些情況中,未定義指令異常的額外用途是實現用戶斷點,看第29章調試獲取關於斷點的更多信息。(也可以在第7章看Linux上下文切換的描述。)
14.3 SVC異常處理
超級用戶調用(supervisor call, SVC)通常被用於允許用戶模式代碼訪問操作系統函數。例如,如果用戶代碼希望訪問系統的特權部分(例如,執行文件I/O),它通常會使用SVC指令來做。
通過使用操作碼中的註釋字段或在寄存器中,參數可以被傳遞到SVC處理程序。
Linux內核中說明SVC使用的代碼如樣例14-1所示。
SVC#0指令使得ARM處理器獲取到SVC異常,這是訪問內核函數的機制。寄存器R7定義我們需要調用的系統調用(這裏是sys_write)。其它參數在寄存器中被傳遞;對於sys_write我們有R0告訴寫在哪裏,R1指向需要寫的字符,R2給出字符串的長度。
另一個使用SVC指令的例子可以被應用程序開發者看到。通過ARM使用SVC 0x123456(ARM狀態)或SVC 0xAB(Thumb)開發的工具來表示半自動的調試函數(例如,在調試器窗口輸出一個字符)。
14.4 Linux異常程序流
Linux爲異常處理使用一個跨平臺框架,在處理異常時不在不同的特權處理器模式之間作區分。因此,ARM實現使用一個異常處理樁來允許內核在SVC模式處理所有的異常。除了SVC和FIQ,所有的異常使用樁來切換到SVC模式並且陷入正確的異常處理程序。
14.4.1 啓動過程
在啓動過程中,內核會分配一個4KB的頁作爲向量頁。它被映射到異常向量的位置,虛擬地址是0xFFFF0000或0x00000000。這是在文件arch/arm/mm/mmu.c中由devicemaps_init()完成的。
在ARM系統中這是非常早期的調用。在此之後,trap_init (在arch/arm/kernel/traps.c),拷貝異常向量表,異常樁(exception
stubs)和kuser helpers到向量頁。異常向量表顯式地被拷貝到向量頁的開始,異常樁被拷貝到地址0x200(kuser helpers被拷貝到頁的頂部,在0x100 - kusr_sz),使用一系列的memcpy()操作,如樣例14-2中所示。
當拷貝完成,內核異常處理程序在它的運行時動態狀態,準備處理異常。
14.4.2 中斷調度
有兩種不同的處理程序,__irq_usr和__irq_svc。這些保存所有的處理器寄存器,並使用一個宏get_irqnr_and_base表示是否有中斷掛起。
處理程序循環這個代碼直到沒有剩餘中斷。如果有中斷,代碼會跳轉到位於arch/arm/kernel/irq.c的do_IRQ。
此時,在所有的架構中代碼是相同的,我們調用一個C編寫的合適的處理程序。
然而,需要進一步考慮的一點。當中斷完成,我們通常需要檢查是否處理程序已完成某個需要調用調度器的事情。如果調度去決定進入一個不同的線程,最初被中斷的保持睡眠直到被選擇再次運行。