中斷和異常的處理

本文爲<x86彙編語言:從實模式到保護模式> 第17章筆記編程

 

中斷和異常

中斷和異常概述

中斷和異常的做用是指示系統中的某個地方發生一些事件, 須要引發處理器(包括正在執行中的程序和任務)的注意. 當中斷和異常發生時, 典型的結果是迫使處理器將控制從當前正在執行的程序或任務轉移到另外一個歷程或任務中去. 該例程叫作中斷處理程序, 或者異常處理程序. 若是是一個任務, 則發生任務切換.數組

1. 中斷(Interrupt)緩存

中斷包括硬件中斷和軟中斷.併發

硬件中斷是由外圍硬件設備發出的中斷信號引起的, 以請求處理器提供服務. 當I/O接口發出中斷請求時, 會被像8259A和I/O APIC這樣的中斷控制器收集, 併發送處處理器. 硬件中斷徹底是隨機產生的, 與處理器的執行並不一樣步. 當中斷髮生時, 處理器要先執行完當前的指令, 而後纔對中斷進行處理.ide

軟中斷是由 int n 指令引起的中斷處理, n是中斷號或者叫類型嗎.性能

 

2. 異常(Exception)測試

異常就是16位彙編中的內部中斷. 它們是處理器內部產生的中斷, 表示在指令執行的過程當中遇到了錯誤的情況. 當處理器執行一條非法指令, 或者因條件不具有, 指令不能正常執行時, 將引起這種類型的中斷. 以上所列都是異常狀況, 因此內部中斷又叫異常或者異常中斷. 好比在執行除法指令 div/idiv 時, 遇到了被0除的狀況(除數是0); 在好比, 使用jmp指令發起任務切換時, 指令的操做數不是一個有效的TSS描述符選擇子.spa

異常分爲三種, 第一種是程序錯誤異常, 指處理器在執行指令的過程當中, 檢測到了程序中的錯誤, 並由此而引起的異常.操作系統

第二種是軟件引起的異常. 這類異常一般由into, int3和bound指令主動發起. 這些指令容許在指令流的當前點上檢查實施異常處理的條件是否知足. 舉個例子, into指令在執行時, 將檢查EFLAGS的OF標誌, 若是知足爲1的條件, 則引起異常.指針

第三種是機器檢查異常. 這種異常是處理器型號相關的, 也就是說, 每種處理器都不太同樣. 不管如何, 處理器提供了一種對硬件芯片內部和總線處理進行檢查的機制, 當檢測到錯誤是, 將引起異常.

根據異常狀況的性質和嚴重性, 異常又分爲如下三種, 並分別實施不一樣的處理.

  • 故障(Faults). 故障一般是能夠糾正的, 好比, 當處理器執行一個訪問內存的指令時, 發現那個段或者頁不在內存中(P = 0), 此時, 能夠在異常處理程序中予以糾正(分配內存, 或者執行磁盤的換入換出操做), 返回時, 程序能夠從新啓動並不失連續性. 爲了作到這一點, 當故障發生時, 處理器把機器狀態恢復到引發故障的那條指令以前的狀態, 在進入異常處理程序時, 壓入棧中的返回地址(CS和EIP的內容)是指向引發故障的那條指令的, 而不像一般那樣指向下一條指令. 如此一來, 當中斷返回時, 將從新執行引發故障的那條指令,  並且再也不出錯(若是引發異常的狀況已經妥善處理).
  • 陷阱(Traps). 陷阱中斷一般在執行了截獲陷阱條件的指令以後當即產生, 若是陷阱條件成立的話. 陷阱一般用於調試目的, 好比單步中斷指令int3和溢出檢測指令into. 陷阱中斷容許程序或任務在從中斷處理過程返回以後繼續進行而不失連續性. 所以, 當此異常發生時, 在轉入異常處理程序以前, 處理器在棧中壓入陷阱截獲指令的下一條指令的地址.
  • 終止(Aborts). 終止標誌着最嚴重的錯誤, 諸如硬件錯誤, 系統表(GDT, LDT等)中的數據不一致或者無效. 這類異常老是沒法精確地報告引發錯誤的指令的位置, 在這種錯誤發生時, 程序或者任務不可能從新啓動. 一個比較典型的終止類異常是"雙重故障"(中斷號爲8), 當發生一次異常以後, 處理器在轉入該中斷的處理程序時, 又發生了另外的異常(如該中斷處理程序所在的段不在內存中, 或者棧溢出). 對於中斷處理程序來講, 很難從棧中得到有關如何糾正此類錯誤的明確信息, 每每是發生極爲重大的錯誤時才伴隨着這種異常,  因此在繼續執行引發此異常的程序或任務已至關困難, 操做系統一般只能把該任務從系統中抹去.

