Skip to content

一兆字节里的世界 ​

Hello! 这里是一片虚无哦!嘿嘿嘿!别找灯了,这里现在啥还没有呢!我们现在呢,电脑还没有开机。对于大部分的机器而言,开机的一瞬间,咱们电脑下,那块CPU芯片就要开始工作咯!对于任何一台 x86 的电脑,CPU一开始工作,就进入到了一个神奇的模式,叫做实模式。

笔者不会大论实模式喔,因为实模式现在已经几乎消失匿迹了。Intel的手册自己都说,我们直接进入影子模式,换而言之,旧的模式也在 x86 架构的没落下,慢慢退出了我们的舞台。

但是理解它,仍然能让我们尝试理解CPU正在试图做什么。所以,让我们来到您,对!就是您,在大早上准备上班的时候,给您的电脑按下开机键的那一瞬间,到底发生了啥吧!

从按下电源说起 ​

咔!您给您的机器上电了!别着急,对于 x86 架构,CPU 醒来的第一个状态——叫实模式(real mode)。它是 8086 那个年代传下来的兼容模式:16 位的寄存器,16 位的指令,没有任何内存保护——您写错一个地址,写坏的就是实实在在的物理内存,没人拦您。

在没有 UEFI 的日子的时候,您看到的是 BIOS。它在主板的固件里,一上电就接管了机器:认硬件、做自检,把设备初始化到"勉强能用"的状态。这些咱们都不用管,咱们关心的是它的收尾动作。BIOS 会按着启动顺序去找可启动的磁盘,把这块磁盘的第一扇区——整整 512 字节,读进物理内存的 0x7C00,然后一个跳转把执行权交给这 512 字节的开头。您问我上面有啥。。。啥都没有喔!这是您来写的!

是的!从那一刻起跑的,就是咱们的代码了。整个交接就这么干脆。没有加载器、没有格式解析。BIOS 只做一次最粗暴的检查——这 512 字节的末尾两个字节是不是 55 AA:是,它就跳了。不是——它就换下一块盘、或者报您一句没有启动设备。咱们常管这 512 字节叫 MBR(Master Boot Record、主引导记录),不过对咱们这个项目来说,它还有个更贴切的身份,咱们本节末尾再聊。

为什么偏偏是 0x7C00?您看数字——听着一点规律都没有,直觉倒是没错。IBM 当年的官方文档里,这个数只有事实、没有解释:BIOS 把第一扇区读到了 0x7C00,通篇没写为什么。

咱们真要追,线索要等到三十年后:有爱好者直接写信去问 BIOS 的作者 David Bradley,他在回信里给了一笔算法——引导代码安在"32KB 减 1KB"的位置:那 1KB 里头 512 字节是引导扇区自己,另外 512 字节留给它跑起来之后的栈和数据。32KB 是 0x8000——减去 1KB 的 0x400、落点正好 0x7C00,一笔算清。恍然大悟啊。

不过您留个心眼:这是当事工程师多年后在私人邮件里的回忆、经第三方整理公布,不算档案——咱们把它当一个有助于记忆的故事听就好。真正要紧的是:它是四十年不变的约定。BIOS 一定会把您放在那,您也只配拥有那 512 字节。

段:偏移,一套别扭但有效的算术 ​

广义的说,这种方式已经在当今的x64上消失了!我们这里提及,只是方便大家在动手做最简单的 BIOS 启动的时候,写不可避免的汇编时,起码能好受点!

实模式里最绕人的一件事就是寻址了。寄存器是 16 位的——一个 16 位数最大能表示 65535。可 8086 的地址线有 20 根、能摸到 1MB。您想想,16 位的寄存器,怎么够着 20 位的地址?

设计者的答案,咱们一看就明白:地址写成两半,咱们叫它段:偏移(segment 与 offset)。两半各是一个 16 位的数,咱们拿它们合到一起算物理地址——公式是:

实模式的段偏移地址计算

咱们拿 BIOS 交棒的那个地址,真的排一遍。段 0000、偏移 7C00:0x0000 乘了 16 还是 0,加上了 0x7C00,物理地址 0x07C00。咱们再看另一家,段 07C0、偏移 0000:0x07C0 乘了 16,而十六进制里乘 16 就是往右挪一位、低位补零,得到了 0x7C00、加 0、还是 0x07C00。您看,两家算出来倒是同一间房子,门牌的写法却完全不同。同一个物理地址,段:偏移的写法能凑出一大把——0x210 这个地址,您写 0020:0010 行、写 0000:0210 行、写 0021:0000 也行。

这套算术的两个推论,咱们都得记在手上。

头一个嘛,一段最多管 64KB——咱们让段固定、偏移随便跑,可偏移是个 16 位的数、顶多跑到 0xFFFF。想摸到更远的地方?只能换段。而且段和段之间是重叠的:您看 0x1000 段从 0x10000 起、0x1010 段从 0x10100 起,两家管辖的地界犬牙交错。

另一条呢,20 根地址线封了顶。咱们把段顶到 0xFFFF,偏移也顶到了 0xFFFF,算出来了 0xFFFF0 + 0xFFFF = 0x10FFEF。可地址线只有 20 根,第 21 根(A20)默认是关着的,再往上要么摸不着、要么绕回起点——这根线后来还牵出一整段兼容性的老故事,咱们往后撞见再聊。眼下您按 1MB 来记实模式的地界就够用、这个数后面到处要用。

