Skip to content

外中断

接口芯片和端口

外设(键盘、鼠标、串口等)通过接口芯片与 CPU 连接。接口芯片有两类端口:

  • 控制端口:CPU 写入,告诉设备做什么
  • 状态端口:CPU 读取,了解设备状态
  • 数据端口:传输实际数据

当外设输入数据后,相关接口芯片会向 CPU 发出中断信息,这就是外中断。

可屏蔽中断与不可屏蔽中断

类型引脚说明
可屏蔽中断(INTR)INTR 引脚IF=1 时才响应;可以被 cli 屏蔽
不可屏蔽中断(NMI)NMI 引脚CPU 必须立即响应,不可屏蔽

控制可屏蔽中断:

asm
sti     ; IF=1,开中断(允许响应可屏蔽中断)
cli     ; IF=0,关中断(屏蔽可屏蔽中断)

在编写中断例程时,通常需要 cli 保护临界区,完成后 sti 恢复。

键盘输入与 int 9

扫描码与键盘缓冲区

  1. 按下/松开某键 → 键盘产生扫描码
  2. 扫描码通过 60h 端口进入 CPU
  3. CPU 响应 9 号中断,执行 BIOS int 9 中断例程
  4. int 9 读取 60h 端口扫描码,转化为 ASCII 码,存入键盘缓冲区

键盘缓冲区是一个环形队列,容量 15 个字(每个字 = 扫描码 + ASCII 码)。

BIOS int 16h:读取键盘缓冲区

asm
mov ah, 0
int 16h         ; 等待并读取键盘输入
; 返回:ah = 扫描码,al = ASCII 码

编写自定义 int 9 例程

目标: 按下 ESC 键时不退出,改变屏幕字符颜色;按其他键正常显示。

思路:

  1. 保存原 int 9 中断向量
  2. 安装新例程,新例程先调用原例程(处理正常键盘逻辑),再自定义处理
  3. 程序退出前恢复原中断向量
asm
assume cs:code

stack segment
    db 128 dup (0)
stack ends

data segment
    dw 0, 0         ; 存原 int 9 的 IP 和 CS
data ends

code segment

start:
    ; 初始化栈
    mov ax, stack
    mov ss, ax
    mov sp, 128

    ; 保存原 int 9 向量
    mov ax, 0
    mov es, ax
    mov ax, data
    mov ds, ax
    mov ax, es:[9*4]        ; 原 IP
    mov ds:[0], ax
    mov ax, es:[9*4+2]      ; 原 CS
    mov ds:[2], ax

    ; 安装新 int 9
    cli
    mov word ptr es:[9*4], offset int9
    mov word ptr es:[9*4+2], cs
    sti

    ; 主程序:显示 a~z,按 ESC 退出,其他键改变颜色
    mov ax, 0b800h
    mov es, ax
    mov ah, 7               ; 初始颜色属性(白色)
    mov bx, 0

showloop:
    mov al, 'a'
    mov es:[bx], al         ; 显示字符
    mov es:[bx+1], ah       ; 颜色
    add bx, 2
    cmp bx, 160*25
    jb showloop

    mov bx, 0               ; 等待按键(通过 int 16h 阻塞)
wait:
    mov ah, 0
    int 16h
    cmp al, 1bh             ; ESC 的 ASCII 码
    je quit
    inc ah                  ; 改变颜色
    mov bx, 0
    ; 重新用新颜色刷新屏幕
refresh:
    mov es:[bx+1], ah
    add bx, 2
    cmp bx, 160*25
    jb refresh
    jmp wait

quit:
    ; 恢复原 int 9
    mov ax, 0
    mov es, ax
    mov ax, data
    mov ds, ax
    cli
    mov ax, ds:[0]
    mov es:[9*4], ax
    mov ax, ds:[2]
    mov es:[9*4+2], ax
    sti

    mov ax, 4c00h
    int 21h

; 新 int 9 中断例程
int9:
    push ax
    push bx
    push ds

    ; 先调用原 int 9 处理键盘输入
    pushf
    call dword ptr ds:[0]   ; 调用原例程(模拟 int 调用)

    ; 读取刚输入的扫描码(已被原例程从端口读走,查缓冲区)
    ; (此处简化,原版需从 60h 端口读,此为示意)

    pop ds
    pop bx
    pop ax
    iret

code ends
end start

小结:外中断处理流程

外设动作
  → 接口芯片向 CPU 发 INTR 信号
  → CPU 在执行完当前指令后检测 INTR
  → 若 IF=1,执行中断过程(pushf, cli, push CS:IP, 读向量表)
  → 跳到中断例程执行
  → iret 返回

与内中断的区别:外中断来自外设,受 IF 标志控制(可屏蔽);内中断由 CPU 内部逻辑触发,不受 IF 影响(除了单步中断 type 1)。