深入理解Phil-opp/blog_os:进入长模式的技术解析

深入理解Phil-opp/blog_os:进入长模式的技术解析

blog_os Writing an OS in Rust blog_os 项目地址: https://gitcode.com/gh_mirrors/bl/blog_os

前言

在上一篇文章中,我们创建了一个最小的多引导内核,它仅打印"OK"后就挂起。本文的目标是扩展这个内核,使其能够调用64位Rust代码。但当前CPU处于保护模式下,仅支持32位指令和最多4GB内存。因此,我们需要先设置分页并切换到64位长模式。

准备工作:硬件特性检测

在进入长模式前,我们需要确保CPU支持所有必需的特性。如果检测失败,内核应显示错误信息并中止执行。

错误处理机制

我们首先在boot.asm中创建一个错误处理程序:

error:
    mov dword [0xb8000], 0x4f524f45  ; 'ERR:'
    mov dword [0xb8004], 0x4f3a4f52
    mov dword [0xb8008], 0x4f204f20
    mov byte  [0xb800a], al          ; 错误代码
    hlt

这段代码利用了VGA文本缓冲区(地址从0xb8000开始),每个字符由8位颜色代码和8位ASCII字符组成。我们使用红色背景白色文本(颜色码0x4f)显示错误信息。

栈的初始化

在调用函数前,我们需要设置有效的栈指针:

section .bss
stack_bottom:
    resb 64
stack_top:

section .text
start:
    mov esp, stack_top

这里我们使用.bss段保留64字节作为栈空间,栈从高地址向低地址增长,因此初始化时esp指向stack_top。

关键特性检测

  1. 多引导检查

    check_multiboot:
        cmp eax, 0x36d76289  ; 多引导魔数
        jne .no_multiboot
        ret
    
  2. CPUID检测

    check_cpuid:
        pushfd
        pop eax
        mov ecx, eax
        xor eax, 1 << 21     ; 尝试翻转ID位
        push eax
        popfd
        pushfd
        pop eax
        cmp eax, ecx         ; 检查是否翻转成功
        je .no_cpuid
        ret
    
  3. 长模式检测

    check_long_mode:
        mov eax, 0x80000000
        cpuid
        cmp eax, 0x80000001  ; 检查是否支持扩展功能
        jb .no_long_mode
        mov eax, 0x80000001
        cpuid
        test edx, 1 << 29    ; 检查LM位
        jz .no_long_mode
        ret
    

分页机制详解

长模式下x86使用4KB页大小和四级页表结构:

  1. 页表层级

    • P4 (PML4) → P3 (PDP) → P2 (PD) → P1 (PT)
  2. 地址转换过程

    • 从CR3寄存器获取P4表地址
    • 使用虚拟地址的39-47位索引P4
    • 使用30-38位索引P3
    • 使用21-29位索引P2
    • 使用12-20位索引P1
    • 最后12位作为页内偏移

身份映射实现

我们使用2MiB大页进行身份映射(物理地址=虚拟地址):

section .bss
align 4096
p4_table: resb 4096
p3_table: resb 4096
p2_table: resb 4096

set_up_page_tables:
    ; 链接P4→P3→P2
    mov eax, p3_table
    or eax, 0b11       ; Present + Writable
    mov [p4_table], eax
    
    mov eax, p2_table
    or eax, 0b11
    mov [p3_table], eax
    
    ; 映射512个2MiB页
    mov ecx, 0
.map_p2_table:
    mov eax, 0x200000  ; 2MiB
    mul ecx            ; ecx*2MiB
    or eax, 0b10000011 ; Present + Writable + Huge
    mov [p2_table + ecx*8], eax
    inc ecx
    cmp ecx, 512
    jne .map_p2_table
    ret

启用长模式

完成以下步骤来启用长模式:

  1. 将P4表地址加载到CR3寄存器
  2. 启用物理地址扩展(PAE)
  3. 设置EFER寄存器中的长模式位
  4. 启用分页
enable_paging:
    ; 1. 加载P4到CR3
    mov eax, p4_table
    mov cr3, eax
    
    ; 2. 启用PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax
    
    ; 3. 设置长模式位
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr
    
    ; 4. 启用分页
    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax
    
    ret

技术要点解析

  1. 大页优势:使用2MiB大页可以减少页表层级,提高TLB命中率,同时兼容较旧的CPU。

  2. 身份映射必要性:在切换模式时,CPU会继续执行下一条指令,此时必须确保该指令的物理地址和虚拟地址相同。

  3. 特性检测顺序:先检查多引导→CPUID→长模式,确保环境符合要求。

  4. 页表属性

    • Present(0): 页是否在内存中
    • Writable(1): 是否可写
    • Huge(7): 是否使用大页

总结

通过本文,我们完成了从保护模式到长模式的转换过程。关键步骤包括:硬件特性验证、分页表设置和长模式启用。这些基础工作为后续运行64位Rust代码铺平了道路。下一阶段,我们将探讨如何初始化GDT和IDT,为进入Rust世界做好准备。

blog_os Writing an OS in Rust blog_os 项目地址: https://gitcode.com/gh_mirrors/bl/blog_os

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜闽弋Flora

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值