注:本分類下文章大多整理自《深入分析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 就跳轉到了中斷或異常處理程式。