保護模式及其程式設計——中斷和異常處理
1.異常和中斷向量
每個需要被處理器進行特殊處理的異常和中斷條件都被賦予了一個標號,稱為向量,向量號的索引範圍是0~255,其中0~31保留,32~255用於使用者定義的中斷。
保護模式下的異常和中斷如下:
向量號 |
助記符 |
說明 |
型別 |
錯誤號 |
產生源 |
0 |
#DE |
除出錯 |
故障 |
無 |
DIV或IDIV指令 |
1 |
#DB |
除錯 |
故障/陷阱 |
無 |
任何程式碼或資料引用,或是INT 1指令 |
2 |
-- |
NMI中斷 |
中斷 |
無 |
非遮蔽外部中斷 |
3 |
#BP |
斷點 |
陷阱 |
無 |
INT 3指令 |
4 |
#OF |
溢位 |
陷阱 |
無 |
INTO指令 |
5 |
#BR |
邊界範圍超出 |
故障 |
無 |
BOUND指令 |
6 |
#UD |
無效操作碼(未定義操作碼) |
故障 |
無 |
UD2指令或保留的操作碼。(Pentium Pro中加入的新指令) |
7 |
#NM |
裝置不存在(無數學協處理器) |
故障 |
無 |
浮點或WAIT/FWAIT指令 |
8 |
#DF |
雙重錯誤 |
異常終止 |
有(0) |
任何可產生異常、NMI或INTR的指令 |
9 |
-- |
協處理器段超越(保留) |
故障 |
無 |
浮點指令(386以後的CPU不產生該異常) |
10 |
#TS |
無效的任務狀態段TSS |
故障 |
有 |
任務交換或訪問TSS |
11 |
#NP |
段不存在 |
故障 |
有 |
載入段暫存器或訪問系統段 |
12 |
#SS |
堆疊段錯誤 |
故障 |
有 |
堆疊操作和SS暫存器載入 |
13 |
#GP |
一般保護錯誤 |
故障 |
有 |
任何記憶體引用和其他保護檢查 |
14 |
#PF |
頁面錯誤 |
故障 |
有 |
任何記憶體引用 |
15 |
-- |
(Intel保留,請勿使用) |
無 |
||
16 |
#MF |
x87 FPU浮點錯誤(數學錯誤) |
故障 |
無 |
x87 FPU浮點或WAIT/FWAIT指令 |
17 |
#AC |
對起檢查 |
故障 |
有(0) |
對記憶體中任何資料的引用 |
18 |
#MC |
機器檢查 |
異常終止 |
無 |
錯誤碼(若有)和產生源與CPU型別有關(奔騰處理器引進) |
19 |
#XF |
SIMD浮點異常 |
故障 |
無 |
SSE和SSE2浮點指令(PIII處理器引進) |
20-31 |
-- |
(Intel保留,請勿使用) |
|||
32-255 |
-- |
使用者定義(非保留)中斷 |
中斷 |
外部中斷或者INT n指令 |
2.中斷和異常源
2.1中斷源
處理器從兩種地方接受中斷:
外部(硬體產生)的中斷軟體產生的中斷
外部中斷通過處理器晶片上的兩個引腳(INTR和NMI)接收。注意,NMI接收到的是不可遮蔽中斷,它使用固定的中斷號2.其他中斷都可以使用標誌暫存器的IF標誌位來遮蔽。
2.2異常源
處理器接收的異常源也有兩個
@@@處理器檢測到的錯誤異常
@@@軟體產生的異常
異常可以詳細分為故障、中止和陷阱。INT 0、INT 3(斷點異常)、BOUND可以從軟體中產生異常。INT n指令可以用於軟體模擬異常,但是有一個限制。
3.異常分類
根據異常被報告的方式和異常的指令是否能夠被重新執行,異常可以細分成故障(Fault)、陷阱(Trap)和中止(Abort)。
Fault:可以被糾正的異常,一旦被糾正,程式就可以正確執行。Trap:是一個引起陷阱的指令被執行後立刻會報告的異常。
Abort是一種不會總是導致異常的指令的精確位置的異常。
4.程式或者任務的重新執行
為了讓程式可以在異常或者中斷處理完成之後能夠重新回覆執行,除了中止之外的異常都能報告精確的指令位置,並且所有的中斷保證是在指令邊界上發生。
對於故障類異常,處理器產生異常時儲存的返回指標指向出錯指令。例如頁面故障指令(想想怎麼執行)。為了保證重新執行對於當前執行程式具有透明性,處理器會儲存必要的暫存器和堆疊指標資訊,使得自己能夠返回到指令出錯之前的狀態。
對於陷阱類異常,處理器產生異常時儲存的返回指標指向引起陷阱操作的後一條指令。例如,JMP指令執行的時候產生了異常,那麼異常恢復後,回到JMP所指的目標位置,而不是JMP的後一條指令。
中止類異常不支援可靠地重新執行程式或者任務。
中斷會嚴格地執行被中斷程式的重新執行而不會丟失任何連貫性。
5.開啟和禁止中斷
EFLAGS中的IF可以用於禁止可以遮蔽的中斷,可以用STI和CLI來設定和清楚這個指令。只有當程式的CPL<=IOPL時候,才可以執行這兩條指令,否則將引發一般保護性異常。IF標誌會受到下面操作的影響。
1)push和pop指令:push用於將eflags壓棧,然後可以被修改,然後pop
2)任務切換、POPF、IRET指令都可以載入EFLAGS,從而可以修改它。
3)通過一箇中斷門來處理一箇中斷IF標誌會被自動清零,從而禁止可遮蔽中斷。如果通過一個陷阱門來處理一箇中斷,那麼IF標誌不會被複位。
6.異常和中斷處理的優先順序
處理器會首先處理最高級別的中斷和異常,低優先順序的異常將會被丟棄,低優先順序的中斷將會被保持等待。當中斷處理程式回到任務的時候,被丟棄的異常會重新發生。
異常或者中斷的優先順序如下:
向量號 |
助記符 |
說明 |
型別 |
錯誤號 |
產生源 |
0 |
#DE |
除出錯 |
故障 |
無 |
DIV或IDIV指令 |
1 |
#DB |
除錯 |
故障/陷阱 |
無 |
任何程式碼或資料引用,或是INT 1指令 |
2 |
-- |
NMI中斷 |
中斷 |
無 |
非遮蔽外部中斷 |
3 |
#BP |
斷點 |
陷阱 |
無 |
INT 3指令 |
4 |
#OF |
溢位 |
陷阱 |
無 |
INTO指令 |
5 |
#BR |
邊界範圍超出 |
故障 |
無 |
BOUND指令 |
6 |
#UD |
無效操作碼(未定義操作碼) |
故障 |
無 |
UD2指令或保留的操作碼。(Pentium Pro中加入的新指令) |
7 |
#NM |
裝置不存在(無數學協處理器) |
故障 |
無 |
浮點或WAIT/FWAIT指令 |
8 |
#DF |
雙重錯誤 |
異常終止 |
有(0) |
任何可產生異常、NMI或INTR的指令 |
9 |
-- |
協處理器段超越(保留) |
故障 |
無 |
浮點指令(386以後的CPU不產生該異常) |
10 |
#TS |
無效的任務狀態段TSS |
故障 |
有 |
任務交換或訪問TSS |
11 |
#NP |
段不存在 |
故障 |
有 |
載入段暫存器或訪問系統段 |
12 |
#SS |
堆疊段錯誤 |
故障 |
有 |
堆疊操作和SS暫存器載入 |
13 |
#GP |
一般保護錯誤 |
故障 |
有 |
任何記憶體引用和其他保護檢查 |
14 |
#PF |
頁面錯誤 |
故障 |
有 |
任何記憶體引用 |
15 |
-- |
(Intel保留,請勿使用) |
無 |
||
16 |
#MF |
x87 FPU浮點錯誤(數學錯誤) |
故障 |
無 |
x87 FPU浮點或WAIT/FWAIT指令 |
17 |
#AC |
對起檢查 |
故障 |
有(0) |
對記憶體中任何資料的引用 |
18 |
#MC |
機器檢查 |
異常終止 |
無 |
錯誤碼(若有)和產生源與CPU型別有關(奔騰處理器引進) |
19 |
#XF |
SIMD浮點異常 |
故障 |
無 |
SSE和SSE2浮點指令(PIII處理器引進) |
20-31 |
-- |
(Intel保留,請勿使用) |
|||
32-255 |
-- |
使用者定義(非保留)中斷 |
中斷 |
外部中斷或者INT n指令 |
7.中斷描述符表
中斷描述符表將每個異常或者中斷向量與它們的處理過程聯絡起來。也是8B,與GDT不同的是,它的第一項可以包含描述符。為了構成IDT表的一個索引,通常將向量號乘以8.因為最多有256箇中斷向量,所以最多需要256個描述符。IDT中的所有空描述符都需要將它的存在位置位為1.IDT表可以處線上性地址空間的任何地方,使用IDTR來定位。IDTR是一個32b的暫存器,含有32位基地址和16b限長。IDT表基地址應該8b對齊,從而加快訪問速度。
如果超過IDT界限,將產生一個一般保護性異常。
8.IDT描述符
IDT表中存放三種類型的門描述符:中斷門、陷阱門、任務門。它們都是8B的,格式如下:中斷門和陷阱門都有一個長指標(段選擇符和偏移值),用於將程式轉移到中斷或者異常處理過程,她們的區別在於IF標誌位。IDT中任務門和GDT/LDT中任務門的描述格式相同,任務門的描述符中含有一個TSS段的選擇符,該任務用於處理異常和中斷。
9.異常和中斷處理
處理方法類似於call指令呼叫程式過程和任務的方法。中斷或者異常處理過程如下圖所示。
1)如果處理過程發生在高特權級,那麼將發生堆疊切換操作,堆疊切換的過程如下
A、處理器從當前執行任務的TSS段得到中斷或者異常處理過程使用的堆疊的段選擇符和棧指標。然後將被中斷任務的棧選擇符和棧指標壓入新棧內部,如下圖所示。
C、如果異常產生一個錯誤號,那麼錯誤號也將被壓入新棧。
2)如果處理過程和中斷任務在同一個特權級上
那麼將沒有上述A步驟
為了從中斷處理任務中返回,處理過程必須使用IRET指令,因為和RET指令相比,IRET會將EFLAGS恢復到相關暫存器中。不過,只有當CPL=0的時候,才恢復EFLAGS的IOPL欄位,並且只有當CPL<=IOPL時候,IF的標誌才會改變。如果發生了堆疊切換,IRET將會恢復到原來的堆疊。
9.1異常或者中斷處理過程的保護
異常或者中斷處理過程的特權級保護機制和通過呼叫門呼叫普通過程類似(也就是說,中斷或者異常處理過程不會發生優先順序提升?)。CPU不會將控制轉移到比CPL更低的程式碼段的中斷處理過程,但是也有不同:
@@@中斷或者異常向量沒有RPL,所以也不會有相應檢查。@@@只有當中斷是由INT n、INT 3、INT 0產生的時候,處理器才會檢查中斷或者陷阱門中的DPL,而且要求CPL<=DPL.這樣可以防止,應用程式通過軟體中斷呼叫重要的異常處理過程,例如頁錯誤處理過程。對於硬體和處理器能夠檢測到的異常,處理器會忽略中斷或者陷阱門中的DPL,例如時鐘中斷。
為了達到這個目標,我們通常採用以下方法之一:
@@@異常或者中斷處理程式放在一個一致性程式碼段之中——該技術可以用於只需訪問堆疊資料的處理過程(如除出錯異常)。如果處理程式需要訪問資料段中的資料,那麼特權級3必須能夠訪問這個資料段,這樣依賴,就沒有保護可言了。因為~~~~~~~~
@@@處理過程可以放在具有特權級0的非一致性程式碼之中,這種處理過程總是可以執行的,與被中斷任務或者程式的當前特權級無關。
9.2異常或者中斷處理過程中的標誌使用方式
中斷門和陷阱門的唯一區別就是二者在處理器操作EFLAGS上IF標誌位的方法上——想想是什麼?9.3執行中斷處理過程的任務
通過IDT表中任務門來訪問終端或者異常的處理過程時,就會導致任務切換。從而可以在一個專用任務中執行中斷或者異常處理過程。IDT表中的任務門,引用GDT表中的TSS描述符,中斷任務的切換和普通任務的切換相同,但是0.12核心中沒有使用這種方法。10.中斷處理任務
當通過IDT表中任務門來訪問終端或者異常的處理過程時,就會導致任務切換。使用單獨的任務來處理異常和中斷具有如下好處
@@@被中斷任務的完整上下文會被自動儲存@@@處理中斷或者異常的時候,新的TSS允許處理過程使用新特權級別0的堆疊。在當前特權級0的堆疊已經損壞時,如果發生了一個異常和中斷,那麼在為中斷處理過程提供一個新特權級0的堆疊條件下,通過任務們訪問中斷處理過程能夠有效防止系統崩潰。
@@@通過使用單獨的LDT給中斷或異常處理任務獨立的地址空間。
使用獨立任務處理異常或者中斷的不足——大量機器狀態需要儲存,中斷相應慢,延遲高。
中斷處理任務切機理如下圖所示:
11.錯誤碼
當異常條件和一個特定的段相關的時候,處理器會將一個錯誤碼壓入異常處理過程的堆疊上。錯誤碼和段選擇符類似,但是最低3位不是T1和RPL欄位,而是以下三個標誌EXT:外部事件標誌
IDT:如果置位,表明錯誤碼所以部分指向IDT中的一個門描述符;反之,指向段描述符
Tl:當IDT=0的時候:Tl=1,表明指向LDT中的描述符;Tl=0,表明指向GDT中的描述符
索引欄位:指明瞭段或者門描述符的索引值。某些情況下,錯誤碼全部是0,表明錯誤不是某個特定的段造成,或者是填入了一個空的段描述符。
頁故障異常的錯誤碼格式與上面的不同,如下圖所示。只有最低三位有用,它們的名稱與頁表項的後三位相同(U/S、W/R、P)。
另外,CPU還會把引起頁面故障異常的線性地址放在CR2中,頁錯誤異常處理程式就可以使用這個地址來定位相關的頁目錄項和頁表項。
注意:錯誤不會被IRET指令自動彈出堆疊,因此中斷處理程式在返回之前,必須清除堆疊上的錯誤碼。另外,雖然處理產生的某些異常會產生錯誤碼並會自動儲存到處理過程的堆疊中,但是外部硬體中斷或程式執行INT n指令產生的異常並不會把錯誤碼壓入堆疊中。