中斷機制和中斷描述符表、中斷和異常的處理

s1mba發表於2013-09-16

注:本分類下文章大多整理自《深入分析linux核心原始碼》一書,另有參考其他一些資料如《linux核心完全剖析》、《linux c 程式設計一站式學習》等,只是為了更好地理清系統程式設計和網路程式設計中的一些概念性問題,並沒有深入地閱讀分析原始碼,我也是草草翻過這本書,請有興趣的朋友自己參考相關資料。此書出版較早,分析的版本為2.4.16,故出現的一些概念可能跟最新版本核心不同。

此書已經開源,閱讀地址 http://www.kerneltravel.net


1、中斷向量

Intel x86 系列微機共支援256 種向量中斷,為使處理器較容易地識別每種中斷源,將它們從0~255 編號,即賦予一箇中斷型別碼 n,Intel 把這個8 位的無符號整數叫做一個向量,因此,也叫中斷向量。所有256 種中斷可分為兩大類:異常和中斷異常又分為故障(Fault)、陷阱(Trap)和夭折(Abort),它們的共同特點是既不使用中斷控制器,又不能被遮蔽。中斷又分為外部可遮蔽中斷(INTR)和外部非遮蔽中斷(NMI),所有I/O 裝置產生的中斷請求(IRQ)均引起屏蔽中斷,而緊急的事件(如硬體故障)引起的故障產生非遮蔽中斷。

非遮蔽中斷的向量和異常的向量是固定的,而遮蔽中斷的向量可以通過對中斷控制器的程式設計來改變。Linux 對256 個向量的分配如下。

• 從0~31 的向量對應於異常和非遮蔽中斷。
• 從32~47 的向量(即由I/O 裝置引起的中斷)分配給遮蔽中斷。
• 剩餘的從48~255 的向量用來標識軟中斷。Linux 只用了其中的一個(即128 或0x80向量)用來實現系統呼叫。當使用者態下的程式執行一條int 0x80 彙編指令時,CPU 就切換到核心態,並開始執行system_call() 核心函式。

2、外設可遮蔽中斷、異常及非遮蔽中斷

Intel x86 通過兩片中斷控制器8259A 來響應15 個外中斷源,每個8259A 可管理8 箇中斷源。第1 級(稱主片)的第2 箇中斷請求輸入端,與第2 級8259A(稱從片)的中斷輸出端INT 相連,如圖3.1 所示。我們把與中斷控制器相連的每條線叫做中斷線,要使用中斷線,就得進行中斷線的申請,就是IRQ(Interrupt ReQuirement ),我們也常把申請一條中斷線稱為申請一個IRQ 或者是申請一箇中斷號。IRQ 線是從0 開始順序編號的,因此,第一條IRQ線通常表示成IRQ0。IRQn 的預設向量是 n+32;如前所述,IRQ 和向量之間的對映可以通過中斷控制器埠來修改。

異常就是CPU 內部出現的中斷,也就是說,在CPU 執行特定指令時出現的非法情況。非遮蔽中斷就是計算機內部硬體出錯時引起的異常情況。從圖3.1 可以看出,二者與外部I/O介面沒有任何關係。Intel 把非遮蔽中斷作為異常的一種來處理,因此,後面所提到的異常也包括了非遮蔽中斷。Intel x86 處理器釋出了大約20 種異常,Linux 核心必須為每種異常提供一個專門的異常處理程式,它們通常把一個UNIX 訊號傳送到引起異常的程式。

3、中斷描述符表

在實地址模式中,CPU 把記憶體中從0 開始的1K 位元組作為一箇中斷向量表。表中的每個表項佔4 個位元組,由兩個位元組的段基址和兩個位元組的偏移量組成,這樣構成的地址便是相應中斷處理程式的入口地址。在真實模式下,中斷向量表中的表項由8 個位元組組成,如圖3.2 所示,中斷向量表也改叫做中斷描述符表IDT(Interrupt Descriptor Table)。其中的每個表項叫做一個門描述符(Gate Descriptor),“門”的含義是當中斷髮生時必須先通過這些門,然後才能進入相應的處理程式。


其中型別佔3 位,表示門描述符的型別,這些描述符如下。

1.任務門(Task gate)
其型別碼為101,門中包含了一個程式的TSS 段選擇符,但偏移量部分沒有使用,因為TSS本身是作為一個段來對待的,因此,任務門不包含某一個入口函式的地址。TSS 是Intel 所提供的任務切換機制,但是 Linux 並沒有採用任務門來進行任務切換。

