如何避免ISR(中断服务例程)陷入循环?QEMU自制OS开发问题
问题:除零中断触发后ISR陷入无限循环
我正在编写一款运行在QEMU上的操作系统,自定义了ISR。用__asm__ ("int $0");手动触发中断时能正常处理,但执行除零代码:
int a = 0; int b = 5; int c = b/a;
会导致中断处理程序反复调用,陷入循环。试过清除标志位、屏蔽中断都没用。
相关代码
C++中断处理函数
void ISR::__interrupt_handler(InterruptRegister *reg) { disp.print_screen("Recieved interrupt: "); char s[3]; int_to_string(reg->int_no, s); disp.print_screen(s); // 省略其他代码 }
汇编中断处理代码
[extern interrupt_handler] ; Common Interrupt Service Routine isr_common: cli ; Disable interrupts ; Save the registers pusha mov ax, ds ; Save the data segment push eax; Save the data segment mov ax, 0x10 ; Set the data segment to 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax ; Call the interrupt handler push esp ; Push the stack pointer call interrupt_handler pop eax ; Pop the stack pointer ; Restore the registers pop eax mov eax, 1 mov ds, ax mov es, ax mov fs, ax mov gs, ax popa ;Clean the pushed error code add esp, 8 ;sti ; Enable interrupts iret ; Pops CS, EIP, EFLAGS, SS, ESP isr0: push byte 0 push byte 0 jmp isr_common
问题原因与解决办法
1. 除零中断循环的核心原因
除零异常属于故障类异常,处理器触发这类异常时,会把EIP指向导致异常的那条指令。当你用iret返回时,处理器会重新执行这条指令,自然再次触发除零异常,陷入死循环。
而手动用int $0触发的是软件中断(陷阱类异常),EIP会指向中断指令的下一条,返回后不会重复执行触发指令,所以能正常退出。
2. 汇编代码中的致命错误
看你的寄存器恢复段:
pop eax mov eax, 1 mov ds, ax mov es, ax mov fs, ax mov gs, ax
你把之前保存的ds值从栈里弹出后,直接把eax改成了1,用这个错误的段值恢复段寄存器!这会导致后续内存访问错误,甚至间接加重中断循环问题。正确写法应该是:
pop eax ; 弹出之前保存的ds值 mov ds, ax mov es, ax mov fs, ax mov gs, ax
3. 打破循环的关键操作
要终止除零中断的循环,必须在中断处理函数中修改EIP寄存器,让处理器跳过导致除零的指令:
- 确保
InterruptRegister结构体包含eip字段(对应栈中保存的EIP值)。 - 当检测到中断号为0(除零)时,把
reg->eip增加对应指令的长度(x86平台上除法指令长度通常是2-3字节,需根据实际机器码确定)。这样iret返回后,处理器会执行下一条指令,不会重复触发异常。
4. 栈平衡修正
你的isr0中先push byte 0(占位错误码)再push byte 0(中断号),栈中此时只有两个字节数据。但你用了add esp, 8来清理,这会破坏栈平衡,导致iret时弹出错误的EIP/FLAGS值,引发更严重问题。应该改成add esp, 4。
5. 其他细节
- 不需要在
isr_common开头执行cli,处理器触发异常时会自动清除IF标志位(屏蔽可屏蔽中断),除非你在处理过程中需要手动开中断。
内容的提问来源于stack exchange,提问作者Jjkivai
相关产品推荐
相关产品推荐

