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

为何我的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识别标记

修复说明

  1. 修正GDT指针:将GDT_table改为LGDT要求的6字节标准结构,确保GDT能被正确加载。
  2. 调整指令模式时机:移除设置CR0前的[bits 32],仅在远跳进入32位代码段后启用32位指令模式,保证CPU在对应模式下执行指令。
  3. 优化A20函数注释:重命名等待函数并修正注释逻辑,避免后续维护混淆。

完成修改后,重新编译内核和引导扇区,写入软盘镜像,在QEMU中运行即可正常进入32位保护模式并显示字符'B',屏幕闪烁问题将解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:14:50