点亮什么与为什么
上一章 001 我们在实模式里把 BIOS 能蹭的服务都蹭完了:读盘、开 A20、配 VESA、存 framebuffer 参数。这一章,我们要和实模式告别——建出 Cinux 的第一张 GDT,拨动 CR0 上的一个开关,再用一句远跳,让 CPU 真正跨进 32 位保护模式。跨过去之后,BIOS 就再也叫不应了。
这一章我们要点亮什么
001 的 Stage2 在配完 VESA 之后是直接 hlt 死等。002 在那个位置接上一段新代码,把机器从实模式切到保护模式:
... (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 OK、Mode 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 →
cli→lgdt→ 置CR0.PE→ 远跳刷新 CS → 刷新段寄存器);OSDev 的 Protected Mode / Global Descriptor Table 页对扁平模型有社区视角的总结。