无法用PIC屏蔽IRQ:x86_64保护模式内核中断异常求助
问题分析:x86_64保护模式下键盘中断配置引发持续INT8(双故障)
问题描述
我正在x86_64保护模式内核中配置键盘中断,已完成IDT加载、PIC重映射,键盘IRQ能正常工作,但持续触发INT8(疑似双故障)。尝试屏蔽定时器中断(IRQ0)及所有中断均无法解决问题。
相关代码
pic.h
#pragma once #define PIC1_COMMAND 0x20 #define PIC1_DATA 0x21 #define PIC2_COMMAND 0xA0 #define PIC2_DATA 0xA1 #define NEW_IRQ_0 0x20 #define NEW_IRQ_8 0x28 #define ICW1_INIT 0x10 #define ICW1_ICW4 0x01 #define ICW4_8086 0x01 void remap_pic() { uint8_t pic1_mask, pic2_mask; pic1_mask = inb(PIC1_DATA); pic2_mask = inb(PIC2_DATA); outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); outb(PIC1_DATA, NEW_IRQ_0); io_wait(); outb(PIC2_DATA, NEW_IRQ_8); io_wait(); outb(PIC1_DATA, 0x4); io_wait(); outb(PIC2_DATA, 0x2); io_wait(); outb(PIC1_DATA, ICW4_8086); io_wait(); outb(PIC2_DATA, ICW4_8086); io_wait(); outb(PIC1_DATA, pic1_mask); outb(PIC2_DATA, pic2_mask); }
idt.c
void idt_init() { for(uint64_t t = 0; t < 256; t++) { _idt[t].zero = 0; _idt[t].offset_low = (uint16_t)(((uint64_t)&isr1 & 0x000000000000ffff)); _idt[t].offset_mid = (uint16_t)(((uint64_t)&isr1 & 0x00000000ffff0000) >> 16); _idt[t].offset_high = (uint32_t)(((uint64_t)&isr1 & 0xffffffff00000000) >> 32); _idt[t].ist = 0; _idt[t].selector = 0x08; _idt[t].types_attr = 0x8e; } remap_pic(); outb(0x21, 0xfd); outb(0xa1, 0xff); load_idt(); } extern void isr1_handler() { uint8_t scancode = inb(0x60); print_char(SCANCODE_LOOKUP_TABLE[scancode]); outb(0x20, 0x20); outb(0xa0, 0x20); }
idt_asm.asm
extern _idt idtDescriptor: dw 4095 dq _idt %macro PUSHALL 0 push rax push rcx push rdx push r8 push r9 push r10 push r11 %endmacro %macro POPALL 0 pop r11 pop r10 pop r9 pop r8 pop rdx pop rcx pop rax %endmacro extern isr1_handler isr1: PUSHALL call isr1_handler POPALL iretq global isr1 load_idt: lidt[idtDescriptor] sti ret global load_idt
问题根源
- IDT全局错误映射:
idt_init中循环将所有256个IDT条目都指向isr1,包括CPU异常(如INT8双故障、页错误等)的条目。isr1仅适配外部中断处理,无法处理CPU异常的栈帧要求——部分异常会自动向栈中压入错误码,isr1的PUSHALL/POPALL和iretq流程会导致栈不平衡,触发双故障。 - 不必要的EOI发送:键盘中断属于主PIC(IRQ1),仅需向主PIC发送EOI指令,
isr1_handler中同时向从PIC发送EOI会干扰PIC正常状态,可能引发额外中断异常。
解决步骤
拆分IDT条目配置:
- 为CPU异常(0~0x1F)编写专用处理例程,区分带错误码和不带错误码的异常:带错误码的异常需要在
PUSHALL前弹出错误码(或在处理函数中处理),保证栈平衡。 - 仅将键盘中断对应的IDT条目(重映射后IRQ1对应
0x21)指向isr1,其他条目配置为默认异常处理例程。
示例修正
idt_init:void idt_init() { // 初始化所有条目为默认异常处理例程(需提前定义isr_default) for(uint64_t t = 0; t < 256; t++) { _idt[t].zero = 0; _idt[t].offset_low = (uint16_t)(((uint64_t)&isr_default & 0x000000000000ffff)); _idt[t].offset_mid = (uint16_t)(((uint64_t)&isr_default & 0x00000000ffff0000) >> 16); _idt[t].offset_high = (uint32_t)(((uint64_t)&isr_default & 0xffffffff00000000) >> 32); _idt[t].ist = 0; _idt[t].selector = 0x08; _idt[t].types_attr = 0x8e; } // 单独配置键盘中断条目 uint64_t irq1_idx = NEW_IRQ_0 + 1; // 0x21 _idt[irq1_idx].offset_low = (uint16_t)(((uint64_t)&isr1 & 0x000000000000ffff)); _idt[irq1_idx].offset_mid = (uint16_t)(((uint64_t)&isr1 & 0x00000000ffff0000) >> 16); _idt[irq1_idx].offset_high = (uint32_t)(((uint64_t)&isr1 & 0xffffffff00000000) >> 32); remap_pic(); outb(0x21, 0xfd); // 仅开启IRQ1,屏蔽其他主PIC中断 outb(0xa1, 0xff); // 屏蔽所有从PIC中断 load_idt(); }- 为CPU异常(0~0x1F)编写专用处理例程,区分带错误码和不带错误码的异常:带错误码的异常需要在
修正EOI发送逻辑:
修改isr1_handler,仅向主PIC发送EOI:extern void isr1_handler() { uint8_t scancode = inb(0x60); print_char(SCANCODE_LOOKUP_TABLE[scancode]); outb(0x20, 0x20); // 仅主PIC发送EOI }实现默认异常处理例程:
编写通用异常处理函数,用于处理未自定义的异常,避免因未处理的异常触发双故障。示例汇编实现:extern isr_default_handler isr_default: PUSHALL call isr_default_handler POPALL iretq global isr_default
内容的提问来源于stack exchange,提问作者Zpher
相关产品推荐
相关产品推荐

