跳轉到內容

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。作業系統可以將段不存在處理程式用於其他功能,例如監控中斷呼叫。

IDT 暫存器

[編輯 | 編輯原始碼]

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 暫存器中的指令(例如 popfiret)也可能修改此標誌。

請注意,此標誌不會影響 int 指令或處理器異常;只有硬體生成的硬體中斷。 另請注意,在保護模式下,執行許可權低於 IOPL 的程式碼如果使用 clisti,將生成一個異常。 這意味著作業系統可以禁止“使用者”程式停用中斷,從而獲得對系統的控制權。

當中斷處理程式開始時,中斷會自動被停用; 這確保處理程式不會被中斷(除非它發出 sti)。 裝置驅動程式等軟體可能需要精確的計時,因此不應被中斷。 這也有助於避免在短時間內兩次發生相同中斷時出現的問題。 請注意,iret 指令在中斷處理程式開始之前恢復 FLAGS 的狀態,從而允許在中斷處理程式完成後發生進一步的中斷。

在執行某些系統任務(例如進入保護模式時)也應停用中斷。 這包括執行多個步驟,如果處理器嘗試在該過程完成之前呼叫中斷處理程式,則有可能導致異常、執行無效程式碼、破壞記憶體或導致其他問題。

華夏公益教科書