前言:上一次我們說了說,異常和中斷是什么,現(xiàn)在我們說說IA-32 架構(gòu)下異常和中斷的處理
還記得這個(gè)圖嗎?
異常和中斷的處理方式很像,都是進(jìn)入內(nèi)核態(tài)。所以兩者的處理方式很相同。在 IA-32 架構(gòu)下有 256 種不同的異常和中斷。
每個(gè)異常和中斷都有唯一編號(hào),稱之為中斷類型號(hào),如類型號(hào) 0,是除法除以 0。而且,每一個(gè)異常和中斷都有其對應(yīng)的異常處理程序和中斷服務(wù)程序,其入口地址放在一個(gè)專門的「中斷向量表」或者「中斷描述符」中
前 32 個(gè)類型(0~31)留給 CPU,剩下的(32~255)由操作系統(tǒng)定義。
通過 int n(31 < n < 256),使 CPU 自動(dòng)轉(zhuǎn)到 OS 給出的中斷服務(wù)程序執(zhí)行
一共有兩種不同的表:「中斷向量表」和「中斷描述符」,原因是,我們從開機(jī)到由操作系統(tǒng)接管機(jī)器經(jīng)歷了兩種模式:
實(shí)地址模式
-------->保護(hù)模式
在實(shí)模式中存儲(chǔ)中斷程序入口表的叫做:「中斷向量表」,而在保護(hù)模式存儲(chǔ)中斷程序入口表的叫做:「中斷描述符表」
現(xiàn)在分別來介紹兩種表:
實(shí)地址模式:中斷向量表
我們知道:在實(shí)地址模式
下通過 cs(16 位) ip(16 位) 來確定地址(cs << 4 + ip)。尋址空間只有 1M(20 位)。
中斷向量表位于 0000H~03FFH。共 256 組,每組占四個(gè)字節(jié) CS:IP 。
(就像這樣)
是誰!讓我的機(jī)器有了「中斷向量表」
都是 BIOS!!!
BIOS 干了啥?
- 開機(jī)后系統(tǒng)首先在實(shí)地址模式下工作(只有 1MB 尋址空間)
- 開機(jī)過程中 BIOS 在實(shí)地址模式下準(zhǔn)備「中斷向量表」和「中斷服務(wù)程序」
- BIOS 程序檢查顯卡,鍵盤和內(nèi)存等等,并在 00000H~003FFH 區(qū)建立中斷向量表,并在主存中準(zhǔn)備了中斷服務(wù)程序
- BIOS 利用 INT 指令執(zhí)行特定的中斷服務(wù)程序把OS從磁盤加載到內(nèi)存中。例如,BIOS 可通過執(zhí)行 int 0x19 指令來調(diào)用中斷向量 0x19 對應(yīng)的中斷服務(wù)程序,將啟動(dòng)盤上的 0 號(hào)磁頭對應(yīng)盤面的 0 磁道 1 扇區(qū)中的引導(dǎo)程序裝入內(nèi)存
- BIOS(Basic Input/Output System)是基本輸入/輸出系統(tǒng)的簡稱,是針對具體主板設(shè)計(jì)的,與安裝的操作系統(tǒng)無關(guān)
- BIOS 包含各種基本設(shè)備驅(qū)動(dòng)程序,通過執(zhí)行 BIOS 程序,基本設(shè)備驅(qū)動(dòng)程序以中斷服務(wù)程序的形式被加載到內(nèi)存,以提供基本 I/O 系統(tǒng)調(diào)用
- 一旦進(jìn)入保護(hù)模式,就不再使用BIOS
保護(hù)模式:中斷描述符表
保護(hù)模式下,通過「中斷描述符表」獲異常處理或中斷服務(wù)程序入口地址。
中斷描述符表 (Interrupt Descriptor Table,IDT)是 OS 內(nèi)核中的一個(gè)表,共有 256 個(gè)表項(xiàng),每個(gè)表項(xiàng)占 8 個(gè)字節(jié),IDT 共占用 2KB,由 IDTR 存放 IDT 在內(nèi)存的首地址,每一個(gè)表項(xiàng)是一個(gè)中斷門描述符,陷阱門描述符,或者任務(wù)門描述符
下面介紹中斷描述符的格式
有了現(xiàn)在的知識(shí),我們就可以來學(xué)習(xí)IA-32中異常和中斷的處理
IA-32 中異常和中斷的處理
每條指令 CPU 都會(huì)根據(jù)執(zhí)行情況判斷內(nèi)部是否發(fā)生了異常事件,在指令結(jié)束后判斷是否發(fā)生了外部中斷請求
由此可見,異常事件和中斷請求的 檢測 都是在某一條指令執(zhí)行過程中進(jìn)行的,顯然由硬件完成
在 CPU 根據(jù) CS 和 EIP 取下條指令之前,會(huì)根據(jù)檢測的結(jié)果判斷是否進(jìn)入中斷響應(yīng)階段
異常和中斷的響應(yīng)也都是在某一條指令執(zhí)行過程中或執(zhí)行結(jié)束時(shí)進(jìn)行的,顯然也由硬件完成
現(xiàn)在開始敘述所有IA -32 中異常和中斷響應(yīng)過程
- 確定中斷類型號(hào) i(int i),從 IDTR 指向的 IDT 中取出第 i 個(gè)表項(xiàng) IDTi
- 從 IDTi 中選擇段選擇符,從 GDTR 中得到 GDT,再從 GDT 中取出相應(yīng)段描述符,得到對應(yīng)異常或中斷處理程序所在段的 DPL、基地址等信息。Linux 下中斷門和陷阱門對應(yīng)的即為內(nèi)核代碼段,所以 DPL 為 0,基地址為 0
- 若 CPL < DPL 或編程異常 IDTi 的 DPL<CPL,則發(fā)生 13 號(hào)異常。Linux 下,前者不會(huì)發(fā)生。后者用于防止惡意程序模擬 INT n 陷入內(nèi)核進(jìn)行破壞性操作
- 若 CPL≠DPL,則從用戶態(tài)換至內(nèi)核態(tài),以使用內(nèi)核棧。切換棧的步驟:
- 讀 TR 寄存器,以訪問正在運(yùn)行的用戶進(jìn)程的 TSS 段
- TSS 段中保存的內(nèi)核棧的段選擇符和棧指針分別裝入寄存器 SS 和 ESP,然后在內(nèi)核棧中保存原來用戶棧的 SS 和 ESP
- 若是故障,則將發(fā)生故障的指令的邏輯地址寫入 CS 和 EIP,以使處理后回到故障指令執(zhí)行。其他情況下,CS 和 EIP 不變,使處理后回到下條指令執(zhí)行
- 在當(dāng)前棧中保存 EFLAGS、CS 和 EIP 寄存器的內(nèi)容(斷點(diǎn)和程序狀態(tài))
- 若異常產(chǎn)生了一個(gè)硬件出錯(cuò)碼,則將其保存在內(nèi)核棧中
- 將 IDTi 中的段選擇符裝入 CS,IDTi 中的偏移地址裝入 EIP,它們是異常處理程序或中斷服務(wù)程序第一條指令的邏輯地址(Linux中段基址 = 0)
下個(gè)時(shí)鐘周期開始,從 CS:EIP 所指處開始執(zhí)行異常或中斷處理程序!
什么是 TSS
內(nèi)核中的 TSS 段記錄了每個(gè)進(jìn)程的狀態(tài)信息,例如,每個(gè)進(jìn)程對應(yīng)的頁表、task 和 mm 等結(jié)構(gòu)信息。
紅色那塊就是 TSS 段。
從中斷程序中跳出來
中斷或異常處理程序最后一條指令是 IRET
。CPU 在執(zhí)行 IRET 指令過程中完成以下工作
- 從棧中彈出硬件出錯(cuò)碼(保存過的話)、EIP、CS 和 EFLAGS
- 檢查當(dāng)前異常或中斷處理程序的 CPL 是否等于 CS 中最低兩位,若是則說明異常或中斷響應(yīng)前、后都處于同一個(gè)特權(quán)級(jí),此時(shí),IRET 指令完成操作。否則,再繼續(xù)完成下一步工作。
- 從內(nèi)核棧中彈出 SS 和 ESP,以恢復(fù)到異常或中斷響應(yīng)前的用戶級(jí)進(jìn)程所使用的棧。
- 檢查DS、ES、FS 和 GS 段寄存器的內(nèi)容,若其中有某個(gè)寄存器的段選擇符指向一個(gè)段描述符且其 DPL 小于 CPL,則將該段寄存器清 0。這
是為了防止惡意應(yīng)用程序(CPL = 3)利用內(nèi)核以前使用過的段寄存器(DPL=0)來訪問內(nèi)核地址空間。
執(zhí)行完 IRET 指令后,CPU 回到原來發(fā)生異常或中斷的進(jìn)程繼續(xù)執(zhí)行