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
相关产品推荐
相关产品推荐

