x86系统启动时出现异常ISR,后续无法响应中断求助
解决x86操作系统IDT/ISR启动错乱及中断无响应问题
核心问题分析
- ISR汇编存根栈帧不匹配:
isr_handler未正确构建C函数期望的栈帧,缺少通用寄存器保存、错误码处理逻辑,导致调用isr_handler_next时参数错位,出现错乱的ISR编号。 - PIC初始化重复且配置冲突:
idt_init中先后两次执行PIC初始化,覆盖了正确配置;最后执行的outb(0x21, 0xFC)直接屏蔽了键盘对应的IRQ1,导致键盘中断无法触发。 - IRQ处理栈帧不完整:
irq_handler未手动压入ds段寄存器,也没有补充PIC中断缺失的中断号压入操作,与registers_t结构体定义不匹配。
修复步骤
1. 修正ISR/IRQ汇编存根
更新汇编代码,统一栈帧结构,区分带错误码和不带错误码的ISR:
.extern irq_handler_next .extern isr_handler_next .globl isr_handler .globl isr_handler_no_err .globl irq_handler ; 处理带错误码的ISR isr_handler: pusha ; 保存通用寄存器 cld ; 清除方向标志 ; 从栈中取出错误码和中断号,传递给C函数 mov %esp, %eax push (%eax + 32) ; 压入错误码 push (%eax + 28) ; 压入中断号 call isr_handler_next add $8, %esp ; 清理栈上的参数 popa ; 恢复寄存器 add $4, %esp ; 跳过错误码 iret ; 中断返回 ; 处理不带错误码的ISR(需提前将中断号存入eax) isr_handler_no_err: pusha cld push $0 ; 填充伪错误码 push %eax ; 压入中断号 call isr_handler_next add $8, %esp popa iret ; IRQ处理函数 irq_handler: pusha cld mov %ds, %eax push %eax ; 保存数据段寄存器 push $0 ; 填充伪错误码 push %eax ; 压入中断号(需提前存入eax) call irq_handler_next add $8, %esp pop %eax mov %eax, %ds ; 恢复数据段寄存器 popa iret
需为每个ISR编写单独的存根,例如:
isr0: mov $0, %eax jmp isr_handler_no_err
2. 修复PIC初始化逻辑
删除重复的PIC初始化代码,确保只执行一次完整配置,并正确开启键盘中断:
void idt_init(void) { __asm__ __volatile__("cli"); nanos_printf("Loading IDT\n"); idt_ptr.limit = sizeof(idt) - 1; idt_ptr.base = (uint32_t)&idt; // 为每个ISR/IRQ设置对应存根(示例,需补全所有条目) idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E); idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E); // ... 补全其他ISR ... idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E); // 键盘IRQ对应IDT33 // ... 补全其他IRQ ... // 仅执行一次PIC初始化 outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); outb(PIC1_DATA, 0x20); // IRQ0-7映射到IDT0x20-0x27 io_wait(); outb(PIC2_DATA, 0x28); // IRQ8-15映射到IDT0x28-0x2F io_wait(); outb(PIC1_DATA, 4); io_wait(); outb(PIC2_DATA, 2); io_wait(); outb(PIC1_DATA, ICW4_8086); io_wait(); outb(PIC2_DATA, ICW4_8086); io_wait(); // 仅屏蔽PIT(IRQ0),开启键盘IRQ1 outb(0x21, inb(0x21) & ~(1 << 1) & (0xFF ^ (1 << 0))); // 禁用PIT的代码保留 outb(0x43, 0x30); outb(0x40, 0x00); outb(0x40, 0x00); idt_load(); __asm__ __volatile__("sti"); nanos_printf("IDT loaded\n"); }
3. 修正中断处理函数参数匹配
更新处理函数,确保与栈帧结构一致:
// ISR处理函数:接收中断号和错误码 void isr_handler_next(int int_no, int err_code) { nanos_printf("\nISR Number: %d, Error Code: %d\n", int_no, err_code); } // IRQ处理函数:匹配registers_t结构体 void irq_handler_next(registers_t *regs) { uint8_t irq = regs->int_no - PIC1_IRQ_BASE; nanos_printf("Received IRQ %d!\n", irq); // 正确发送EOI信号 if (regs->int_no >= PIC2_IRQ_BASE) { outb(PIC2_COMMAND, 0x20); } outb(PIC1_COMMAND, 0x20); }
确保
registers_t结构体的字段顺序与栈帧完全对应,包括手动压入的ds、pusha的寄存器、错误码、中断号以及CPU自动压入的内容。
4. 验证IDT条目设置
确保所有ISR/IRQ对应的IDT条目都指向正确的汇编存根,不能统一使用isr_handler(需区分错误码处理逻辑)。
关键注意事项
- 中断栈帧的一致性是核心:CPU自动压入的内容、手动压入的寄存器/错误码/中断号必须与C函数参数或结构体严格对应。
- PIC初始化只能执行一次,重复操作会导致配置混乱。
- 必须确保未屏蔽需要响应的中断(如键盘IRQ1)。
iret指令要求栈内容严格符合CPU压入顺序,任何栈操作错误都会导致系统崩溃或错乱。
内容的提问来源于stack exchange,提问作者user21223662
相关产品推荐
相关产品推荐

