You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:27:24