为何我的32位内核进入保护模式时会随机闪烁?
进入32位保护模式时屏幕闪烁的问题排查与修复
核心问题分析
1. GDT指针结构错误
LGDT指令要求的是6字节标准指针结构:
- 前2字节:GDT总字节数减1(限长,小端序)
- 后4字节:GDT的线性基地址
你的GDT_table仅定义了2字节,完全不符合指令要求,导致GDT加载失败。CPU进入保护模式后无法正确识别代码/数据段,触发异常,表现为屏幕持续闪烁。
2. 指令模式切换时机错误
你在设置CR0的PE位(保护模式使能位)前就使用了[bits 32],此时CPU仍处于16位实模式,执行32位指令会直接引发错误。必须在设置CR0并完成远跳切换代码段后,再进入32位指令模式。
3. 可选:A20等待函数注释混淆
你的a20wait60和a20wait64注释逻辑写反了,虽然代码本身功能正常,但会增加后续维护的理解成本。
修正后的代码
内核代码(修复版)
[bits 16] org 0x7E00 ; 内核加载地址:0x7C00 + 512字节 = 0x7E00 kernel_start: cli ; 初始化数据段 mov ax, 0x07E0 mov ds, ax mov es, ax mov gs, ax mov fs, ax ; 初始化栈 mov ax, 0x0000 mov ss, ax mov sp, 0x8E00 ; 准备进入32位保护模式 ; 禁用NMI in al, 0x70 or al, 10000000b ; 设置第7位 out 0x70, al ; 启用A20地址线 call enableA20line ; 加载GDT lgdt [GDT_table] ; 设置CR0的PE位(仍在16位模式下执行) mov eax, cr0 or eax, 0x1 ; 置位PE位 mov cr0, eax ; 远跳切换到32位代码段,刷新指令流水线 jmp 0x08:KernelMainMode ; A20地址线启用函数 enableA20line: call a20wait_ctrl_write mov al, 0xAD ; 禁用键盘 out 0x64, al call a20wait_ctrl_write mov al, 0xD0 ; 读取键盘控制器输出端口 out 0x64, al call a20wait_ctrl_read in al, 0x60 ; 读取数据端口 push ax ; 保存到栈 call a20wait_ctrl_write mov al, 0xD1 ; 写入键盘控制器输出端口 out 0x64, al call a20wait_ctrl_write pop ax ; 恢复保存的值 or al, 0x2 ; 置位第1位启用A20 out 0x60, al call a20wait_ctrl_write mov al, 0xAE ; 重新启用键盘 out 0x64, al ret a20wait_ctrl_read: ; 等待输出缓冲区有数据(bit0=1) in al, 0x64 test al, 0x1 jz a20wait_ctrl_read ret a20wait_ctrl_write: ; 等待输入缓冲区为空(bit1=0) in al, 0x64 test al, 0x2 jnz a20wait_ctrl_write ret ; 32位保护模式GDT表 GDT_start: ; 空描述符(必须存在) dq 0x0000000000000000 ; 代码段描述符(选择子0x08) dw 0xFFFF ; 段限长0-15位 dw 0x0000 ; 段基地址0-15位 db 0x00 ; 段基地址16-23位 db 10011011b ; 访问权限位 db 11001111b ; 标志位 + 段限长16-19位 db 0x00 ; 段基地址24-31位 ; 数据段描述符(选择子0x10) dw 0xFFFF ; 段限长0-15位 dw 0x0000 ; 段基地址0-15位 db 0x00 ; 段基地址16-23位 db 10010011b ; 访问权限位 db 11001111b ; 标志位 + 段限长16-19位 db 0x00 ; 段基地址24-31位 GDT_end: ; 标准GDT指针结构 GDT_table: dw GDT_end - GDT_start - 1 ; 2字节限长(小端序) dd GDT_start ; 4字节GDT基地址 [bits 32] KernelMainMode: cli ; 初始化数据段 mov ax, 0x10 ; 数据段选择子 mov ds, ax mov es, ax mov gs, ax mov fs, ax ; 初始化栈 mov ss, ax mov esp, 0x8E00 ; 向VGA显存写入字符'B'(黑底白字) mov edi, 0xB8000 mov word [edi], 0x0F42 jmp $ ; 无限循环
引导扇区代码(逻辑正确,无需修改)
[bits 16] org 0x7C00 ; BIOS加载引导扇区的固定地址 boot_start: cli ; 初始化数据段 mov ax, 0x07C0 mov ds, ax mov es, ax ; 初始化栈 mov ax, 0x0000 mov ss, ax mov sp, 0x8000 sti ; 读取内核到内存 mov bx, 0x200 ; ES:BX = 0x07C0:0x200 = 0x7E00,对应内核的org地址 mov ah, 0x02 mov al, 0x4 ; 读取4个扇区 mov ch, 0x0 ; 磁道0 mov cl, 0x2 ; 从第2个扇区开始读取 mov dh, 0x0 ; 磁头0 ; BIOS已自动设置dl为启动盘编号 int 0x13 jc disk_error ; 读取失败则跳转至错误处理 jmp 0x07C0:0x200 ; 远跳至内核入口 disk_error: mov ah, 0x0E mov al, '!' int 0x10 hlt times 510 - ($ - $$) db 0 db 0x55, 0xAA ; 引导扇区签名,BIOS识别标记
修复说明
- 修正GDT指针:将
GDT_table改为LGDT要求的6字节标准结构,确保GDT能被正确加载。 - 调整指令模式时机:移除设置CR0前的
[bits 32],仅在远跳进入32位代码段后启用32位指令模式,保证CPU在对应模式下执行指令。 - 优化A20函数注释:重命名等待函数并修正注释逻辑,避免后续维护混淆。
完成修改后,重新编译内核和引导扇区,写入软盘镜像,在QEMU中运行即可正常进入32位保护模式并显示字符'B',屏幕闪烁问题将解决。
内容的提问来源于stack exchange,提问作者user23636435
相关产品推荐
相关产品推荐