中斷和異常發生時, 處理器將掛起當前正在執行的任務或者過程, 而後執行中斷和異常處理程序. 返回時, 處理器恢復程序或者任務的執行, 並且被打斷的程序或任務的執行不失連續性, 除非遇到了一個終止類型的異常. 對於某些異常, 處理器在轉入異常處理程序以前, 會在當前棧中壓入一個成爲錯誤代碼的數值, 幫助程序進一步診斷異常產生的位置和緣由. 下表列出了Intel處理器在保護模式下的中斷和異常.

向量 助記 描述 類型 錯誤代碼 來源
0 #DE 除法錯 故障 div或idiv指令
1 #DB 保留      
2    - NMI 中斷 不可屏蔽的外部中斷
3 #BP 斷點 陷阱 int3指令
4 #OF 溢出 陷阱 into指令
5 #BR 對數組的引用超出邊界 故障 bound指令
6 #UD 無效或未定義的操做碼 故障 ud2指令, 或保護的操做碼
7 #NM 設備不可用(無數學協處理器) 故障 浮點或者wait/fwait指令
8 #DF 雙重故障 終止 有(0) 任何會產生異常的指令, NMI或者硬件中斷
9   協處理器段超越(保留). 協處理器執行浮點運算時, 至少有兩個操做數不在一個段內(跨段) 故障 浮點指令
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 機器檢查 終止 錯誤代碼(若是有的話)和來源是處理器型號相關的
19 #XM SIMD(單指令多數據)浮點異常 故障 sse/sse2/sse3浮點指令

20~31

  Intel公司保留, 建議不要使用      
32~255   用戶自定義的中斷 中斷   外部中斷, 或者int n指令

 

當中斷和異常發生時, NMI和異常的向量是由處理器自動給出的; 硬件的向量是由I/O中斷控制器芯片送給處理器的; 軟中斷的向量是由指令中的操做數給出的. 從80486以後開始, 處理器內部通常集成了浮點運算部件x87FPU, 再也不須要安裝獨立的數學協處理器, 因此有些和浮點運算有關的異常可能不會產生(好比向量爲9的協處理器段超越故障). wait和fwait指令用於主處理器和浮點處理部件(FPU)之間的同步, 它們應當放在浮點指令以後, 以捕捉任何我浮點異常.

  

bound r16, m16 
bound r32, m32

該指令用於檢查數組的索引是否在邊界以內, 目的操做數包含了數組索引, 源操做數必須指向內存位置, 那裏包含兩個成對出現的字或雙字, 分別是數組索引的下限和上限. 若是執行bound時, 數組的索引小於下標的下限, 或者大於下標的上限, 則產生異常.

 

ud2指令是從Pentium Pro處理器開始引入的, 它只有操做碼而沒有操做數, 執行該指令時, 會引起一個無效操做碼異常. 該指令沒有別的用處, 典型的用於軟件測試. 儘管異常是該指令故意引起的, 可是, 在轉入異常處理程序時, 壓入棧中的指令指針是指向該指令的, 而非下一條指令.

中斷描述符表 中斷門和陷阱門

 

