预算顶着上限,地图跟着回改
上一站收工的时候咱们量过:stage2 实测 3376 字节,预算是 8 扇区的 4096,咱们手上还剩 720 个字节。当时咱们还说,下一站 VESA 的活是住得下了。这话这回食言了,而且食言得挺有教育意义。
顶穿,和拦住它的闸
机制这一层是这样的:stage2 的镜像住在 0x7E00,8 扇区的声明到 0x8E00 为止,超出去的那些字节,MbrMain 的 DAP 读盘根本不会读进来。动工前的试写探测里,咱们把枚举循环和逐模式打印一股脑编了进去,那版镜像量出了 4744 字节,已经越过了 0x8E00。
到了眼下这一版,没进内存的字节里头躺着的就是 .rodata 了,它从 0x8E38 起的头,所有格式串的地址都在那以上,要是绕开闸门、硬拿一个 8 扇区的镜像去跑,后半截镜像压根没进内存了,咱们自然也打不出一句话。咱们没有当场看过硬塞的走法,因为构建闸提前把路堵上了:咱们做 MBR 时立下的 stage2_size_check,对着 4744 字节的镜像按设计抛了 FATAL_ERROR,而 run 依赖它,给出的命令根本产不出 8 扇区的镜像。真想看看没加载的那一截,咱们得绕开闸门手动拼一个镜像。
那道闸的报错文本直接就是操作指引:报错指名 boot/layout.hpp 里的 .sectors,还告诉咱们 MBR 的 DAP 会自动跟上。咱们照着抬:
inline constexpr BootRegion kStage2Spot{.segments = 0x0000, .offset = 0x7E00, .sectors = 12};咱们的预算就此变成了 6144 字节,咱们重新构建,拿到的是绿灯。咱们这会儿再量正式版,数字从 4744 掉到了 4496。少的 248 个字节,就是撤掉那些逐模式打印挣回来的。咱们把这 248 个字节都减掉了,镜像也还是压不回 4096 以内了,12 扇区于是这么定下来了。您回头看看整个过程,咱们改的只有 .sectors 一处。DAP 读的是 kStage2Spot 的字段,它自己就跟上了。闸门的正则抓的也是它,同样跟着走了。上一站咱们把 stage2 住在哪儿、多大收拢成一个 BootRegion,这一回两处手改的成本都省掉了。笔者给这个项目拍板的时候,其实原话就是“这体现了我们的优势嘛”。咱们的 .sectors 声明了多少,DAP 和闸门读到的就是多少。咱们往后哪天要抬上限,改的也只有这么一处。眼下这一版哪天要是真把镜像压回了 4096 以内,咱们把它改回 8 就行了,闸门照样给咱们放行,两个方向都是通的。
.bss 里的新住户
.bss 这块地方本来就住着 g_memory_map,到了本站又添了四位。stage2.cpp 的匿名 namespace 里,咱们把它们排在 g_memory_map 身后:
cinux::boot::VbeInfoBlock g_vbe_info;
cinux::boot::ModeInfoBlock g_mode_scratch;
cinux::boot::ModeInfoBlock g_mode_chosen;
cinux::boot::FrameBufferInfo g_framebuffer;g_vbe_info 装控制器的自报家门,g_mode_scratch 留作查模式参数的草稿,g_mode_chosen 存下挑中那个模式的快照,g_framebuffer 收最终的存档。咱们把草稿和快照分开留,是有讲究的:循环里 QueryModeInfo 一遍遍覆写草稿,可咱们要写进存档的五个字段,得来自挑中的那个快照,而不是草稿里最后留下的模式详情。
轮到低内存了,咱们用一条 static_assert 看着它:内核的加载地址 kKernelLoadLma,得高于 stage2 镜像加上 .bss 里那几个全局变量的字节数。上一站写下的断言里只算了 g_memory_map 一份,本站咱们把新来的四位也全数登记了进去,sizeof 被咱们连乘带加排成了一长串。将来有谁再往 .bss 的地界搬东西,咱们写下的断言都会逼着他回来,把自家的尺寸数清楚才算完。
咱们顺带把地图的家务也做完。前头把 kVesaBuffers 删掉了之后,0x6000 到 0x6400 那块地的身份,就从留给 VESA 的预留变回了无主的空地。您要是手边还摊着上一站画的布局图,可以拿笔把它划掉:如今的地图上,低内存里每个区间都有真实的住户,咱们再找不出一块以后再说的预留。