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

64位Ubuntu下汇编内核按键输入失效问题求助

你的64位内核键盘输入失效的原因与修复方案

咱们直接说核心问题:你现在的代码有两个致命问题导致按键没反应——模式切换不完整和错误地在64位模式下调用BIOS中断。


问题1:从实模式直接跳64位代码,CPU根本没进入长模式

你的引导加载程序是16位实模式代码,但内核标了[BITS 64],直接跳转过去的话,CPU还在16位模式,会把64位指令当成乱码执行,根本跑不起来。要运行64位代码,必须先完成从实模式→保护模式→长模式的完整切换流程。

修复后的引导加载程序(带长模式切换)

我给你写了一个基础版的引导加载程序,包含了进入长模式必需的步骤:

[BITS 16]
org 0x7C00

bootdrive db 0

start:
    mov [bootdrive], dl
    ; 先关中断,避免切换过程中被打断
    cli
    ; 启用A20地址线,让CPU能访问1MB以上的内存
    in al, 0x92
    or al, 0x02
    out 0x92, al
    ; 加载64位GDT(全局描述符表,长模式必需)
    lgdt [gdt_descriptor]
    ; 切换到保护模式
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    ; 远跳转到32位代码段,强制刷新CPU指令流水线
    jmp 0x08:pm_start

[BITS 32]
pm_start:
    ; 设置所有段寄存器为32位数据段
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    ; 设置栈指针到安全的内存位置
    mov esp, 0x90000
    ; 启用PAE(物理地址扩展,长模式必需)
    mov eax, cr4
    or eax, 0x20
    mov cr4, eax
    ; 加载页表(这里用1GB大页简单映射所有内存)
    mov eax, 0x1000
    mov cr3, eax
    ; 启用长模式(通过EFER寄存器)
    mov ecx, 0xC0000080
    rdmsr
    or eax, 0x100
    wrmsr
    ; 启用分页,正式进入长模式
    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax
    ; 远跳转到64位代码段
    jmp 0x18:lm_start

; 定义64位GDT
gdt_start:
    ; 空描述符(必须有)
    dd 0x0
    dd 0x0
    ; 32位代码段
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 0x9A
    db 0xCF
    db 0x0
    ; 32位数据段
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 0x92
    db 0xCF
    db 0x0
    ; 64位代码段
    dw 0x0
    dw 0x0
    db 0x0
    db 0x9A
    db 0xAF
    db 0x0
    ; 64位数据段
    dw 0x0
    dw 0x0
    db 0x0
    db 0x92
    db 0xAF
    db 0x0
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; 加载内核到64位模式下的线性地址0x10000
    mov ah, 0x2
    mov al, 0x10
    mov dl, [bootdrive]
    mov ch, 0
    mov dh, 0
    mov cl, 2
    mov bx, 0x10000
    int 0x13

[BITS 64]
lm_start:
    ; 跳转到内核入口
    jmp 0x10000
    ; 填充引导扇区到512字节
    times 510-($-$$) db 0
    dw 0xAA55

问题2:64位长模式下不能用BIOS中断

int 16h、int 10h这些都是BIOS中断,只能在实模式或者V86模拟模式下用。长模式下CPU完全不支持实模式的中断调用方式,所以你调用了也没反应。

替换方案:直接访问硬件端口处理输入输出

进入长模式后,要操作键盘和屏幕,得直接跟硬件端口打交道。下面是修改后的内核代码,用PS/2键盘端口读取按键,用VGA帧缓冲输出字符:

[BITS 64]
start:
    call _stdin_loop
    jmp $

; 无限循环处理键盘输入
_stdin_loop:
    ; PS/2键盘状态端口是0x64,先等数据就绪
    mov dx, 0x64
wait_for_key:
    in al, dx
    test al, 0x01 ; 检查第0位是否为1(数据就绪)
    jz wait_for_key
    ; 数据就绪,从数据端口0x60读取扫描码
    mov dx, 0x60
    in al, dx
    ; 把扫描码转成ASCII(这里只处理小写字母和回车,你可以扩展更多按键)
    cmp al, 0x1E ; 'a'的扫描码
    jl skip_output
    cmp al, 0x28 ; 'z'的扫描码
    jg skip_output
    add al, 0x20 ; 扫描码转小写ASCII
    call _print_char
    ; 处理回车
    cmp al, 0x1C ; 回车的扫描码
    jne skip_output
    mov al, 0x0A ; 换行符
    call _print_char
    mov al, 0x0D ; 回车符
    call _print_char
skip_output:
    jmp _stdin_loop

; 输出字符到VGA文本模式(0xB8000是VGA帧缓冲的物理地址)
_print_char:
    mov rbx, 0xB8000
    ; 这里简单把字符输出到第一行,你可以自己加光标定位逻辑
    mov [rbx], al
    mov byte [rbx+1], 0x07 ; 黑底白字的属性
    add rbx, 2 ; 移动到下一个字符位置
    ret

编译和测试注意事项

  • 用nasm分别编译引导和内核:
    • 引导加载程序:nasm boot.asm -f bin -o boot.bin
    • 内核:nasm kernel.asm -f bin -o kernel.bin
  • 制作镜像:cat boot.bin kernel.bin > os.img
  • 用QEMU测试:qemu-system-x86_64 os.img

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:39:17