2. 实现串口接收中断
任务目标
在BIOS开发环境中,配置串口(COM1,I/O端口0x3F8)的接收中断,编写中断服务程序(ISR),实现从串口接收单个字符并将其回显(发送回同一串口)。本案例聚焦于8259A中断控制器与16550 UART的初始化及中断处理流程。
环境与依赖
- 处理器:x86(实模式)
- 中断控制器:8259A(主片基地址0x20)
- 串口芯片:16550 UART(COM1基地址0x3F8)
- 中断向量号:IRQ4对应中断向量0x0C(主片第4个中断请求)
- 汇编器:NASM(或兼容x86汇编器)
- 运行环境:模拟器(如QEMU)或真实硬件
实现原理
串口接收中断的完整链路为:UART收到字符后,其接收缓冲寄存器(RBR)有数据,触发中断请求;UART的中断输出引脚连接到8259A主片的IRQ4;8259A将IRQ4映射为中断向量0x0C并通知CPU;CPU响应中断后跳转到我们注册的ISR。ISR中需要读取RBR以清除中断状态,然后将该字符写入发送保持寄存器(THR)完成回显,最后向8259A发送EOI(End of Interrupt)命令。
完整代码
以下代码使用NASM语法,包含完整的初始化、ISR实现和主循环。代码可在实模式下直接运行,通过QEMU的-serial参数进行验证。
;--------------------------------------------------------
; 串口接收中断实现 - COM1 (0x3F8), IRQ4 → 向量 0x0C
; 功能:接收字符并回显
; 汇编:nasm -f bin uart_int.asm -o uart_int.bin
;--------------------------------------------------------
[org 0x7C00]
[bits 16]
; 初始化段寄存器
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; 保存原中断向量
mov ax, [0x0C * 4]
mov [old_int_off], ax
mov ax, [0x0C * 4 + 2]
mov [old_int_seg], ax
; 设置新中断向量
mov word [0x0C * 4], uart_isr
mov word [0x0C * 4 + 2], cs
; 初始化8259A(主片)
mov al, 0x11 ; ICW1: 边沿触发,级联,需要ICW4
out 0x20, al
mov al, 0x08 ; ICW2: 中断向量基地址 0x08
out 0x21, al
mov al, 0x04 ; ICW3: 从片连接在IR2
out 0x21, al
mov al, 0x01 ; ICW4: 8086模式
out 0x21, al
mov al, 0xEF ; OCW1: 只屏蔽IRQ4(清除bit4)
out 0x21, al
; 初始化UART (COM1)
mov dx, 0x3FB ; LCR (Line Control Register)
mov al, 0x80 ; DLAB = 1
out dx, al
mov dx, 0x3F8 ; DLL (Divisor Latch Low)
mov al, 0x0C ; 波特率 9600 (115200/9600=12)
out dx, al
mov dx, 0x3F9 ; DLM (Divisor Latch High)
mov al, 0x00
out dx, al
mov dx, 0x3FB ; LCR
mov al, 0x03 ; 8位数据,无校验,1停止位,DLAB=0
out dx, al
mov dx, 0x3F9 ; IER (Interrupt Enable Register)
mov al, 0x01 ; 使能接收数据可用中断
out dx, al
mov dx, 0x3FC ; MCR (Modem Control Register)
mov al, 0x0B ; 启用OUT2(使中断输出到8259A)
out dx, al
sti ; 开中断
; 主循环
main_loop:
hlt ; 等待中断
jmp main_loop
;--------------------------------------------------------
; 串口接收中断服务程序
;--------------------------------------------------------
uart_isr:
push ax
push dx
mov dx, 0x3F8 ; RBR (Receiver Buffer Register)
in al, dx ; 读取接收字符(同时清除中断状态)
mov dx, 0x3F8 ; THR (Transmitter Holding Register)
out dx, al ; 回显字符
mov al, 0x20 ; EOI命令
out 0x20, al ; 发送到8259A主片
pop dx
pop ax
iret
;--------------------------------------------------------
; 数据区
;--------------------------------------------------------
old_int_off: dw 0
old_int_seg: dw 0
times 510-($-$$) db 0
dw 0xAA55 ; 引导扇区标志
运行与验证
- 编译:
nasm -f bin uart_int.asm -o uart_int.bin - 运行(QEMU):
qemu-system-i386 -fda uart_int.bin -serial stdio - 验证步骤:
- 在QEMU终端中直接输入字符(如字母
A),该字符会通过串口发送到虚拟机。 - UART收到字符后触发IRQ4中断,ISR读取该字符并写回THR。
- 回显的字符会出现在QEMU终端中。若输入
A后终端显示A,则功能正常。
- 在QEMU终端中直接输入字符(如字母
- 通过条件:每个输入的字符都能立即在终端上显示出来,且系统不崩溃、不进入死循环。
关键说明
- MCR的OUT2位(bit3)必须置1,否则UART的中断信号无法到达8259A,这是初学者最容易遗漏的步骤。
- 读取RBR会自动清除接收中断状态;若不清除,中断会反复触发。
- 8259A初始化后,OCW1写入0xEF表示只开放IRQ4(bit4=0),其他IRQ保持屏蔽。
- 本代码未处理串口发送中断和线路状态中断,仅聚焦于接收中断的最小闭环。