第二个编译世界
咱们在切模式之前,还得回答一件本站真正的大事:跨过远跳之后要跑的代码,从哪儿来?
当年的答案是 .code16 和 .code32 的一对汇编器指令。咱们在同一个汇编文件里办事:远跳之前标上的是 .code16,远跳之后补上的却是 .code32,汇编器就照着把两边的指令各编成 16 位和 32 位的机器码。可这里有个当年把笔者绕了半天的认知点:这两句是写给汇编器看的,而不是写给 CPU 的。而 CPU 拿哪种宽度译码,看的是 CS 指向的段的 D 位,而汇编器编成哪种宽度,看的是咱们标下的指令。两边各管各的,一旦对不上——比如某段代码标错了边,CPU 就会拿着 16 位的译码去啃 32 位的编码,几条指令之内就是一场错位的车祸了。所以它们的分界,必须严丝合缝地卡在远跳的身上。
这一遍咱们不写汇编文件了,整个 stage2 都是 C++ 的,那么边界落在哪儿呢?咱们把它落在了文件上。pm_switch.cpp 是 16 位世界的最后一段代码,它跟 stage2 的其余部分一起用 -m16 编译,而远跳要去的 pm.cpp 整个文件都是 -m32 编的。编译器替咱们当了当年 .code16/.code32 的角色:-m16 的世界里编出的指令带着 16 位的操作数前缀,-m32 的世界里编出的就是原生的 32 位编码。一条 ljmp 从前一个文件伸进了后一个文件,咱们跨过去,CPU 的译码和代码的编码就同时换了挡。
边界还让咱们固定进了构建系统。您看 boot/CMakeLists.txt:
add_library(boot_pm OBJECT
pm/pm.cpp)
target_link_libraries(boot_pm PRIVATE cinux_warnings cinux_boot32)pm.cpp 并没有直接挂进 stage2 的目标,而是自己组了一个 OBJECT 库,链的是 cinux_boot32,也就是 32 位的那套编译配置。为什么不把源文件列进 stage2 再逐个改属性呢?因为 stage2 目标整体挂着 cinux_boot16,源文件级的选项叠上去,两套 -m 就打了架,谁赢全看赌运了。独立成了库以后,边界就长在了构建系统里:咱们任何人打开工程里的 CMake、连一行汇编都不用读,也能一眼看穿工程里住着的两个编译世界,16 位的一个,32 位的又一个。
那么两个世界里的代码,互相能借用吗?这个问题咱们得分两面答,而且两面都挺有意思。
能借的一面,咱们打头说。pm.cpp 一进门就把 early/boot_console.hpp 包含了进来,紧接着直接调 serial::PutString 打了整行字——它什么额外的事都没干,32 位版的打印函数就到手了。为什么?因为咱们的早期输出件住在头文件里,是 inline 的:每个包含它的编译世界,都白拿到一份按自己的位数实例化出来的版本。16 位的世界有一份 16 位编码的 PutString,32 位的世界也有自己的一份,各住各的翻译单元,互不打扰了。
借不动的一面,就得说到 .cpp 编译的实体了。格式引擎就是这么住的,format/entry.cpp 加上 radix.cpp 的那几个件,眼下都是按 -m16 编进 stage2 的。它们编出来的机器码就是 16 位编码,32 位的世界想 call 过去,就成了拿新译码去啃旧编码——跟当年 .code16/.code32 标错边是同一场车祸,只是换了行头。所以 pm.cpp 里咱们打印的是常量整行,不劳格式引擎的大驾。咱们真要在 32 位世界里用上格式引擎,就得按每个世界各编一份的路子重新安排,那个活儿咱们不预写,等真有了需求再说。
链接倒是一点都不折腾。-m16 编出来的目标文件,和 -m32 的一样,都是 ELF32 i386——-m16 的本质是“32 位编码加前缀”,两边目标文件的 ABI 是同一个。所以两个世界一次链接、共用一份链接脚本,ljmp $0x08, $PmEntry 直接把符号接在了一起。咱们这里还留着一处小绊,是笔者替您记着的:跨世界被汇编引用的符号得是 extern "C"。C++ 的名字修饰在两个世界里不一定对得上号,而 C 的链接约定最稳,这跟咱们在 MBR 里立的约是同一条。笔者的第一版还把 EnterProtectedMode 的声明塞进了匿名 namespace,声明成了内部链接,跟另一边的外部定义压根是两个符号,IDE 当场就把错咬了出来。咱们把声明放回 include 区,一刀两断的事。
咱们回头数一数,工程里从此住着三个世界:host 的一个,16 位 boot 的一个,还有 32 位 boot 的一个。世界多了,边界反倒更清楚了:每个世界住在哪些文件里、用什么编译、能看见谁,全都有名有姓的。新住进来的那户邻居就是 boot_pm 这个 OBJECT 库,32 位的活计往后都从它这儿出。