ISR无法正常返回:Bochs报check_cs(0x0020)无效代码段错误
问题根源
- 直接将C函数作为IDT中断处理函数的错误:x86中断触发时CPU会生成特定栈帧(包含EIP、CS、EFLAGS等),C函数的调用栈帧结构与中断栈帧不兼容。你直接把
timer_isr这个C函数注册到IDT中,未通过汇编Stub处理栈帧转换,导致执行iret时栈状态异常,CS寄存器被设置为无效值。 - EOI发送错误:定时器IRQ0属于主PIC(PIC1),无需向从PIC(PIC2)发送EOI,多余操作可能干扰中断流程。
- C函数内直接使用
iret的错误:C函数执行过程中会修改栈指针(如压入EBP、分配局部变量),破坏了中断栈帧结构,此时执行iret无法正确恢复中断前的CPU状态。
修复步骤
1. 添加定时器ISR的汇编Stub
创建汇编文件(如isr_stubs.s),添加对应IRQ0的Stub:
[GLOBAL irq0_stub] [EXTERN timer_isr] irq0_stub: ; 保存所有通用寄存器 pusha ; 保存段寄存器 push ds push es push fs push gs ; 设置内核数据段选择子(假设GDT中内核数据段选择子为0x10) mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax ; 调用C语言实现的ISR逻辑 call timer_isr ; 恢复段寄存器 pop gs pop fs pop es pop ds ; 恢复通用寄存器 popa ; 仅向主PIC发送EOI(IRQ0属于主PIC) mov al, 0x20 out 0x20, al ; 执行中断返回 iret
2. 修改C语言ISR代码
移除timer_isr中的EOI发送和iret指令,仅保留业务逻辑:
void timer_isr() { println("You're in the timer isr."); }
3. 更新IDT初始化代码
将IRQ0的IDT描述符指向汇编Stubirq0_stub,而非直接指向C函数:
void init_idt() { idtr.base = (uintptr_t)&idt[0]; idtr.limit = (u16)sizeof(idtEntry) * IDT_MAX_DESCRIPTORS - 1; for (u8 vector = 0; vector < 32; vector++) { idt_set_descriptor(vector, isr_stub_table[vector], 0x8E); vectors[vector] = true; } // 替换为汇编Stub的地址 idt_set_descriptor(0x20, irq0_stub, 0x8E); vectors[0x20] = true; asm volatile ("lidt %0" : : "m"(idtr)); asm volatile ("sti"); }
4. 编译链接时包含汇编Stub
确保构建脚本(如Makefile)将汇编文件编译并链接到内核中,示例片段:
isr_stubs.o: isr_stubs.s nasm -f elf32 isr_stubs.s -o isr_stubs.o
额外说明
- 确认GDT中内核代码段选择子为0x08、数据段为0x10(osdev教程常见配置),若你的GDT配置不同,需调整汇编Stub中的段选择子值。
- 所有硬件中断(IRQ0-15)都需要类似的汇编Stub处理栈帧转换,不能直接用C函数注册到IDT。
内容的提问来源于stack exchange,提问作者Jackson Kidwell
相关产品推荐
相关产品推荐