地址有了两半——寄存器就得有人分头拿着。x86 一共六个段寄存器、各有各的差事,咱们挨个认:CS 装代码段、取指令跟着它走。DS 装数据段、咱们读写内存默认找它。SS 配着 SP 组成咱们的栈。ES 是个附加的段、给串操作和 BIOS 服务当帮手。剩下的 FS、GS,是后来 386 加的两个空位,实模式里咱们用不上。

这里面 CS 最特殊:它是唯一一个不能直接改的段寄存器。您写 mov %ax, %ds,DS 就换了。可您翻遍指令手册、也找不到一条 mov %ax, %cs——CPU 不许。咱们想换 CS,只有一种正经的办法:远跳转,ljmp 段:偏移,一条指令把 CS 和 IP 一起换掉了——这个限制眼下看着别扭,可咱们后面归一化 CS 的时候恰恰全靠它。

低内存的地图 ​

1MB 听着寒酸——可这 1MB 里还挤着一屋子的老住户。咱们把地图摊开——后面每一步落子都要对着它:

实模式低内存地图

咱们从下往上看:最底下 1KB 是IVT,中断向量表——它是一本 256 项的电话簿,int 指令一来——CPU 就按中断号翻到对应的一项,查到地址、跳过去。咱们待会儿要请 BIOS 干活,走的全是本电话簿,所以这 1KB 动不得。往上的 BDA 是 BIOS 自己的记事本,记着它数出来的设备情况,咱们同样别去碰。0x500 到 0x7BFF 这约 30KB 是真正的自由区,咱们安栈的地方就在这。0x7C00 起的 512 字节是咱们自己、再往上又是自由区。本站的第二段代码 stage2 就落在 0x7E00、紧挨着 MBR,而两家中间一个字节都不隔。

再往上就要小心了。0x80000 往上、紧贴着 0xA0000 的下方,有一块长度不定的 EBDA,是 BIOS 的扩展数据区。老机器占了 1KB、新固件可能占得更多,它有多长——咱们拿不到承诺,稳妥的态度就是别往那写东西。0xA0000 再往上就是显存和各种 ROM 的地界了。那是另一片世界、本站咱们一步都不踏进去。

地图是摊开看的,还有一条时间上的线。咱们把从上电到听见回声的全程连起来数五步:BIOS 把第一扇区读进 0x7C00、跳进去,入口桩把段和栈铺平、MbrMain 请 BIOS 把 stage2 读了进来,远跳交棒、stage2 出声应答——大伙看动画的时候,把眼睛放在每一步交给下一步的东西上。桩交出铺平的世界、MbrMain 交出读进来的 stage2、一步递一步,递到了最后才换得那一声"活着":

0.0s / 28.1s
STEP 01开场

您哪一步看得云里雾里,不要紧——桩怎么铺世界、读盘怎么请 BIOS、镜像怎么拼、回声怎么落地,后面四节挨个拆给您看。咱们这就下去。

BIOS 只在实模式应答 ​

地图看完了,咱们说说这世界里最重要的一位住户:BIOS 本身。

咱们马上要干的活里有一件只有 BIOS 能干:读盘。实模式下咱们没有驱动、没有文件系统,咱们想让磁盘转起来——只能请 BIOS 代劳。请的办法是软件中断——咱们往 AH 寄存器里放好功能号,该给的参数放齐、然后一条 int $0x13,CPU 就顺着 IVT 把咱们送进 BIOS 的磁盘服务里去了。

可这里有一条硬邦邦的时序限制,咱们得记牢:BIOS 这套服务只在实模式应答。它的电话簿挂在实模式的 IVT 里,它的代码按实模式的段:偏移写成。咱们往后会把 CPU 切进保护模式、再切进长模式。那两次搬家之后、中断的走法全变了,BIOS 这套门铃就再也按不响了——门铃没坏,是路没了。

所以引导代码有一条不成文的行事准则:趁 BIOS 还在,把只有它能干的活全干完:读盘、探内存、问显示模式,这些差事往后几站咱们会挨着请它办。每办完了一件,咱们离"永远告别实模式"就更近一步。本站咱们只办一件:读盘。

跳板,不是程序 ​

世界观齐了,来!镜头请!看看 MBR 自己的身份。512 字节装不下任何称得上"程序"的东西,没有像样的初始化、没有输入输出库,连一个安稳的栈都得自己搭。硬要在里面塞一整个引导逻辑?塞不下的。业界四十年的共识也是:谁塞谁后悔。

所以 MBR 的正确定位是:一块 512 字节的跳板。它的一生只干三件事:头一件,在巴掌大的地盘上站稳脚跟、把段和栈安顿好。第二件呢,把真正的引导代码从磁盘上读进内存。第三件呢,跳了过去、把世界交给下一段。它不算咱们操作系统的一部分,它是把操作系统"接生"出来的那双手。

当年笔者第一遍写纯汇编 MBR 的时候,对这个定位是有点不服气的:好不容易机器到手了,只干三件事就退场了?后来死过几回才服气。MBR 里的每一行代码,都在 512 字节的预算里跟人抢地盘。您在它身上多塞一个功能,留给"站稳、读盘、跳转"三件正事的余量就少一分。

这一遍咱们把 C++ 请进来、跳板哲学不变,变的是搭跳板的材料。C++ 装不装得下呢,咱们上一节已经亲手试过了。那接下来就看它在这块跳板上怎么站稳。下一节咱们正式开工,写 boot/ 目录里的头一份代码。

r05_long_mode-2-gfc5569b · fc5569b · 2026-10-01