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

如何在带图形支持的微内核运行时切换16位实模式与32位保护模式?

嘿,刚接触汇编就敢搞带图形支持的微内核,勇气可嘉!我来一步步帮你拆解实模式和32位保护模式切换的核心逻辑,结合你的i686-elf交叉编译环境和内联GAS语法来实现,尽量讲得直白好懂~

实模式↔32位保护模式切换的核心思路与实现

你要调用的中断0x10确实只能在16位实模式下工作,所以核心逻辑就是:先从32位保护模式切回实模式,调用中断设置图形模式,再切回保护模式。下面分环节给你讲清楚,附适配你环境的代码示例。

一、切换前的准备:实模式专用GDT

保护模式用的是32位平坦段GDT,但实模式需要16位的段描述符,所以得先定义一个简化的实模式GDT:

#include <stdint.h>

// GDT条目结构体(按x86规范打包)
struct gdt_entry {
    uint16_t limit_low;
    uint16_t base_low;
    uint8_t base_mid;
    uint8_t access;
    uint8_t granularity;
    uint8_t base_high;
} __attribute__((packed));

// GDT指针结构体
struct gdt_ptr {
    uint16_t limit;
    uint32_t base;
} __attribute__((packed));

struct gdt_entry real_mode_gdt[3];
struct gdt_ptr real_mode_gdtr;

// 初始化实模式GDT
void init_real_mode_gdt() {
    // 空描述符(必须放在第一个位置)
    real_mode_gdt[0] = (struct gdt_entry){0};
    // 16位代码段:基址0,限长0xFFFF,特权级0,可执行可读
    real_mode_gdt[1] = (struct gdt_entry){
        0xFFFF, 0x0000, 0x00, 0x9F, 0x00, 0x00
    };
    // 16位数据段:基址0,限长0xFFFF,特权级0,可写可读
    real_mode_gdt[2] = (struct gdt_entry){
        0xFFFF, 0x0000, 0x00, 0x93, 0x00, 0x00
    };
    real_mode_gdtr.limit = sizeof(real_mode_gdt) - 1;
    real_mode_gdtr.base = (uint32_t)&real_mode_gdt;
}

二、完整切换流程的内联GAS实现

下面是封装好的函数,实现“保护模式→实模式→调用中断0x10→切回保护模式”的完整流程:

void switch_to_real_mode_and_set_video(uint8_t video_mode) {
    cli(); // 先关中断,切换过程不能被打断

    // 加载实模式GDT
    asm volatile (
        "lgdt %0\n\t"
        :
        : "m"(real_mode_gdtr)
    );

    // 关闭保护模式:把CR0寄存器的PE位(第0位)清零
    uint32_t cr0;
    asm volatile (
        "movl %%cr0, %0\n\t"
        "andl $0xFFFFFFFE, %0\n\t"
        "movl %0, %%cr0\n\t"
        : "=r"(cr0)
        :
        : "memory"
    );

    // 远跳刷新流水线,进入16位实模式
    asm volatile (
        "ljmp $0x08, $1f\n\t" // 0x08是实模式GDT的代码段选择子
        ".code16\n\t"         // 告诉GAS接下来是16位汇编
        "1:\n\t"
        // 初始化实模式段寄存器
        "movw $0x10, %%ax\n\t" // 0x10是实模式GDT的数据段选择子
        "movw %%ax, %%ds\n\t"
        "movw %%ax, %%es\n\t"
        "movw %%ax, %%fs\n\t"
        "movw %%ax, %%gs\n\t"
        "movw %%ax, %%ss\n\t"
        // 设置实模式栈(选0x7000,避开内核初始化区域)
        "movl $0x7000, %%esp\n\t"
        // 调用中断0x10设置视频模式:AH=00是设置模式,AL传模式号
        "movb $0x00, %%ah\n\t"
        "movb %0, %%al\n\t"
        "int $0x10\n\t"
        // 开始切回32位保护模式
        "cli\n\t"
        "movl %%cr0, %%eax\n\t"
        "orl $0x01, %%eax\n\t" // 把CR0的PE位设为1,开启保护模式
        "movl %%eax, %%cr0\n\t"
        "ljmp $0x08, $2f\n\t" // 远跳回32位代码段(假设你保护模式代码段选择子是0x08)
        ".code32\n\t"         // 切回32位汇编模式
        "2:\n\t"
        // 恢复保护模式的段寄存器
        "movl $0x10, %%eax\n\t" // 假设你保护模式数据段选择子是0x10
        "movl %%eax, %%ds\n\t"
        "movl %%eax, %%es\n\t"
        "movl %%eax, %%fs\n\t"
        "movl %%eax, %%gs\n\t"
        "movl %%eax, %%ss\n\t"
        :
        : "r"(video_mode)
        : "eax", "ebx", "ecx", "edx", "memory" // 声明被破坏的寄存器,避免GCC优化出错
    );

    sti(); // 重新开启中断
}

三、避坑关键提醒

  • 一定要用.code16/.code32伪指令:GAS需要明确知道当前是16位还是32位汇编,不然指令会编译错误
  • 远跳不能省:CPU的指令流水线会缓存旧模式的指令,远跳能强制刷新,避免执行错误代码
  • 实模式栈要选对:实模式下栈不能超过64KB(段寄存器左移4位+栈指针不能超过0xFFFF),还要避开内核或应用的内存区域
  • 备选简化方案:如果不需要动态切换图形模式,不如在内核初始化的实模式阶段就设置好图形模式,这样就不用来回切换了,更省心

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:34:17