iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
自我挑戰組

30 天 hypervisor 入門系列 第 7

Day07 Real Mode 中斷與 IRQ 測試

  • 分享至 

  • xImage
  •  

Real mode 中斷

在實模式(Real Mode)下,中斷的處理是透過 中斷向量表(IVT, Interrupt Vector Table)來完成的。

  1. 地址範圍:0x0000 ~ 0x03FF,共 256 個中斷向量。
  2. 每個向量表項大小:4 bytes,其中,低 2 byte 放 offset (IP),高 2 byte 放 segment (CS)
    舉例來說,BIOS 的 INT 15h, AX=E820h 就是透過查詢 IVT 進入 BIOS 的記憶體探測。
    此外,實模式下的實體地址計算公式為:

[segment:offset] = (segment << 4) + offset
這種「段基址 + 偏移量」的表示方法,使得 16 位元暫存器可以透過拼接將記憶體訪問範圍從 64 KB 延伸到 1MB(每個 segment 一樣只能訪問 64 KB)。

當中斷觸發時將會觸發以下步驟:

  1. CPU 會 push FLAGS、CS、IP 到 stack。
  2. 從 IVT 中讀取 第 n 項 [CS:IP]。
  3. 跳轉到該位置執行 中斷服務程式 (ISR)。

8259 PIC 與中斷重映射

雖然 IVT 提供了 256 個向量,在 PC 架構中,並不是所有中斷都由軟體 INT n 觸發。有一部分來自外部硬體。
外部裝置的 IRQ 訊號需要經過中斷控制器轉換如 8259 / APIC,才能映射到對應的向量號。

在 X86 架構下 0 ~ 0x13 的向量號是保留給 CPU 觸發 EXECPTION 的,而 0X14~0X1F 則為保留,為避免衝突我們可定義 8259A 的中斷從 0X20 開始。

  • IRQ0 ~ IRQ7 -> INT 0x20 ~ 0x27
  • IRQ8 ~ IRQ15 -> INT 0x28 ~ 0x2F (這一部分的是銜接在 irq2 的引腳上需要兩組 EOI 已表示中斷處理完成)
    因為這部分的 8259 的硬體設定比較繁瑣,這裡我請 chatgpt 生成設定程式碼。
cli

mov al, 0x11
out 0x20, al       ; ICW1 master
out 0xA0, al       ; ICW1 slave

mov al, 0x20
out 0x21, al       ; ICW2 master: IRQ0 起始於 INT 0x20
mov al, 0x28
out 0xA1, al       ; ICW2 slave:  IRQ8 起始於 INT 0x28

mov al, 0x04
out 0x21, al       ; ICW3 master: IR2 接 slave
mov al, 0x02
out 0xA1, al       ; ICW3 slave: 連接在 master IR2

mov al, 0x01
out 0x21, al       ; ICW4 master: 8086 模式
out 0xA1, al

; 僅允許 IRQ0
mov al, 0xFE
out 0x21, al
mov al, 0xFF
out 0xA1, al

這樣一來,我們就能把定時器的 IRQ0 映射到 INT 0x20。

PIT 產生 IRQ0

PIT (Programmable Interval Timer) 是早期 PC 的定時器,頻率為 1.193182 MHz。透過設定除數,可以產生週期性中斷。
以下範例設定 PIT channel 0,以產生約 290 Hz 的中斷頻率

mov al, 0x36
out 0x43, al    ; Ch0, LSB+MSB, Mode2 (rate generator)

mov al, 0x00
out 0x40, al    ; divisor low
mov al, 0x10
out 0x40, al    ; divisor high → 除數 = 0x1000 = 4096

觸發頻率為 1193182 / 4096 ~ 290 Hz。

IRQ0 Handler 範例

當 IRQ0 發生時,PIC 會觸發 INT 0x20,CPU 會查找 IVT[0x20],跳到我們設置的 ISR:

irq0_handler:
    push ax
    push dx
    push bx

    mov dx, 0x00E9        ; debug port
    mov bx, 0x0500
    inc byte ptr [bx]     ; counter++
    mov al, [bx]
    cmp al, 10
    jb 1f
    mov byte ptr [bx], 0
    mov al, 0
1:
    add al, '0'
    out dx, al            ; 觸發 VM-exit 輸出 '0' ... '9'

    mov al, 0x20
    mov dx, 0x0020
    out dx, al            ; EOI → 告訴 master PIC 中斷處理完畢

    pop bx
    pop dx
    pop ax
    iret

這段程式做了三件事:

  1. 計數並輸出到 0xE9 i/o port,在 Host 可透過 VM-exit 觀測中斷。
  2. 發送 EOI (End Of Interrupt) 通知 PIC 該 IRQ 已處理完畢。
  3. iret 返回中斷前的程式。

Guest.S 架構

在 Guest code 中只需要

cli
; 初始化 PIC + PIT
sti
halt_loop:
    hlt
    jmp halt_loop

cli 可屏蔽中斷,這裡用於保證 Guest code 的初始化行為。 sti 則用於恢復中斷。
在初始化過程中,可直接將 irq0_handler 的 CS:IP 地址寫入到 IVT 的 0x20 * 4。 PIT 觸發 IRQ0 時就會執行我們定義的 irq0_handler 並觸發 VM exit,將 IO 的資料在 host 中打印。執行結果大致如下:
https://ithelp.ithome.com.tw/upload/images/20250921/201788144eKWNZgQYy.png


上一篇
Day 06 中斷事件
下一篇
Day08 eventfd
系列文
30 天 hypervisor 入門11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言