2.中斷門(Interrupt gate)
其型別碼為110,中斷門包含了一箇中斷或異常處理程式所在段的選擇符和段內偏移量。當控制權通過中斷門進入中斷處理程式時,處理器清IF 標誌,即關中斷,以避免巢狀中斷的發生。中斷門中的DPL(Descriptor Privilege Level)為0,因此,使用者態的程式不能訪問Intel 的中斷門。所有的中斷處理程式都由中斷門啟用,並全部限制在核心態。

3.陷阱門(Trap gate)
其型別碼為111,與中斷門類似,其唯一的區別是,控制權通過陷阱門進入處理程式時維持IF 標誌位不變,也就是說,不關中斷。

4.系統(呼叫)門(System gate)
這是Linux 核心特別設定的,用來讓使用者態的程式訪問Intel 的陷阱門,因此,門描述符的DPL 為3。通過系統門來啟用4 個Linux 異常處理程式,它們的向量是3、4、5 及128,也就是說,在使用者態下,可以使用int 3、into、bound 及int 0x80 四條彙編指令。

最後,在保護模式下,中斷描述符表在記憶體的位置不再限於從地址0 開始的地方,而是可以放在記憶體的任何地方。為此,CPU 中增設了一箇中斷描述符表暫存器IDTR,用來存放中斷描述符表在記憶體的起始地址。中斷描述符表暫存器IDTR 是一個48 位的暫存器,其低16位儲存中斷描述符表的大小,高32 位儲存IDT 的基址,如圖3.3 所示。


Linux 核心在系統的初始化階段要進行大量的初始化工作,其與中斷相關的工作有:初始化可程式設計控制器8259A;將中斷向量IDT 表的起始地址裝入IDTR 暫存器,並初始化表中的每一項。

使用者程式可以通過INT 指令發出一箇中斷請求,其中斷請求向量在0~255 之間。為了防止使用者使用INT 指令模擬非法的中斷和異常,必須對IDT 表進行謹慎的初始化。其措施之一就是將中斷門或陷阱門中的DPL 域置為0。如果使用者程式確實發出了這樣一箇中斷請求,CPU 會檢查出其CPL(3)與DPL(0)有衝突,因此產生一個“通用保護”異常。

但是,有時候必須讓使用者程式能夠使用核心所提供的功能(比如系統呼叫),也就是說從使用者空間進入核心空間,這可以通過把中斷門或陷阱門的DPL 域置為3 來達到。


4、中斷和異常的處理

當CPU 執行了當前指令之後,CS 和EIP 這對暫存器中所包含的內容就是下一條將要執行指令的邏輯地址。在對下一條指令執行前,CPU 先要判斷在執行當前指令的過程中是否發生了中斷或異常。如果發生了一箇中斷或異常,那麼CPU 將做以下事情。

• 確定所發生中斷或異常的向量 i(在0~255 之間)。
• 通過IDTR 暫存器找到IDT 表,讀取IDT 表第 i 項(或叫第i 個門)。

• 分兩步進行有效性檢查:首先是“段”級檢查,將CPU 的當前特權級CPL(存放在CS暫存器的最低兩位)與IDT 中第 i 項中的段選擇符中的RPL 相比較,如果RPL(3)大於CPL(0),就產生一個“通用保護”異常(中斷向量13),因為中斷處理程式的特權級不能低於引起中斷的程式的特權級。這種情況發生的可能性不大,因為中斷處理程式一般執行在核心態,其特權級0。然後是“門”級檢查,把CPL 與IDT 中第 i 個門的DPL 相比較,如果CPL (3)大於DPL(0),CPU 就不能“穿過”這個門,於是產生一個“通用保護”異常,這是為了避免使用者應用程式訪問特殊的陷阱門或中斷門。但是請注意,這種檢查是針對一般的使用者程式引起的中斷(INT 指令),而不包括外部I/O 產生的中斷或因CPU內部異常而產生的異常,也就是說,如果產生了中斷或異常,就免去了“門”級檢查。

• 檢查是否發生了特權級的變化。若中斷髮生時CPU執行在使用者空間,而中斷處理程序執行在核心態,特權級發生了變化,所以會引起堆疊的更換。也就是說,從使用者堆疊切換到核心堆疊。而當中斷髮生在核心態時,即CPU 在核心中執行時,則不會更換堆疊。

CS : EIP 的值就是IDT 表中第i 項門描述符的段選擇符和偏移量的值,此時,CPU 就跳轉到了中斷或異常處理程式。



相關文章