电脑开机之后,CPU 做的第一件事是什么?

不是运行 Linux,不是加载驱动,甚至不是执行你写的任何代码——而是执行一段固化在主板上的程序:BIOS(Basic Input/Output System,主板上的固件,负责硬件自检和引导启动)。

BIOS 做完硬件自检之后,会去找一个可以启动的磁盘。找到之后,它做了一件很朴素的事:把磁盘最开头的 512 字节复制到内存地址 0x7C00,然后跳过去执行。

就这样。没有操作系统,没有文件系统,没有任何框架。你写的代码,就从这 512 字节开始。


512 字节能干什么

说实话,不多。但够用来在屏幕上打一行字。

这一章的目标很简单:从磁盘启动,打印 Hello, OS!

这段代码叫 Bootloader(引导程序,操作系统启动前最先运行的代码),是你第一次真正掌控机器的代码。

有两个硬性要求必须满足,BIOS 才会认你:

  1. 代码必须放在内存 0x7C00 开始的位置
  2. 这 512 字节的最后两个字节必须是 0x55 0xAA(引导魔数,BIOS 用来识别可启动扇区的标志)

少一个,BIOS 直接忽略你。


实模式:CPU 刚醒来的状态

启动的时候,CPU 处于实模式(Real Mode,x86 最原始的工作模式,16 位寻址,最多访问 1MB 内存)。

实模式有一个好处:可以直接调用 BIOS 提供的中断服务(INT,软中断,通过中断号调用 BIOS 内置功能)。打印字符用的是 int 0x10,往 ah 里放功能号 0x0E,往 al 里放字符,中断一触发,字符就出现在屏幕上。

这是整个计算机世界里最"低级"的打印方式,但也是最直接的。


两个坑

第一次写这段代码,我卡在两个地方:

段寄存器没初始化。 实模式下内存寻址是通过"段:偏移"来算的,dsesss(数据段、附加段、栈段寄存器)这些上来没初始化,地址算出来是错的。在代码开头手动清零就好。

栈没设置。 汇编代码里调用任何东西之前,(Stack,后进先出的内存区域,用于保存临时数据和返回地址)要先指到一个安全的地方。把 sp(栈顶指针)设成 0x7C00 就够了——往下增长,不会覆盖代码。


跑起来

写好汇编,用 NASM(汇编器,把汇编代码编译成机器码)编译成二进制,扔给 QEMU(开源的硬件模拟器,可以模拟一台完整的 x86 计算机)模拟器:

nasm -f bin boot.asm -o boot.bin
qemu-system-x86_64 -drive format=raw,file=boot.bin -nographic

然后看到:

Booting from Hard Disk...
Hello, OS!

这一行字,代表你的代码是第一个在这台(虚拟)机器上运行的东西。BIOS 把控制权交给了你。


这一步之后

512 字节的空间太小,做不了什么大事。真正的操作系统要做的事情多得多——切换 CPU 模式、设置内存管理、处理中断……

这些都是后面的章节。但起点就在这里:让机器在屏幕上打出你指定的第一句话。

源码在这里:github.com/tongpengfei/learn-with-ai