如何修复C语言中断处理程序中编译器用retf替代iret导致的栈偏移问题
看起来你遇到的是Watcom C对普通远函数和中断服务程序的返回指令差异问题——当BIOS触发中断时,CPU会自动把FLAGS、CS、IP依次压入栈,中断处理程序必须用iret指令返回才能完整弹出这三个值;但普通的far函数编译器会生成retf,只弹出CS和IP,少了FLAGS,自然会导致栈错位。下面给你几个靠谱的解决办法,按优雅程度排序:
1. 用Watcom专属的__interrupt修饰符声明中断函数
这是最省心的方式,Watcom C专门提供了__interrupt关键字标记中断服务程序(ISR),编译器会自动生成iret返回指令,而不是retf。只需要修改int_handler的声明和定义:
// 函数声明部分 void __interrupt far int_handler(void); // 函数定义部分 void __interrupt far int_handler(void) { print_hello(); }
这样编译时,编译器会自动识别这是一个中断服务程序,匹配CPU的中断栈帧结构(FLAGS→CS→IP)生成正确的返回指令,完全不需要手动干预。
2. 手动在中断函数末尾插入iret指令
如果不想用编译器的中断修饰符,可以直接在int_handler的末尾插入汇编版的iret,强制中断返回。Watcom在遇到显式的汇编返回指令时,会跳过自动生成的retf,所以这个方法也能正常工作:
void far int_handler(void) { print_hello(); // 手动插入iret指令,覆盖编译器默认的retf _asm { iret } }
注意这里不需要加return语句,因为iret会直接从中断返回,不会执行到函数末尾的编译器生成代码。
3. 规范的中断服务程序写法:保存/恢复寄存器+手动iret
上面的方法解决了返回指令的问题,但严格来说,中断服务程序应该保存所有会被修改的寄存器,避免破坏原程序的执行环境。你可以在int_handler里加上寄存器的保存和恢复逻辑,这也是生产环境中更规范的写法:
void far int_handler(void) { // 保存所有会被修改的通用寄存器 _asm { push ax push bx push cx push dx push si push di push bp } print_hello(); // 恢复寄存器顺序要和保存时相反 _asm { pop bp pop di pop si pop dx pop cx pop bx pop ax iret } }
虽然你的print_hello调用了BIOS的int 0x10,BIOS会保存大部分寄存器,但自己手动处理能避免任何潜在的寄存器冲突问题,让代码更健壮。
最后验证编译和链接
修改代码后,保持你原来的编译和链接命令即可:
wcc -0 -d0 -mc -s -wx -zl kernel.c wlink FILE kernel.obj FORMAT RAW BIN NAME kernel.bin OPTION NODEFAULTLIBS,OFFSET=4096,START=kernel_
加载到0x0000:0x1000后,中断处理程序的返回指令会正确弹出FLAGS、CS、IP,栈偏移的问题就解决了。




