x86 彙編/高階中斷
在關於中斷的章節中,我們提到了軟體中斷的存在,並且它們可以由系統安裝。本頁將深入探討這個過程,並討論如何安裝 ISR、系統如何找到 ISR 以及處理器實際上如何執行中斷。
當發生中斷時呼叫的實際程式碼稱為中斷服務例程 (ISR)。當發生異常時,程式呼叫中斷,或者硬體引發中斷,處理器使用幾種方法之一(將在後面討論)將控制權轉移到 ISR,同時允許 ISR 在執行完成後安全地將控制權轉移回它所中斷的內容。至少, FLAGS 和 CS:IP 會被儲存,ISR 的 CS:IP 會被載入;但是,一些機制會導致在 ISR 開始之前發生完整的任務切換(並在其結束時發生另一個任務切換)。
在原始的 8086 處理器(以及所有真實模式下的 x86 處理器)中,中斷向量表控制了 ISR 的流程。IVT 從記憶體地址 0x00 開始,可以高達 0x3FF,最多可以有 256 個 ISR(從中斷 0 到 255)。IVT 中的每個條目包含 2 個字的資料:IP 的值和 CS 的值(按此順序)。例如,假設我們有以下中斷
int 14h
當我們觸發中斷時,處理器會轉到 IVT 中的第 21 個位置 (14h = 20,索引從 0 開始)。由於每個表條目為 4 個位元組(2 個位元組 IP,2 個位元組 CS),因此微處理器會轉到位置 [4*14H]=[50H]。在位置 50H 是新的 IP 值,在位置 52H 是新的 CS 值。硬體和軟體中斷都儲存在 IVT 中,因此安裝新的 ISR 就像將函式指標寫入 IVT 一樣簡單。在更新的 x86 模型中,IVT 被中斷描述符表取代。
當在真實模式下發生中斷時,FLAGS 暫存器會被壓入堆疊,然後是 CS,然後是 IP。iret 指令會恢復 CS:IP 和 FLAGS,使被中斷的程式不受影響地繼續執行。對於硬體中斷,所有其他暫存器(包括通用暫存器)必須顯式儲存(例如,如果中斷例程使用 AX,它應該在開始時壓入 AX,並在結束時彈出 AX)。對於軟體中斷,最好儲存所有暫存器,除了包含返回值的暫存器。更重要的是,任何被修改的暫存器都必須記錄在案。
從 286 開始(但在 386 上擴充套件),中斷可以由記憶體中名為中斷描述符表 (IDT) 的表來管理。IDT 僅在處理器處於保護模式時才會起作用。與 IVT 非常相似,IDT 包含指向 ISR 例程的指標列表;但是,現在有三種方法可以呼叫 ISR
- 任務門:這些會導致任務切換,允許 ISR 在其自己的上下文中執行(使用它自己的 LDT 等)。請注意,IRET 仍然可以用於從 ISR 返回,因為處理器在 ISR 的任務段中設定了一個位,導致 IRET 執行任務切換以返回到先前的任務。
- 中斷門:這些類似於原始的中斷機制,將 EFLAGS、CS 和 EIP 放入堆疊。ISR 可以位於特權級別等於或高於當前執行段的段中,但不能位於特權級別較低的段中(特權級別數值較低,級別 0 是最高特權級別)。
- 陷阱門:這些與中斷門相同,只是它們不會清除中斷標誌。
以下 NASM 結構表示 IDT 條目
struc idt_entry_struct base_low: resb 2 sel: resb 2 always0: resb 1 flags: resb 1 base_high: resb 2 endstruc
| 欄位 | 中斷門 | 陷阱門 | 任務門 |
|---|---|---|---|
| base_low | ISR 入口地址的低字 | 未用 | |
| sel | ISR 的段選擇器 | TSS 描述符 | |
| always0 | 位 5、6 和 7 應為 0。位 0-4 未用,可以保留為零。 | 未用,可以保留為零。 | |
| flags | 低 5 位是(MSB 優先):01110,位 5 和 6 形成 DPL,位 7 是 Present 位。 | 低 5 位是(MSB 優先):01111,位 5 和 6 形成 DPL,位 7 是 Present 位。 | 低 5 位是(MSB 優先):00101,位 5 和 6 形成 DPL,位 7 是 Present 位。 |
| base_high | ISR 入口地址的高字 | 未用 | |
where
- DPL 是描述符特權級別(0 到 3,其中 0 是最高特權級別)
- Present 位指示該段是否在 RAM 中存在。如果此位為 0,則如果觸發中斷,將發生段不存在錯誤(異常 11)。
這些 ISR 通常由作業系統安裝和管理。只有具有足夠特權修改 IDT 內容的任務才能直接安裝 ISR。
ISR 本身必須放置在適當的段中(如果使用任務門,則必須設定適當的 TSS),特別是在特權級別永遠不低於執行程式碼的特權級別的情況下。不可預測中斷(例如硬體中斷)的 ISR 應放置在特權級別 0(這是最高特權級別)中,這樣在執行特權級別 0 任務時就不會違反此規則。
請注意,ISR,特別是硬體觸發的 ISR,應始終存在於記憶體中,除非有充分的理由不將其存在於記憶體中。大多數硬體中斷需要及時處理,而交換會導致明顯的延遲。此外,一些硬體 ISR(例如硬碟 ISR)可能在交換過程中需要。由於硬體觸發的 ISR 在不可預測的時間中斷程序,因此鼓勵裝置驅動程式程式設計師將 ISR 保持得非常短。ISR 通常只是安排核心任務完成必要的工作;此核心任務將在下一個合適的機會執行。因此,硬體觸發的 ISR 通常非常小,交換它們到磁碟幾乎沒有好處。
但是,即使 ISR 實際上存在於 RAM 中,也可能希望將 Present 位設定為 0。作業系統可以將段不存在處理程式用於其他功能,例如監控中斷呼叫。
x86 包含一個暫存器,其作用是跟蹤 IDT。此暫存器稱為IDT 暫存器,或簡稱為“IDTR”。IDT 暫存器長 48 位。低 16 位稱為 IDTR 的 LIMIT 部分,高 32 位稱為 IDTR 的 BASE 部分
|LIMIT|----BASE----|
BASE 是 IDT 在記憶體中的基地址。IDT 可以位於記憶體中的任何位置,因此 BASE 需要指向它。LIMIT 欄位包含 IDT 的當前長度。
要載入 IDTR,使用LIDT指令
lidt [idtr]
要儲存 IDTR,使用SIDT指令
sub esp,6 sidt [esp] ;store the idtr to the stack
中斷指令
[edit | edit source]int arg
呼叫指定的硬體中斷。
into 0x04
如果溢位標誌被置位,則呼叫中斷 4。
iret
從中斷服務例程 (ISR) 返回。
預設 ISR
[edit | edit source]良好的程式設計實踐是提供一個預設的 ISR,它可以作為未使用的中斷的佔位符。 這樣做是為了防止在發生無法識別的中斷時執行隨機程式碼。 預設 ISR 可以像一個簡單的 iret 指令一樣。
但是,請注意,在 DOS(處於真實模式)下,某些 IVT 條目包含指向重要位置(但不一定是可執行位置)的指標。 例如,條目 0x1D 是指向影片控制器影片初始化引數表的遠指標,條目 0x1F 是指向圖形字元位圖表的指標。
停用中斷
[edit | edit source]有時,重要的是一個例程不會意外中斷。 因此,x86 允許在必要時停用硬體中斷。 這意味著處理器將忽略它從中斷控制器接收到的任何中斷訊號。 通常,控制器將一直等待,直到處理器接受中斷訊號,因此中斷被延遲而不是被拒絕。
x86 在 FLAGS 暫存器中有一個中斷標誌 (IF)。 當此標誌設定為 0 時,硬體中斷被停用,否則它們被啟用。 命令 cli 將此標誌設定為 0,而 sti 將其設定為 1。 將值載入到 FLAGS 暫存器中的指令(例如 popf 和 iret)也可能修改此標誌。
請注意,此標誌不會影響 int 指令或處理器異常;只有硬體生成的硬體中斷。 另請注意,在保護模式下,執行許可權低於 IOPL 的程式碼如果使用 cli 或 sti,將生成一個異常。 這意味著作業系統可以禁止“使用者”程式停用中斷,從而獲得對系統的控制權。
當中斷處理程式開始時,中斷會自動被停用; 這確保處理程式不會被中斷(除非它發出 sti)。 裝置驅動程式等軟體可能需要精確的計時,因此不應被中斷。 這也有助於避免在短時間內兩次發生相同中斷時出現的問題。 請注意,iret 指令在中斷處理程式開始之前恢復 FLAGS 的狀態,從而允許在中斷處理程式完成後發生進一步的中斷。
在執行某些系統任務(例如進入保護模式時)也應停用中斷。 這包括執行多個步驟,如果處理器嘗試在該過程完成之前呼叫中斷處理程式,則有可能導致異常、執行無效程式碼、破壞記憶體或導致其他問題。