在os的內容中,我們很多次說到中斷和異常,不過都沒有詳細的介紹,這裡會有一個詳細的介紹。
1.什麼是中斷和異常(INTERRUPT AND EXCEPTION),它們在什麼場景下產生?
你在圖書館看書,突然一個電話來了,說你家狗丟了,那麼你肯定要去找狗。可能你很快找到了,回去繼續看書,也有可能沒有那麼容易找到,你就不回去看書了。這個比如對應到計算機中,你是處理器,你家狗丟了這個通知就是中斷和異常的信號,你去找狗就是中斷和異常的處理。你回去繼續看書,就是中斷和異常處理完成後繼續執行之前的任務,不能回去看書,就是一些中斷和異常會導致用戶程序被os kill。
每種類型的中斷和異常,我們都會進行編號,稱為vector number。處理器支持從0到255的編號,最多256種情況。其中前32個是cpu自己用的,剩下的交給開發者。下面的表6-1顯示對應編號的具體中斷和異常類型:
那麼在計算機中,中斷和異常在哪些情況下會產生呢?
1.中斷:
1.外部的硬體產生(比如有一個網路I/O來了)。cpu有對應的引腳來接受中斷信息,分別為INTR和NMI。從INTR通知的中斷信號是可以屏蔽的,通過EFLAGS中的IF標誌來控制。NMI的中斷必須處理,並且NMI對應的中斷號默認是2.
2.程序自己通過調用INT指令產生。比如INT 35 就表示強制執行35號中斷的處理程序。
2.異常
處理器的異常有以下三個來源:
1.指令執行的時候產生的錯誤。對於每種錯誤處理器都會有相應的編號。異常通常被分為這三種不同的類型:faults,traps和aborts
2.使用INTO, INT 3, 和 BOUND命令產生。不過使用INT n指令來產生指令是有限制的。如果INT n指令指定的vertor number是處理器定義的異常。這種情況如果是硬體正常產生通常是會有錯誤代碼的,但是如果是指令產生這種異常,處理器在處理的時候是不會將錯誤代碼進行壓棧的。這樣在進行異常處理的時候,異常處理程序仍然會去對錯誤代碼進行出棧操作,因為沒有錯誤代碼,所以pop的時候就會將eip設置為錯誤數據,這樣返回的時候執行點就不是之前的了。
3.機器檢查的異常。P6和Pentium系列的處理器提供了內部和外部的機器檢查機制來檢查內部晶元硬體的執行和匯流排事務。如果這種檢查發現了錯誤,那麼處理器會發起一個vector 18的機器檢查異常並且返回一個錯誤代碼。
上面說到異常會被分為faults,traps,aborts.這是根據這些異常產生的方式和異常處理後重新開始執行指令的地點來分類的。
不可屏蔽的中斷NMI (NONMASKABLE INTERRUPT)
不管是這2種中的哪一種,處理器都會立刻調用NMI中斷對應的中斷處理程序。並且在處理程序執行期間保證沒有其他的中斷產生,包括NMI中斷。當然,這2種方式產生的NMI中斷不會被EFLAGS中的IF標誌位屏蔽。
還有一種方式,也就是通過INTR引腳產生可屏蔽中斷,不過中斷的vector number是2。這種模擬的方式並不是真正的NIM中斷,所以在進行相應處理的時候,處理器對應的專門用來支持NMI中斷的硬體並不會工作,
EFLAGS中的IF標記位可以用來開啟或者屏蔽INTR引腳的中斷,不過不會影響NMI中斷的接受,處理器產生的異常也不會受到影響。可以分別使用STI(set interrupt-enable flag )和CLI (clear interrupt-enable flag) 來設置和清除IF標誌位。下面的三種情況也會對IF標誌位產生影響。
當我們在切換棧的時候,可能會使用如下的2個指令來完成操作:
MOV SS, AX
MOV ESP, StackTop
但是問題在於,如果第一條指令執行完成後,發生了中斷或者異常,那麼在中斷或者異常的處理程序執行的過程中,對應的棧的地址空間就是異常的了。為了解決這個問題,處理器在修改ss寄存器的命令後禁止中斷,debug異常,single-step trap ,直到下一條指令執行完畢。當然如果使用LSS命令來修改SS寄存器的值就不會出現這個問題了。
中斷和異常的優先順序
上面表中的分類等級在各個架構中都是統一的,不過在各個分類中不同的中斷或者異常的優先順序各個架構就會不一樣了。處理器會首先處理最高等級的異常或者中斷。低等級別的異常會被丟掉,低等級別的中斷會被掛起。當中斷處理程序返回到程序或者任務產生異常或者中斷的地方,被丟掉的異常會再次產生,
中斷描述符表IDT( INTERRUPT DESCRIPTOR TABLE )
接收到中斷後,處理器應該怎麼處理呢?首先每種類型的中斷和異常對應的處理程序可以在IDT中找到。和GDT和LDT類型,IDT也是一個數組,其中的元素就是8byte大小的門描述符(gate descriptor)。你可以把IDT放在任何位置,然後將對應的線性地址基地址記載IDTR寄存器中就可以了,下面的圖6-1展示了2者的關係和應用。
IDT中包含3中類型的門描述符:
1.任務門描述符 Task-gate descriptor
2.中斷門描述符 Interrupt-gate descriptor
3.陷阱門描述符 Trap-gate descriptor
下面的圖6-2顯示了各種描述符的內容格式:
下面我們來看下具體的中斷和異常處理的過程。
處理器對異常和中斷處理程序的調用,類似於使用CALL指令調用一個過程或者任務一樣。首先根據中斷或者異常編號在IDT中找到對應的描述符。如果是trap-gate 或者Interrupt-gate,那麼就類似於使用CALL命令調用一個過程。如果是task-gate,則會進行上下文切換,執行task-gate對應的任務,類似於CALL調用一個task gate。圖6-3顯示了這個過程:
如果中斷和異常處理過程的許可權和目前代碼的許可權不同:
1.從當前任務的TSS中獲取相應許可權的棧信息,在新的棧上,處理器將被中斷的過程的段選擇子和棧指針信息入棧
2.處理器將當前過程的EFLAGS, CS, 和 EIP 寄存器的值入棧
如果相同:
1.將EFLAGS, CS, 和 EIP 寄存器的值入棧
2.如果有錯誤代碼也入棧
下面的圖6-4顯示了這2種不同情況:
不管是通過interrupt gate還是trap gate來訪問異常或者中斷的處理程序,處理器都會在保存了EFLAGS 寄存器值後將TF標誌位清空。清除 TF標誌位可以防止指令調製影響中斷的響應。 IRET指令會將TF的值重新恢復。
interrupt gate還是trap gate唯一區別在於對於IF標誌位的使用。interrupt gate在調用的時候會清除IF標誌位,但是trp gate不會。
如果異常或者中斷的處理程序是task gate,那麼在進行異常或者中斷處理的時候會產生任務的切換,下面是使用任務來作為中斷或者異常處理的優勢:
當然劣勢也很明顯,就是任務切換太耗費資源,所以效率很慢。
下面的圖6-5是調用過程的圖示。
推薦閱讀: