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

如何在x86_64 C内核中检测按键并传入键码调用指定函数?

自制x86_64内核检测按键操作实现步骤

1. 理解PS/2键盘的工作逻辑

x86平台的PS/2键盘通过端口0x60返回按键扫描码,端口0x64用于状态查询与控制。按键按下时,键盘会把扫描码发送到0x60端口,同时触发IRQ1中断(主PIC的中断1号)。

2. 配置PIC启用键盘中断

IRQ1默认会被PIC屏蔽,需要初始化PIC并开启IRQ1的中断权限:

// 初始化主PIC(8259A),核心是启用IRQ1
void pic_init() {
    // 省略基础PIC初始化的ICW1-ICW4命令发送步骤(根据你的内核现有代码调整)

    // 操作主PIC掩码寄存器(端口0x21),清除IRQ1对应的位以允许中断
    uint8_t mask = inb(0x21) & ~(1 << 1);
    outb(0x21, mask);
}

注:inb/outb是你内核中需要实现的端口读写函数,用于读取/写入8位端口数据。

3. 注册IRQ1中断处理函数

需要把键盘中断处理函数挂载到IDT(中断描述符表)的IRQ1对应条目(IRQ1映射到中断向量号0x21,因为PIC默认偏移为0x20):

// 汇编包装器:处理中断上下文的寄存器保存与恢复,以及PIC的EOI信号
__attribute__((naked)) void keyboard_irq_wrapper() {
    asm volatile("push rax\npush rbx\npush rcx\npush rdx\n");
    asm volatile("call keyboard_irq_handler");
    // 发送EOI信号给主PIC,告知中断处理完成
    asm volatile("mov al, 0x20\nout 0x20, al");
    asm volatile("pop rdx\npop rcx\npop rbx\npop rax\niretq");
}

// C语言实现的键盘中断处理逻辑
void keyboard_irq_handler() {
    uint8_t scancode = inb(0x60); // 读取端口0x60的扫描码

    // 区分按键按下(扫描码无0x80位)与释放(扫描码含0x80位)
    if (!(scancode & 0x80)) {
        // 将扫描码转为十六进制字符串传入回调函数
        char code_str[4];
        snprintf(code_str, sizeof(code_str), "%02X", scancode);
        on_key_press(code_str);
    }
}

// 在IDT初始化时注册中断处理函数
void idt_init() {
    // 省略IDT基础初始化步骤
    idt_set_entry(0x21, (uint64_t)keyboard_irq_wrapper, 0x08, 0x8E);
}

注:idt_set_entry是你内核中设置IDT条目的函数,参数分别为中断向量号、处理函数地址、代码段选择子、门类型(0x8E对应32位中断门,x86_64环境可调整为对应64位门类型)。

4. 可选:实现扫描码到字符的映射

如果需要输出实际按键字符而非十六进制扫描码,可定义扫描码到ASCII的映射表:

// 基础扫描码到ASCII的映射表(可参考PS/2键盘标准补全)
char scancode_to_ascii[] = {
    0, 0x1B, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
    '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
    // 省略其余扫描码映射
};

// 修改keyboard_irq_handler中的处理逻辑
if (!(scancode & 0x80)) {
    char c = scancode_to_ascii[scancode];
    if (c != 0) {
        char code_str[2] = {c, '\0'};
        on_key_press(code_str);
    }
}

5. 启用全局中断

在内核初始化的最后一步开启全局中断:

void kernel_main() {
    pic_init();
    idt_init();
    // 省略其他内核初始化步骤
    asm volatile("sti"); // 开启全局中断
}

关键细节补充

  • 必须确保内核已正确初始化GDT、IDT和PIC,这是中断处理的基础。
  • 端口读写函数的汇编实现示例:
static inline uint8_t inb(uint16_t port) {
    uint8_t ret;
    asm volatile("inb %1, %0" : "=a"(ret) : "Nd"(port));
    return ret;
}

static inline void outb(uint16_t port, uint8_t data) {
    asm volatile("outb %0, %1" : : "a"(data), "Nd"(port));
}
  • QEMU默认会模拟PS/2键盘并传递按键事件,无需额外配置Docker或QEMU参数,直接按键即可触发中断。

内容的提问来源于stack exchange,提问作者user21729863

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 08:25:19