Skip to content

点亮什么与为什么

上一章 001 我们在实模式里把 BIOS 能蹭的服务都蹭完了:读盘、开 A20、配 VESA、存 framebuffer 参数。这一章,我们要和实模式告别——建出 Cinux 的第一张 GDT,拨动 CR0 上的一个开关,再用一句远跳,让 CPU 真正跨进 32 位保护模式。跨过去之后,BIOS 就再也叫不应了。

这一章我们要点亮什么

001 的 Stage2 在配完 VESA 之后是直接 hlt 死等。002 在那个位置接上一段新代码,把机器从实模式切到保护模式:

text
... (VESA 配屏,同 001) ...
 └─▶ cli                  # 全程关中断:没有 IDT,开了中断就三重故障
      └─▶ DS = 0          # 给 lgdt 一个干净的寻址基址
      └─▶ lgdt gdt_ptr    # 把我们的 GDT 告诉 CPU
      └─▶ CR0 |= 0x1      # 拨动 PE(Protection Enable)位
      └─▶ ljmp $0x08, $pm_entry   # 远跳:刷新 CS,正式进入 32 位 PM
           └─▶ pm_entry (.code32)
                ├─ DS=ES=FS=GS=SS = 0x10   # 装载新的数据段选择子
                ├─ ESP = 0x90000           # 保护模式下的新栈
                ├─ outb 'P', $0xE9         # 往 debugcon 吐一个 'P'
                └─ hlt 循环

完成后你会看到:QEMU 窗口里 001 那几行文本(Stage2 OKMode info OK, switching...)照常出现,屏幕切进图形模式;然后——因为进了 PM、屏幕是图形、BIOS 也没了——没法再用 INT 0x10 打印。于是我们改用一个新的输出手段:往端口 0xE9 写一个字节,QEMU 会把它记进 build/debug.log。看到 debug.log 里出现一个 P,就说明保护模式这条链真的走通了。

为什么现在需要它

实模式有两个绕不开的天花板。第一,只能寻址 1MB(段<<4 + 偏移,段 16 位、偏移 16 位,理论 1MB+64KB),现代内核要管几百兆内存,这点空间塞牙缝都不够。第二,段式寻址那套 段<<4+偏移 又啰嗦又没有保护——任何程序都能写任何地址,一个野指针就能把别人(或内核自己)写花。

保护模式把这两件事一起解决:地址翻译改成"段选择子 → 查 GDT → 得到段基址和限长",而且每个段带访问权限(可读/可写/特权级),CPU 会检查——你拿数据段选择子去当代码执行,CPU 直接给你一个异常。要管大内存,我们用一张"扁平"的 GDT:段基址全设成 0、限长设成 4GB,于是"段"这个抽象基本透明,地址就等于线性地址——既绕开了 1MB 天花板,又为后面的分页打好了底。

那为什么是"现在"切?因为切过去之前,所有要用 BIOS 的活必须先干完(见 001 的解释)。001 把 VESA、A20 都做完了,这一章正好是离别的时刻。

外部依据:Intel SDM Vol.3A §9.9 给出了从实模式切换到保护模式的标准步骤(建 GDT → clilgdt → 置 CR0.PE → 远跳刷新 CS → 刷新段寄存器);OSDev 的 Protected Mode / Global Descriptor Table 页对扁平模型有社区视角的总结。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04