如何处理实模式(16位)下的中断?——32位保护模式操作系统切换至实模式调用BIOS中断故障排查
问题分析与解决方案
我来帮你拆解下这个问题——从Bochs的报错日志和你的切换代码来看,你其实已经成功进入实模式了(日志里明确显示real mode,且CR0的PE位已经被清除),问题出在中断设置时机和BIOS中断调用的错误使用上,具体如下:
核心问题点
1. 中断调用完全错误
看Bochs日志最后一行:>> int 0x21 : CD21,但你的代码里写的是:
mov ax,4c00h int 10h
这是典型的错误搭配:ax=4c00h是DOS中断int 0x21的程序退出功能,你却用它调用视频中断int 10h,完全不匹配。而且实模式下没有DOS环境,调用int 0x21会触发未定义的中断向量,直接导致报错。
2. IDT加载时机滞后
你在切换到实模式后先开了中断(sti),再加载实模式IVT(中断向量表)。如果在sti到lidt之间有中断触发,CPU会使用旧的保护模式IDT,而实模式下的中断向量范围(0-255)会超出保护模式IDT的限制,直接抛出interrupt(real mode) vector > idtr.limit错误。
3. 段寄存器设置混乱
你在realMode标签下反复修改段寄存器:先把ds/es设为0,又改成0x7c0;ss先设为0再改成0x8000。虽然这种操作本身不致命,但会增加调试复杂度,实模式下建议统一设置段寄存器为符合BIOS预期的值(通常ds/es=0,栈设置在高地址比如0xf000)。
修正后的代码示例
global go16 ;______________________________________________________________________________________________________ ;Switch to 16-bit real Mode ;IN/OUT: nothing go16: [BITS 32] cli ; 全程关闭中断,直到所有实模式设置完成 pop edx ; 保存返回地址 jmp 0x20:PM16 ; 跳转到16位保护模式代码段(确保GDT中0x20是16位代码段,基址0,限长0xFFFF) [BITS 16] PM16: mov ax, 0x28 ; 加载16位数据段选择器(GDT中0x28对应16位数据段,基址0,限长0xFFFF) mov ss, ax mov ds, ax mov es, ax mov gs, ax mov fs, ax mov sp, 0x7c00+0x200 ; 设置保护模式下的临时栈 ; 保存并修改CR0,关闭PE位 mov eax, cr0 mov [savcr0], eax and eax, 0xfffffffe ; 清除PE位(第0位) mov cr0, eax jmp 0:realMode ; 远跳冲刷CPU流水线,彻底进入实模式 realMode: ; 初始化实模式段寄存器:段值左移4位为基址,这里设置为0对应基址0 mov ax, 0 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov sp, 0xf000 ; 设置实模式栈(BIOS通常使用高地址栈) lidt [idt_real] ; 先加载实模式IVT,再开中断 sti ; 测试视频模式:设置为80x25文本模式(AH=00h是设置视频模式,AL=03h是文本模式) mov ax, 0x0003 int 0x10 ; 暂停系统,观察结果 cli hlt ; Real mode interrupt vector table definition idt_real: dw 0x3ff ; IVT限长:256个中断向量,每个4字节,共1KB dd 0 ; IVT基址:实模式下默认从0x0000开始 savcr0: dd 0
额外注意事项
- 确保GDT中的
0x20(代码段)和0x28(数据段)是16位保护模式段:段描述符的D/B位设为0,限长设为0xFFFF,基址设为0。 - 切换过程中必须全程关闭中断,直到实模式IVT加载完成,避免中途触发中断导致错误。
- 调用BIOS中断时,严格遵循BIOS中断规范:比如
int 10h的视频模式设置功能,必须保证AH=00h,AL为对应的模式号。
内容的提问来源于stack exchange,提问作者Konect Team




