初始化IDT后仅触发一次IRQ的x86内核问题排查
x86 OS开发:IDT初始化后仅触发一次IRQ,键盘按键无响应
我正在用C语言开发基于GRUB-Multiboot的x86操作系统,GDT已正常工作,但初始化IDT时遇到问题:系统启动时仅收到一次IRQ,后续按下键盘按键无法触发对应的IRQ。启动时输出仅显示一次Received an IRQ!,之后无任何响应。
相关代码
idt.c
#include <stdint.h> #include "idt.h" #include "lib/stdio.h" #include "include/io.h" // Define the Interrupt Descriptor Table (IDT) struct idt_entry_t idt[IDT_ENTRIES]; struct idt_entry_t idt_entries[IDT_ENTRIES]; // Load the IDT pointer struct idt_ptr_t idt_ptr = { .limit = sizeof(idt) - 1, .base = (uint32_t)&idt }; typedef struct registers { uint32_t ds; // Data segment selector uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha uint32_t int_no, err_code; // Interrupt number and error code (if applicable) uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically } registers_t; // Define an IRQ handler function void irq_handler(registers_t *regs) { nanos_printf("Received an IRQ!\n"); // Send an EOI (end-of-interrupt) signal to the PICs outb(0xA0, 0x20); // Send reset signal to slave outb(0x20, 0x20); // Send reset signal to master return; } void isr_handler() { nanos_printf("\n\nSTOP\n\nException occurred... halting..."); // Halt the CPU for (;;); } void idt_load() { asm volatile("lidt %0" : : "m"(idt_ptr)); } void idt_init(void) { // Disable interrupts __asm__ __volatile__("cli"); // Setup the PIC(s) outb(0x20, 0x11); outb(0xA0, 0x11); outb(0x21, 0x20); outb(0xA1, 0x28); outb(0x21, 0x04); outb(0x21, 0x01); outb(0x21, 0xFB); outb(0xA1, 0xFF); // Print a message to indicate that IDT is being loaded nanos_printf("Loading IDT\n"); // Initialize the IDT pointer idt_ptr.limit = sizeof(idt) - 1; idt_ptr.base = (uint32_t)&idt; // Set each IDT entry to the default handler for (int i = 0; i < IDT_ENTRIES; i++) { idt_set_gate(i, (uint32_t)isr_handler, 0x08, 0x8E); } // Set the IRQ entries in the IDT idt_set_gate(32, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(33, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(34, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(35, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(36, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(37, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(38, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(39, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(40, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(41, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(42, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(43, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(44, (uint32_t)irq_handler, 0x08, 0x8E); idt_set_gate(45, (uint32_t)irq_handler, 0x08, 0x8E); // Send initialization control word 1 and 2 to both PICs outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // Initialization Control Word 1 io_wait(); outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); // Send initialization control word 3 to both PICs outb(PIC1_DATA, 0x20); // Initialization Control Word 3: IRQ 0-7 map to IDT entries 0x20-0x27 io_wait(); outb(PIC2_DATA, 0x28); // Initialization Control Word 3: IRQ 8-15 map to IDT entries 0x28-0x2F io_wait(); // Send initialization control word 4 to both PICs outb(PIC1_DATA, ICW4_8086); io_wait(); outb(PIC2_DATA, ICW4_8086); io_wait(); // Unmask IRQs outb(PIC1_DATA, 0x0); outb(PIC2_DATA, 0x0); // Load IDT idt_load(); // Enable interrupts __asm__ __volatile__("sti"); nanos_printf("IDT loaded\n"); } void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { // Set the base address idt[num].base_lo = base & 0xFFFF; idt[num].base_hi = (base >> 16) & 0xFFFF; // Set the selector idt[num].sel = sel; // Set the always0 field idt[num].always0 = 0; // Set the flags idt[num].flags = flags; }
idt.h
#ifndef IDT_H #define IDT_H #include <stdint.h> // Number of entries in the IDT #define IDT_ENTRIES 256 // Struct for IDT entry struct idt_entry_t { uint16_t base_lo; // Lower 16 bits of handler function address uint16_t sel; // Kernel segment selector uint8_t always0; // Must always be zero uint8_t flags; // Flags for entry (present, privilege level, type) uint16_t base_hi; // Upper 16 bits of handler function address } __attribute__((packed)); // Struct for IDT pointer struct idt_ptr_t { uint16_t limit; // Size of IDT uint32_t base; // Base address of IDT } __attribute__((packed)); // Define macros for setting flags in IDT entries #define IDT_PRESENT_BIT 0x80 #define IDT_RING0 0x00 #define IDT_RING3 0x60 #define IDT_INT_GATE 0x0E #define IDT_TRAP_GATE 0x0F #define IDT_SIZE 0x08 // Define an IRQ handler function void irq_handler(); // Define an ISR handler function void isr_handler(); // Define a function to load the IDT void idt_load(); // Define a function to initialize the IDT void idt_init(void); // Define a function to set a gate in the IDT void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags); #endif // IDT_H
kernel_main.c
void kernel_main(void) { clear_screen(); nanos_printf("Hello NanOS!\n"); gdt_init(); nanos_printf("Initialized Global Descriptor Table.\n"); idt_init(); nanos_printf("Initialized Interrupt Descriptor Table.\n"); while (true) {} }
问题排查与修复方案
1. 删除重复的PIC初始化代码
idt_init中存在两段PIC初始化逻辑,第一段非标准初始化会和后面的ICW1-ICW4流程冲突,直接删除以下代码块:
// Setup the PIC(s) outb(0x20, 0x11); outb(0xA0, 0x11); outb(0x21, 0x20); outb(0xA1, 0x28); outb(0x21, 0x04); outb(0x21, 0x01); outb(0x21, 0xFB); outb(0xA1, 0xFF);
2. 添加IRQ汇编包装函数
C语言函数无法直接处理x86中断的栈帧,必须编写汇编stub来保存/恢复寄存器并执行iret返回。示例汇编代码(可放入单独的.S文件):
[GLOBAL irq_common_stub] [EXTERN irq_handler] irq_common_stub: pusha ; 保存所有通用寄存器 push ds push es push fs push gs mov ax, 0x10 ; 加载内核数据段选择子(对应GDT中数据段) mov ds, ax mov es, ax mov fs, ax mov gs, ax mov eax, esp ; 将寄存器结构体指针传入C handler push eax call irq_handler pop eax pop gs pop fs pop es pop ds popa ; 恢复通用寄存器 add esp, 8 ; 跳过中断号和错误码 iret ; 从中断返回
然后修改IDT中IRQ入口的绑定,使用汇编stub的地址:
// 替换原来的irq_handler绑定为irq_common_stub idt_set_gate(32, (uint32_t)irq_common_stub, 0x08, 0x8E); idt_set_gate(33, (uint32_t)irq_common_stub, 0x08, 0x8E); // ... 其余IRQ入口同理
3. 修正EOI发送逻辑
根据中断类型判断是否需要发送EOI到从PIC,避免不必要的信号导致PIC状态异常:
void irq_handler(registers_t *regs) { nanos_printf("Received an IRQ: %d!\n", regs->int_no - 32); // 发送EOI信号 if (regs->int_no >= 40) { outb(0xA0, 0x20); // 仅当IRQ来自从PIC时发送 } outb(0x20, 0x20); // 主PIC始终发送EOI }
4. 处理键盘中断的扫描码
键盘触发IRQ1(IDT入口33)后,必须读取0x60端口的扫描码来清除中断请求,否则键盘不会再次触发中断:
void irq_handler(registers_t *regs) { nanos_printf("Received an IRQ: %d!\n", regs->int_no - 32); // 处理键盘IRQ if (regs->int_no == 33) { uint8_t scancode = inb(0x60); // 读取扫描码,清除键盘中断标记 } // 发送EOI信号 if (regs->int_no >= 40) { outb(0xA0, 0x20); } outb(0x20, 0x20); }
5. 确保io_wait()函数正确实现
PIC初始化时需要等待I/O完成,io_wait()必须是有效的空操作,比如:
void io_wait(void) { asm volatile("outb %%al, $0x80" : : "a"(0)); }
内容的提问来源于stack exchange,提问作者user21223662
相关产品推荐
相关产品推荐

