来,准备开启干活模式
上一站收工的时候,咱们撂下过一句:接下来就是你了,这 512 字节的 MBR 引导扇区!那现在,它真的到咱们手上了。
虽然眼下的场面简直是荒原:机器上什么都没有——但咱们手里,已经有武器了。所以现在动手,不是心血来潮:错误有名字、排版有了引擎、断言死得明白、验证一条命令。这些家当都是咱们在开发用的电脑上磨出来的!所以卖过前一站的朋友,值得享受现在美好的基建!引导这一程,恰好是最容易"死了都没人知道"的一程。武器赶在点火之前磨好了,顺序上咱们没有吃亏。
不过有一段实在话,笔者得在动工之前交代:boot 是一个没有调试器拥抱您的世界。您平时写普通程序,崩了有栈回溯,挂了有核心转储,实在不行您 printf 一次也能问出一句话。引导代码呢?它跑在机器冷启动之后的那几毫秒里:那几毫秒里操作系统不在、标准库不在,连 main 都不存在——它要是死了,表现通常是无声无息的:屏幕一片黑、或者机器干脆重启,连一句遗言都不留给咱们。上一站开场咱们提过的"这个 CPU 怎么疯狂死机和重启了"的恐怖故事,从本站开始就是咱们自己的处境了。
所以上一站的验证习惯,到了这里被逼着升级。习惯本身没变——咱们改一行、敲一条命令,一下子几秒钟内见分晓。变的是命令背后开的东西。以前它跑的是几个测试程序,现在它要拉起的是一台虚拟机,把 512 字节写进一块裸磁盘镜像、让机器真的从"砖"启动一次。QEMU 和 debugcon 两样新的家伙,本章会给它们各自的位置。眼下您只需要知道:命令还是同一条,您敲下去就行。
一句老话,咱们来试试
真正给本站定调子的是另一件事。读者里要是有人翻过别的操作系统教程,您多半见过一个说法:引导扇区必须写汇编,因为 512 字节里装不下 C++ 的家当。这话笔者当年也是信的。第一遍做 Cinux 的时候,MBR 是一个纯汇编的文件,连读盘带跳转、全是一行一行的指令码。当时笔者想得很直白:512 字节寸土寸金,C++ 编译器生成的那些序言、对齐、栈帧,怎么塞得进去嘛。
这一遍笔者想把这句话亲手试掉。
试法很朴素——咱们做一个最小实验:一个 C++ 源文件,交给 GCC 一面 -m16 的旗子,让编译器生成 16 位的实模式代码。特权指令不另写汇编文件——直接用 GCC 的内联汇编嵌进 C++ 里。链接出了裸二进制、拿 QEMU 跑。整个实验的代码不到一百行、连读盘带跳转。结果呢?编出来的引导扇区,代码段一百五十来字节——512 字节的预算,咱们只用了不到三分之一。
您看,那句老话至少在今天的工具链上,是站不住的——C++ 装得下。于是本站咱们把立场摆明:起手就写 C++,其实连 MBR 都是一个 .cpp。汇编不消失、退到一条一条的指令上。整个引导链条里咱们不会出现一个 .S 文件。等本章收了官,您在仓库里会看到 boot/ 目录——里面躺着 mbr.cpp、stage2.cpp 和几份头文件,每一个其实都是 C++。
这个实验笔者做得比写下来的还糙:mbr、stage2 两对源文件加链接脚本、再配上一条 qemu 命令,笔者连构建系统都没进。糙有糙的好处——变量少,炸了就知道该找谁。
C++ 进了场,图的是什么?不为赶时髦。图的是武器库里磨出来的那套东西,终于能跟着咱们进最底层:编译器替咱们查类型、查名字、查警告,-Werror 的纪律照常在场,连"代码不许超过 512 字节"的要求,咱们都能交给构建系统去把关,而不是靠人眼。纯汇编也不是写不了,只是写起来没人替咱们看着。有人看着和没人看着——您深夜里调试一次就知道——是两个世界。
本站结束时,机器长什么样
咱们把目标也说具体。本站走完了,您在仓库根目录敲:
cmake -B build && cmake --build build --target run配置、编译、链接、拼镜像、拉起 QEMU——全在它里头的!咱们等它跑完,您的终端里会印出三行字:
Ready to call bios
Jump to Stage 2
[stage2] echo alive头两行是 MBR 说的,第三行是被它从磁盘上读进内存的第二段代码说的。这三行字意味着什么,咱们分别看看:BIOS 把咱们的 512 字节请进了内存,咱们的 C++ 在实模式里活了下来,它成功指挥 BIOS 读了盘,然后跳进了新读进来的代码。那段代码也活着呢,还能开口回话——机器从一块砖变成了一台会应答的机器。
当然,三行字背后是一条不短的路。咱们得认识实模式这个一兆字节的世界,得让 C++ 在 16 位的地界上站稳,得学会跟 BIOS 这个只此一家的中间人打交道、还得在构建系统里砌好几道墙,把"不小心超标"一类的事故挡在链接期。别慌——咱们把它们挨个走完就是了。
出发前的交代照旧:咱们默认您读过前置卷的工具链课和汇编课,或者有等价的底子。gcc、ld、objcopy 这些名字出现的时候,您不会问"这是啥"。看到
ljmp、int $0x13这样的指令,您知道它们大致在干嘛。细讲就不抢前置卷的活了,您缺什么回头补,咱们往前走。