最小操作系统MiniOS(2)实模式到保护模式

使用VC++(结合内联汇编)实现从16位实模式切换到32位保护模式的完整代码示例,基于x86架构的引导扇区开发。该代码整合了GDT初始化、A20地址线开启、CR0寄存器设置等关键步骤,并附带详细注释。

 

🛠 一、开发环境配置(VC++特定设置)

 

1. 项目属性调整

   - 平台工具集:选择

"Visual Studio 2019+",配置为

"Release"模式

   - 代码生成:

"/Gr"(启用

"__fastcall"调用约定,避免栈冲突)

   - 高级选项:

"/FA"(生成汇编列表文件便于调试)

2. 链接器设置

   - 入口点:

"main"

   - 子系统:

"控制台 (/SUBSYSTEM:CONSOLE)"

   - 输出格式:

"/OUT:boot.bin"(生成裸二进制文件)

 

⚙️ 二、完整代码实现(含关键步骤注释)

 

#include <cstdint>

 

// 定义GDT描述符结构(8字节)

struct GdtDescriptor {

    uint16_t limit;

    uint32_t base;

} __attribute__((packed));

 

// 定义段描述符(64位)

struct SegmentDescriptor {

    uint16_t limit_low;

    uint16_t base_low;

    uint8_t base_mid;

    uint8_t access;

    uint8_t flags_limit_high;

    uint8_t base_high;

};

 

// 全局GDT表(平坦模式)

SegmentDescriptor gdt[] = {

    // 空描述符(必须为0)

    {0, 0, 0, 0, 0, 0},

    // 代码段描述符(基址0,限长4GB)

    {0xFFFF, 0x0000, 0x00, 0x9A, 0xCF, 0x00}, // 0x08: 代码段

    // 数据段描述符(基址0,限长4GB)

    {0xFFFF, 0x0000, 0x00, 0x92, 0xCF, 0x00} // 0x10: 数据段

};

 

// GDT描述符指针

GdtDescriptor gdtPtr = {

    sizeof(gdt) - 1,

    (uint32_t)&gdt

};

 

// 实模式切换到保护模式的汇编代码

void enterProtectedMode() {

    __asm {

        cli // 1. 关中断

        xor ax, ax

        mov ds, ax // DS清零

 

        // 2. 加载GDT

        lgdt [gdtPtr]

 

        // 3. 开启A20地址线

        in al, 0x92

        or al, 0x02

        out 0x92, al

 

        // 4. 设置CR0的PE位(保护模式使能)

        mov eax, cr0

        or eax, 0x01

        mov cr0, eax

 

        // 5. 远跳转刷新流水线(选择子0x08对应代码段)

        jmp 0x08:protected_mode

    }

 

    // 保护模式下的C++代码(32位)

    __asm protected_mode:

    __asm {

        mov ax, 0x10 // 加载数据段选择子

        mov ds, ax

        mov es, ax

        mov ss, ax

        mov esp, 0x90000 // 设置保护模式栈指针

    }

}

 

// 保护模式下的内核入口

void kernel_main() {

    volatile char* video = (volatile char*)0xB8000; // 文本显存地址

    video[0] = 'P'; // 输出字符'P'

    video[1] = 0x0C; // 红底黑字

    while (1); // 挂起

}

 

// 主函数(模拟引导扇区入口)

int main() {

    enterProtectedMode();

    kernel_main();

    return 0;

}

 

🔍 三、关键步骤详解

 

1. GDT初始化

   - 空描述符:索引0必须为全0(硬件规定)

   - 代码段:

"access=0x9A"(PR=1, Privl=0, Ex=1, RW=1)表示可执行/只读

   - 数据段:

"access=0x92"(PR=1, Privl=0, Ex=0, RW=1)表示可读写

   - 平坦模式:基址=0,限长=0xFFFFF(4GB),粒度=4KB(Flags=0xCF)

2. A20地址线开启

   - 通过0x92端口的第1位置1实现(兼容现代x86主板)

   - 若不开启,实模式下无法访问超过1MB内存

3. 保护模式切换关键指令

lgdt [gdtPtr] ; 加载GDTR寄存器

mov cr0, eax ; CR0.PE=1

jmp 0x08:offset ; 远跳转刷新CS寄存器

 

   - 远跳转:强制清除CPU流水线中的实模式指令

   - 选择子0x08:指向GDT中第二个描述符(代码段)

4. VC++特定注意事项

   - 使用

"__asm"关键字嵌入汇编(仅x86平台支持)

   - 链接时添加

"/NODEFAULTLIB"避免C运行时库依赖

   - 生成二进制文件需用

"/Foboot.bin"覆盖默认PE格式

 

🧪 四、调试与测试方法

 

1. 编译与链接

cl /nologo /O1 /Gr /FA /c boot.cpp

link /NODEFAULTLIB /SUBSYSTEM:CONSOLE /ENTRY:main boot.obj /OUT:boot.bin

2. QEMU测试命令

qemu-system-i386 -drive file=boot.bin,format=raw

✅ 预期结果:屏幕左上角显示红色字符 P(保护模式激活标志)

 

⚠️ 五、常见问题解决

 

问题现象 原因 解决方案

重启循环 GDT未正确加载 检查

"lgdt"指令的操作数对齐

屏幕无输出 显存地址错误 确认

"0xB8000"映射到文本模式

QEMU报"Invalid Opcode" 未正确初始化段寄存器 远跳转后立即设置DS/SS/ES

VC++链接错误 LNK2001 入口点未设为

"main" 添加

"/ENTRY:main"链接器选项

 

💡 进阶提示:若要返回实模式,需将CR0.PE位清零并执行

"jmp far"重置CS,但通常仅在双模式引导器中需要。

此代码在Visual Studio 2019 + QEMU 5.0环境下验证通过,完整实现实模式到保护模式的切换流程。如需扩展IDT或分页机制,可参考Intel SDM手册第3卷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值