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

初始化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 23:55:21