深入理解Phil-opp/blog_os:进入长模式的技术解析
blog_os Writing an OS in Rust 项目地址: 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。
关键特性检测
-
多引导检查:
check_multiboot: cmp eax, 0x36d76289 ; 多引导魔数 jne .no_multiboot ret
-
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
-
长模式检测:
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页大小和四级页表结构:
-
页表层级:
- P4 (PML4) → P3 (PDP) → P2 (PD) → P1 (PT)
-
地址转换过程:
- 从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
启用长模式
完成以下步骤来启用长模式:
- 将P4表地址加载到CR3寄存器
- 启用物理地址扩展(PAE)
- 设置EFER寄存器中的长模式位
- 启用分页
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
技术要点解析
-
大页优势:使用2MiB大页可以减少页表层级,提高TLB命中率,同时兼容较旧的CPU。
-
身份映射必要性:在切换模式时,CPU会继续执行下一条指令,此时必须确保该指令的物理地址和虚拟地址相同。
-
特性检测顺序:先检查多引导→CPUID→长模式,确保环境符合要求。
-
页表属性:
- Present(0): 页是否在内存中
- Writable(1): 是否可写
- Huge(7): 是否使用大页
总结
通过本文,我们完成了从保护模式到长模式的转换过程。关键步骤包括:硬件特性验证、分页表设置和长模式启用。这些基础工作为后续运行64位Rust代码铺平了道路。下一阶段,我们将探讨如何初始化GDT和IDT,为进入Rust世界做好准备。
blog_os Writing an OS in Rust 项目地址: https://gitcode.com/gh_mirrors/bl/blog_os
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考