在實模式下, 位於內存最低端的1KB內存, 是中斷向量表IVT, 定義了256種中斷的入口地址, 包括16位段地址和16位段內偏移量. 當中斷髮生時, 處理器要麼自發產生一箇中斷向量, 要麼從int n 指令中獲得中斷向量, 或者從外部的中斷控制器接受一箇中斷向量. 而後, 它將該向量做爲索引訪問中斷向量表. 具體作法是, 乘以4, 做爲表內偏移量訪問中斷向量表, 從中取得中斷處理過程的段地址和偏移地址, 並轉到那裏執行.

在保護模式下, 處理器對中斷的管理是類似的, 但並不是使用傳統的中斷向量表來保存中斷處理過程的地址, 而是中斷描述符表(Interrupt Descriptor Table: IDT). 顧名思義, 這個表裏, 保存的是和中斷處理過程有關的描述符, 包括中斷門, 陷阱門和任務門.

任務門的格式在<任務切換>中有說, 中斷門和陷阱的格式以下圖所示.

事實上, 調用門, 任務門, 中斷門和陷阱門的描述符很是類似, 從大的方面來講, 由於都用於實施控制轉移, 故都包括16位的目標代碼段選擇子, 以及32位的段內偏移量. 由上圖可知, 中斷門和陷阱門僅僅有一比特的差異. 中斷門和陷阱門描述符只容許存放在IDT內, 任務門能夠位於GDT, LDT和IDT中.

和實模式下的中斷向量表(IVT)不一樣, 保護模式下的IDT不要求必須位於內存的最低端. 事實上, 在處理器內部, 有一個48位的中斷描述符表寄存器(Interrupt Descriptor Table Register:IDTR), 保存着中斷描述符表在內存中的線性基地址和界限. 以下圖所示, 和GDT同樣, 由於整個系統中只須要一個IDT就夠了, 因此, GDTR與IDTR不像LDTR和TR, 沒有也不須要選擇器部分.

這就意味着, IDT能夠位於內存中的任何地方, 只要IDTR指向了它, 整個中斷系統就能夠正常工做. 爲了利用高速緩存使處理器的工做性能最大化, 建議IDT的基地址是8字節對齊的(地址的數值可以被8整除). 處理器復位時, IDTR的基地址部分爲0, 界限部分爲0xffff. 16位的表界限值意味着IDT和GDT, LDT同樣, 表的大小可使64KB, 可是, 事實上, 由於處理器只能識別256種中斷,  故一般只是用2KB, 其餘空餘的槽位應當將描述符的P位清0. 最後, 與GDT不一樣的是, IDT中的第一個描述符也是有效的.

如上圖所示, 在保護模式下, 當中斷和異常發生時, 處理器用中斷向量乘以8的結果去訪問IDT, 從中取得對應的描述符. 由於IDT在內存中的位置是由IDTR指示的, 因此這很容易作到.

