从零写OS(一):让机器听你的第一句话
电脑开机之后,CPU 做的第一件事是什么? 不是运行 Linux,不是加载驱动,甚至不是执行你写的任何代码——而是执行一段固化在主板上的程序:BIOS(Basic Input/Output System,主板上的固件,负责硬件自检和引导启动)。 BIOS 做完硬件自检之后,会去找一个可以启动的磁盘。找到之后,它做了一件很朴素的事:把磁盘最开头的 512 字节复制到内存地址 0x7C00,然后跳过去执行。 就这样。没有操作系统,没有文件系统,没有任何框架。你写的代码,就从这 512 字节开始。 512 字节能干什么 说实话,不多。但够用来在屏幕上打一行字。 这一章的目标很简单:从磁盘启动,打印 Hello, OS!。 这段代码叫 Bootloader(引导程序,操作系统启动前最先运行的代码),是你第一次真正掌控机器的代码。 有两个硬性要求必须满足,BIOS 才会认你: 代码必须放在内存 0x7C00 开始的位置 这 512 字节的最后两个字节必须是 0x55 0xAA(引导魔数,BIOS 用来识别可启动扇区的标志) 少一个,BIOS 直接忽略你。 实模式:CPU 刚醒来的状态 启动的时候,CPU 处于实模式(Real Mode,x86 最原始的工作模式,16 位寻址,最多访问 1MB 内存)。 实模式有一个好处:可以直接调用 BIOS 提供的中断服务(INT,软中断,通过中断号调用 BIOS 内置功能)。打印字符用的是 int 0x10,往 ah 里放功能号 0x0E,往 al 里放字符,中断一触发,字符就出现在屏幕上。 这是整个计算机世界里最"低级"的打印方式,但也是最直接的。 两个坑 第一次写这段代码,我卡在两个地方: 段寄存器没初始化。 实模式下内存寻址是通过"段:偏移"来算的,ds、es、ss(数据段、附加段、栈段寄存器)这些上来没初始化,地址算出来是错的。在代码开头手动清零就好。 栈没设置。 汇编代码里调用任何东西之前,栈(Stack,后进先出的内存区域,用于保存临时数据和返回地址)要先指到一个安全的地方。把 sp(栈顶指针)设成 0x7C00 就够了——往下增长,不会覆盖代码。 跑起来 写好汇编,用 NASM(汇编器,把汇编代码编译成机器码)编译成二进制,扔给 QEMU(开源的硬件模拟器,可以模拟一台完整的 x86 计算机)模拟器: ...