本节摘要:按下电源后的第一个瞬间,没有操作系统、没有文件系统、没有内存管理——CPU 从一个硬件规定的固定地址开始执行固件代码,经过固件自检、读取引导扇区、加载内核几站接力,操作系统才接管世界。本节沿这条启动链逐站走,重点拆解引导扇区协议(0x7C00 与 510 字节加魔数),并亲手写一个在裸机上打印字符的 512 字节引导扇区。读完你能回答"内核被加载之前谁在执行",并拥有一件可运行的裸机作品。
先建立一个反直觉的事实:CPU 上电后执行的代码和你装的操作系统毫无关系。硬件把指令指针初始化到一个固定地址(x86 实模式下是 0xFFFF0 附近的固件映射区),那里躺着主板固件——老一代叫 BIOS,新一代叫 UEFI——的代码。固件的任务按序是:自检(确认内存、键盘、磁盘这些硬件都在岗)、枚举可启动设备、把设备第一个扇区(512 字节)读进内存固定位置、跳过去执行。从这一跳开始,控制权才离开固件,进入"你的代码"的地盘。
为什么是 512 字节、为什么是那个固定位置?历史答复:第一代 PC 的设计如此,读一个扇区是当时实现的最简方案,内存位置 0x7C00 则是当年某个决策的化石。但化石成了标准——四十年来所有 x86 引导扇区都遵守同一份协议:512 字节、被加载到 0x7C00、最后两字节必须是魔数 0x55 与 0xAA(没有魔数固件判定"这不是可启动介质")。协议虽老,机制不变:今天 Ubuntu 与 Windows 的引导器,最初几毫秒的日子也和我们要写的一样过。
把从电源到内核的路程站站标清,每一站的"代码来源"与"交接方式":
| 站点 | 执行者 | 位置 | 交接动作 |
|---|---|---|---|
| 第一站 固件自检 | BIOS 或 UEFI | 固件芯片映射区 | 检测硬件、选启动设备 |
| 第二站 引导扇区 | 你写的 512 字节 | 内存 0x7C00 | 固件读入并跳转 |
| 第三站 引导器 | GRUB 一类 | 磁盘若干扇区 | 从文件系统读入内核 |
| 第四站 内核 | Linux 或其他 | 内存高端区 | 初始化后启动首个进程 |
第二站到第三站的过渡值得多说一句:512 字节装不下任何像样的功能,它的正职是"接着搬运"——从磁盘读入更大的引导器(第三站),自己光荣退场。引导器再负责认识文件系统、按内核的加载协议把内核映像放进约定位置、按协议填好启动参数,最后跳进内核入口。每一站交接都遵守明确的协议(扇区协议、多引导协议、内核启动协议)——"协议链"是启动过程的关键词,这也解释了为什么引导开发的第一课永远是读协议而不是写代码。
还有一处必须现场补课的模式差异:上电时 CPU 处于 16 位实模式——地址总线 20 位、寄存器按 16 位用、寻址走"段寄存器乘 16 加偏移"的老规矩(1.2 节 ISA 契约的化石条款);直到引导器或内核显式切换,才有 32 位保护模式与 64 位长模式。写引导扇区就是写 16 位代码——NASM 里以 bits 16 声明,段寄存器在开场必须自己设好。

写一个在裸机上打印字符的引导扇区。没有 BIOS 中断以外的任何服务(打印字符走 BIOS 的 10h 号视频中断,这是固件留给引导扇区唯一的遗产),代码 16 位、段寄存器自理:
; boot.asm —— 裸机打印 A 后挂起 [bits 16] [org 0x7c00] ; 告诉汇编器:本段会被加载到 7C00 start: cli ; 引导早期先关中断,避免半初始化状态被打断 xor ax, ax mov ds, ax ; 数据段归零(实模式必须显式设段寄存器) mov ss, ax mov sp, 0x7c00 ; 栈定在本扇区下方,向下生长 mov ah, 0x0e ; BIOS 视频服务:电传打字模式 mov al, 'A' int 0x10 ; 调固件打印 .halt: hlt ; 停机等中断(中断已关,等于安静挂起) jmp .halt times 510-($-$$) db 0 ; 填零补齐到第 510 字节 dw 0xaa55 ; 魔数:小端序落盘即 55 AA
构建与运行(用 QEMU 模拟裸机,不需要真实机器冒险):
$ nasm -f bin boot.asm -o boot.bin ; -f bin:纯二进制,无任何格式包装 $ qemu-system-x86_64 -drive format=raw,file=boot.bin (窗口里出现一个大大的 A,然后安静挂起——你在裸机上运行了自己的代码)
逐条体会这段五十来行不到的代码里塞进的知识点:org 伪指令是 2.3 节地址观念的回归(汇编器需要知道最终加载地址才能算对绝对引用);设段寄存器与栈顶是 4.2 节栈知识的裸机版——没有装载器替你初始化,一切自己动手;times 填零与魔数是扇区协议的履行;hlt 是 3.3 节特权指令清单的成员——此刻你正以最高特权执行它,不受任何围栏限制。这个 A 是检验全部前六章理解的一块试金石:每一个字节都知道为什么在那儿,引导这块就算真懂了。
真实的引导扇区不会满足于打印字符,它的正职在 4.2 节呼过一次:用 BIOS 的磁盘读中断把后续扇区搬进内存,然后跳过去——这一步之后,你就离开了 512 字节的牢笼,可以加载任意长度的第二阶段代码,进而切换保护模式、加载自己的"迷你内核"。操作系统内核开发的入门路径正是沿这条线铺开:扇区加载 → 切模式 → 屏幕与内存管理 → 中断 → 调度。每一步的门槛都是"读协议",每一步的奖励都是"对机器的彻底掌控感"。
即使不打算写内核,这段经历的价值也是实的:调试操作系统问题时,你会知道内核之前还有谁、启动日志里每一行对应四站中的哪一站;分析启动慢的原因时,你能把"固件阶段"与"引导器阶段"分开计时——这些判断力,都源自亲眼见过上电第一毫秒的世界。
⚠️ 常见坑:在真实硬件上测试引导扇区。写错扇区协议轻则不启动,重则在老机器上扰动固件数据;一切实验请在模拟器(QEMU、VirtualBox)中进行——模拟器对固件协议的仿真足够忠实,且搞坏什么重启即可。
💡 关键直觉:引导是"信任链的根"——固件信任引导扇区、引导扇区信任引导器、引导器信任内核,每一环只做签名校验与接力。理解信任链,不仅读懂了启动,也读懂了安全启动(Secure Boot)这类机制防的是什么:链条上任何一环被替换,后面全部失守。
本章走完程序的内与外。最后一章把视野放到最广处:攻击者的汇编、别的架构的汇编,以及这门手艺的当下与未来。