注意, 上圖沒有考慮分頁, 也沒有考慮門描述符是任務門的狀況, 由於任務門的處理比較特殊. 中斷門和陷阱門中有目標代碼段描述符的選擇子, 以及段內偏移量, 取決於選擇子的TI位 , 處理器訪問GDT或者LDT, 取出目標代碼段的描述符. 接着, 從目標代碼段的描述符中取得目標代碼段所在的基地址, 再同門描述符中的偏移量相加, 就獲得了中斷處理程序的32位線性地址. 若是沒有開啓分頁功能, 該線性地址就是物理地址; 不然, 送頁部件轉換成物理地址. 注意, 當處理器用中斷向量訪問IDT時, 要訪問的位置超出了IDT的界限,  則產生常規保護異常(#GP).

中斷和異常處理程序的保護

和經過調用門實施控制轉移同樣, 處理器要對中斷和異常處理程序進行特權級保護. 當目標代碼段描述符的特權級(能夠用門描述符中的段選擇子, 從GDT或LDT中找到)低於當前特權級CPL時, 即, 在數值上,

 
CPL < 目標代碼段的DPL

時, 不容許將控制轉移到中斷或異常處理程序, 違反此規則將引起常規保護異常(#GP).

 

不過, 中斷和異常處理程序的特權級保護也有一些特別之處. 具體表如今:

  • 由於中斷和異常的向量中沒有RPL字段, 故當處理器進入中斷或異常處理器, 或者經過任務門發起任務切換時, 不檢查RPL.
  • 中斷門, 陷阱門也有本身的描述符特權級DPL, 即門的DPL. 可是, 一般狀況下不針對該DPL進行檢查, 除了用軟中斷int n和單步中斷int3, 以及into引起的中斷和異常. 在這種狀況下, 當前特權級CPL必須高於, 或者和門的特權級DPL相同, 即, 在數值上
     
    CPL <= 門描述符的DPL
    這主要是爲了防止低段特技的軟件經過軟中斷指令訪問一些只爲內核服務的例程, 如頁故障處理. 相反地, 對於硬件中斷和處理器檢測到異常狀況而引起的中斷處理, 不檢查門的DPL.

中斷和異常是隨機產生的, 不可預測的. 可是, 有一點能夠肯定的, 即, 它老是發生在某個任務內, 是在某個任務正在進行的時候產生的, 即便整個系統內只有一個任務. 當中斷和異常發生時, 任務可能正在特權級0的全局空間(內核)中執行, 也可能正在特權級爲3的局部空間執行. 所以, 當處理器將控制轉移到中斷或異常處理程序時, 若是處理程序運行在較高的特權級上(數值上較低的), 那麼, 將轉換棧:

  • 根據處理程序的特權級別, 從當前任務的TSS中取得棧段選擇子和棧指針. 處理器把舊棧的選擇子和棧指針壓入新棧. 畢竟, 中斷處理程序也是當前任務的一部分.
  • 處理器把EFLAGS, CS和EIP的當前狀態壓入新棧.
  • 對於有錯誤代碼的異常, 處理器還要把錯誤代碼壓入新棧, 緊挨在EIP以後, 以下圖下面所示.
  • 若是中斷處理程序的特權級別和當前特權級別一致, 則不用轉換棧.
  • 處理器把EFLAGS, CS和EIP的當前狀態壓入當前棧.
  • 對於有錯誤代碼的異常, 處理器還要把錯誤代碼壓入當前棧, 緊挨在EIP以後, 以下圖上面所示

中斷門和陷阱門區別不大, 經過中斷門進入中斷處理程序時, EFLAGS寄存器的IF位被處理器清零, 以禁止嵌套的中斷, 當中斷返回時, 將從棧中恢復EFLAGS寄存器的原始狀態. 陷阱中斷的優先級較低, 當經過陷阱門進入中斷處理程序時, EFLAGS寄存器的IF位不變, 以容許其餘中斷優先處理.

EFLAGS寄存器的IF位僅影響硬件中斷, 對NMI, 異常和int n形式的軟件中斷不起做用.

中斷任務

當中斷和異常發生時, 若是很具中斷向量從IDT中找到的描述符是任務門, 則不是進行通常的中斷處理過程, 而是發起任務切換. 以下圖所示, 這是經過中斷髮起任務切換的原理.

具體的說, 在中斷中使用任務門能夠得到如下好處:

  • 被中斷的那個程序或任務的整個執行環境能夠被完整的保存起來(保存到它的TSS中).
  • 因爲接管控制的是一個新的任務, 所以, 可使用要給全新的0特權級棧. 這能夠有效地防止因當前任務的0特權級棧遭到破壞而使系統崩潰.
  • 因爲是切換到一個新任務, 所以, 它有一個獨立的地址空間.

固然, 和通常的中斷處理過程相比, 利用中斷髮起任務切換也有不利的一面, 那就是速度很慢, 筆記要保存大量的機器狀態, 並進行一系列的特權級和內存訪問的檢查. 因中斷和異常而發起任務切換時, 再也不保存CS, EIP的狀態, 可是, 在任務切換工做完成後, 處理器要把錯誤代碼壓入新任務的棧中(若是有錯誤代碼的話).

任務是不可重入的, 所以, 在進行中斷任務以後和執行iret指令以前, 必須關中斷, 以防止因相同的中斷再此發生而產生常規保護異常(#GP);

做爲對任務門的保護, 和中斷門, 陷阱門同樣, 只對經過int3, int n和into指令發起的任務切換實施特權級檢查, 即, 只有在數值上符合如下條件, 才容許經過以上指令發起任務切換:

CPL <= 任務門的DPL

在其餘異常和硬件中斷的狀況下, 不檢查任務門的特權級. 另外, 因爲任務切換, 不對目標代碼段的特權級別進行檢查.

 

錯誤代碼

有些異常產生時, 處理器會在異常處理程序或中斷任務的棧中壓入一個錯誤代碼, 一般, 這意味着異常和特定的段選擇子或中斷向量有關.

以下圖所示, 壓入棧中的錯誤代碼是32位的, 但高16位不用.

EXT位的意思是, 異常是由外部事件引起的. 此位置位時, 表示異常是由NMI, 硬件中斷等引起的.

IDT位用於指示描述符的位置. 爲1時, 表示段選擇子的索引部分(錯誤代碼的位15~3)是指向中斷描述符表的; 爲0時, 表示段選擇子的索引部分指向GDT或者LDT.

TI位僅在IDT位是0的狀況下才有意義. 此位是0時, 表示段選擇子的索引部分指向GDT, 不然, 指向LDT.

段選擇子的索引部分用於指示GDT/LDT內的段描述符, 或者IDT內的門描述符, 它就是咱們平時所用的段選擇子的高13位.

有時候, 錯誤代碼多是全零(空), 這表示異常的產生並不是因爲引用了一個特定的段. 固然, 也可能確實是在引用一個段時發生的, 並且因爲那個段的描述符是空描述符.

注意, 當經過iret指令從中斷處理程序返回時, 處理器並不會自動彈出錯誤代碼. 所以, 對那些有異常代碼的異常處理程序來講, 在執行iret以前必須先從棧中移去錯誤代碼.

對於外部異常(經過處理器引腳觸發), 以及用軟中斷指令int n引起的異常, 處理器不會壓入錯誤代碼, 即便它本來是一個有錯誤代碼的異常.

8259A芯片初始化

一旦設置了中斷描述符表, 並加載了IDTR寄存器(用lidt指令), 處理器的中斷機制就開始起做用了. 但如今還不宜開放硬件中斷. 在保護模式下, 若是計算機系統的可編程中斷控制器芯片仍是8259A, 那就得從新進行初始化, 事實上, 8259A並無過期, 在單處理器系統中, 它依然健在. 從新初始化的緣由是其主片的中斷向量和處理器的異常向量衝突. 計算機啓動以後, 主片的中斷向量爲0x08~0x0F, 從片的中斷向量爲0x70~0x77, 在以8086位處理器的系統中, 這沒有什麼問題, 在32位處理器上,  0x08~0x0f已經被處理器用作異常向量.

好在8259A(以及I/O APIC)都是可編程的, Intel公司建議, 中斷向量0x20~0xff是用戶能夠自由分配的部分. 那麼, 咱們能夠設置8259A的主片, 把它的中斷向量改爲0x20~0x27.

對8259A編程須要使用初始化命令字(Initialize Command Word: ICW), 以設置它的工做方式, 共有4個初始化命令字. 分別是ICW1~ICW4, 都是單字節命令.ICW1用於設置中斷請求的觸發方式, 以及級聯的芯片數量; ICW2用於設置每一個芯片的中斷向量; ICW3用於指定用哪一個引腳實現芯片的級聯; ICW4用於控制芯片的工做方式.

主片的端口是0x20和0x21, 從片的端口是0xa0和0xa1, 要發送初始化命令字給8259A, 對於主片來講, 須要先向0x20端口發送ICW1, 而對於從片來講, 這個端口是0xa0. 這是一個標誌, 每次8259A接到ICW1時, 都意味着一個新的初始化過程開始了.

從0x20/0xa0端口接受命令字ICW1後, 8259A期待從0x21/0xa1端口接受命令字ICW2, 可是, 它是否接受ICW3和ICW4, 還要看ICW1的內容. 以下圖所示, ICW1的位0決定了是否有ICW4命令, 位1指示是否爲多片級聯. 若是是多片級聯, 那麼, 一定有ICW3命令. 這樣一來, 8259A就知道, 在接受了ICW2以後, 是否還要在相同的端口(0x21/0xa1)上依次再接受ICW3和ICW4.

注意, 上圖中, 深色的比特位表示它被保留, 或者不用, 使用途中所標註的固定值(0或1); 有些雖然不是深色, 但也標註了固定值(0或1),這些位是有意義的, 能夠設置或改變, 具體含義可參考芯片手冊.  

轉換後援緩衝期的刷新

開啓也功能時, 處理器的頁部件要把線性地址轉換成物理地址, 而訪問頁目錄表和頁表是至關費時的. 所以, 把頁表項預先存放處處理器中, 能夠加考哪一個地址轉換速度. 爲此, 處理器專門構造了一個特殊的高速緩存裝置, 叫作轉換後援緩衝器(Translation Lookaside Buffer:TLB). 事實上, 對該緩衝器的命名可謂五花八門, 從"轉換旁路緩衝器", "轉換後備緩衝區"到"快表"不一而足.

如上圖所示, 這是TLB的結構, 它分爲兩大部分, 第一部分是標記, 其內容爲線性地址的高20位; 第二部分是頁表數據, 包括屬性, 訪問權和頁物理地址的高20位. 在分頁模式下, 當段部件發出一個線性地址時, 處理器用線性地址的高20位來查找TLB(用線性地址的高20位和TLB中的標記比對查找), 若是找到匹配項(命中), 則直接使用數據部分物理地址做爲轉換用的地址; 若是檢索不成功(不中), 則處理器還得花時間訪問內存中的頁目錄表和頁表, 找到那個頁表項, 而後將它填寫到TLB中, 以備後用. TLB容量不大,  若是它裝滿了, 則必須淘汰掉那些用的較少的項目.

TLB中的屬性位來自頁表項, 好比D位等; 訪問權來自頁目錄項和對應的頁表項, 好比RW位和US位等. 問題是, 就RW位和US位來講, 頁目錄項和頁表項都有着兩位, 以哪個爲準呢? 在分頁機制中, 對頁的訪問控制按最嚴格的訪問權進行. 對於某個線性地址, 若是其頁目錄項的RW爲0而其也表項的RW爲1, 則按RW位是0執行. 也就是說, TLB中的訪問權, 是頁目錄項和頁表項中, 對應訪問權的邏輯與.

處理器僅僅緩存那些P位是1的頁表項, 並且, TLB的工做和CR3的PCD和PWT位無關, 不受這兩位影響. 另外, 對於頁表項的修改不會同時反映到TLB中, 是的, 這是很糟糕的, 若是內從中的頁表項已經修改, 但TLB中對應的條目尚未更新, 那麼, 轉換後的物理地址一定是錯誤的.

能夠將CR3寄存器的內容都出, 再原樣寫入, 這樣就會是的TLB中的全部條目失效. 固然, 這是比較直接的辦法. 當任務切換時, 由於要重新任務中的CR3寄存器域加載頁目錄項, 也會隱式得致使TLB中的全部條目無效. 注意, 上述方法對於那些標記爲全局(G位 = 1)的頁表項來講無效, 不起做用.

也能夠用指令invlpg來刷新TLB中的單個條目. 固然, 要作到這一點, 必須指定一個線性地址, 處理器用給出的線性地址搜索TLB, 找到那個條目, 而後從新加載它. invlpg指令格式:

invlpg m

 

該指令的操做數是一個內存地址, 當指令執行時, 處理器首先肯定該線性地址位於哪一個頁內, 而後刷新相應的TLB條目. 它的操做數之因此是內存地址, 而不是要給當即數, 是應爲, TLB是一個附加的硬件機構, 只有在處理器正常訪問內存時纔會致使它的填充和更新, 所以, 處理器用一個訪問內存的操做來促使TLB條目的更新會更方便. invlpg是特權指令, 當前特權級必須爲0. 該指令不影響任何標誌位.

相關文章
相關標籤/搜索