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

如何修复自定义操作系统中PS/2键盘输入字符乱屏问题

解决LainOS中PS/2键盘输入乱显示的问题

1. 修正扫描码到ASCII的映射逻辑

PS/2键盘发送的是扫描码而非直接ASCII字符,必须区分通码(按键按下,最高位为0)和断码(按键松开,最高位为1),仅处理按下事件:

  • 误将断码当成通码处理会映射出错误字符,比如0x9E(A键断码)不能直接映射为'A'
  • 初始化时清空键盘缓冲区,避免残留扫描码干扰:
void keyboard_init() {
    // 清空缓冲区残留数据
    while (inb(0x64) & 0x01) {
        inb(0x60);
    }
    // 启用键盘中断(主PS/2端口)
    outb(0x64, 0xAE);
    uint8_t status = inb(0x60);
    status |= 0x01;
    outb(0x64, 0x60);
    outb(0x60, status);
}

void keyboard_handler() {
    uint8_t scancode = inb(0x60);
    // 忽略断码
    if (scancode & 0x80) {
        // 发送中断结束信号
        outb(0x20, 0x20);
        return;
    }
    char c = scancode_to_ascii[scancode];
    if (c != 0) {
        terminal_putchar(c);
    }
    // 发送中断结束信号
    outb(0x20, 0x20);
}

2. 确保终端光标与显存写入同步

乱显示大多是软件跟踪的光标位置(terminal_row/terminal_column)与实际显存写入位置不一致导致:

  • 写入字符后必须正确更新行列值,处理换行和屏幕滚动,同步硬件光标:
#define VGA_WIDTH 80
#define VGA_HEIGHT 25

uint16_t* terminal_buffer = (uint16_t*)0xB8000;
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;

void terminal_putchar(char c) {
    cli(); // 关闭中断避免被打断
    if (c == '\n') {
        terminal_row++;
        terminal_column = 0;
    } else {
        size_t index = terminal_row * VGA_WIDTH + terminal_column;
        terminal_buffer[index] = (uint16_t)c | ((uint16_t)terminal_color << 8);
        terminal_column++;
    }

    // 处理屏幕滚动
    if (terminal_row >= VGA_HEIGHT) {
        // 向上滚动一行
        memcpy(terminal_buffer, terminal_buffer + VGA_WIDTH, (VGA_HEIGHT - 1) * VGA_WIDTH * sizeof(uint16_t));
        // 清空最后一行
        size_t last_row_idx = (VGA_HEIGHT - 1) * VGA_WIDTH;
        for (size_t i = 0; i < VGA_WIDTH; i++) {
            terminal_buffer[last_row_idx + i] = (uint16_t)' ' | ((uint16_t)terminal_color << 8);
        }
        terminal_row = VGA_HEIGHT - 1;
    }

    // 更新硬件光标位置
    size_t cursor_pos = terminal_row * VGA_WIDTH + terminal_column;
    outb(0x3D4, 0x0F);
    outb(0x3D5, (uint8_t)(cursor_pos & 0xFF));
    outb(0x3D4, 0x0E);
    outb(0x3D5, (uint8_t)((cursor_pos >> 8) & 0xFF));

    sti(); // 恢复中断
}

3. 修复中断处理的竞态问题

键盘中断(IRQ1)处理不当会导致重复触发或寄存器混乱:

  • 中断处理函数必须完整保存/恢复寄存器,发送EOI(中断结束)信号:
keyboard_interrupt:
    pusha               ; 保存所有通用寄存器
    call keyboard_handler
    mov al, 0x20
    out 0x20, al        ; 向主PIC发送EOI信号
    popa                ; 恢复寄存器
    iret                ; 中断返回

4. 保证显存操作的原子性

如果系统支持多任务或中断可能打断显存写入,会出现字符拆分显示:

  • 在terminal_putchar中用cli()和sti()包裹写入逻辑,确保操作不被中断打断(如上面代码示例)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:55:08