介绍 家里角落里是不是也有一台吃灰的老电脑?装Windows跑不动,装Linux都嫌它配置寒酸。小编最近给它找了份新工作:不装任何操作系统,直接跑我自己写的“内核”。 本文从按下电源键的那一刻讲起:BIOS怎么把控制权交给我们的代码、怎么写一个引导扇区、怎么从16位实模式切换到32位保护模式、最后用C写一个能在屏幕上打印的迷你内核。全部代码加起来不到两百行,先在QEMU里跑通,再用U盘拿真机开刀。 先说明一下:这里的“内核”是带引号的——它不会调度进程也不会管理内存,只会在屏幕上打招呼。但麻雀虽小,从上电到你自己的C代码接管整台机器,这条链路它可是完整走了一遍。 从按下电源键说起 老PC用的是传统BIOS启动(Legacy Boot),这套流程几十年没变过: 所以“写内核”的第一步,其实是写好这512字节的引导扇区。也正因为这套流程简单直接,老电脑反而是最好的裸机实验台:自带BIOS中断可以用,自带VGA文本模式可以打印,连显卡驱动都省了。(现代UEFI机器是另一套玩法,本文不涉及。) 准备工具 QEMU用来在动真机之前把代码调通——毕竟裸机上没有调试器,只有黑屏和不黑屏两种状态。本文的代码小编都在QEMU 6.2上实际跑过,文中的输出和截图都是真实运行结果。 步骤 第一步:先写个会打招呼的引导扇区 先来体验一下“机器归你了”的感觉。新建hello.asm: 编译成纯二进制,直接扔给QEMU: QEMU窗口里,SeaBIOS的启动信息后面会跟着一行我们的问候: 就这么二十来行汇编,没有操作系统、没有libc,屏幕上就有字了——打印的脏活是BIOS的INT 10h中断帮我们干的。 第二步:完整的引导程序——读盘、进保护模式 打招呼只是开胃菜。一个真正的引导程序还要干两件事:把内核从磁盘读进内存(BIOS只帮你搬第一个512字节),以及把CPU从16位实模式切到32位保护模式(不然C编译器生成的32位代码没法跑)。完整的boot.asm如下: 几个值得说明的地方: 第三步:真正的“内核”——C语言登场 在继续之前,先理清一个容易迷糊的点:C代码是什么时候、在哪里被编译的?答案是——在你的开发机上、制作镜像的时候。GCC把kernel.c提前编译成32位x86机器码,链接成不带任何格式头的纯机器码文件kernel.bin,再拼进磁盘镜像。老电脑上从头到尾都没有编译器,更没有什么“运行C程序的环境”——引导程序只是把第2扇区开始的那串字节搬到0x8000,然后一个call跳过去。对CPU来说那就是一串普通指令,它根本不知道、也不关心这些指令是C编译出来的还是手写的汇编。 整条流水线画出来是这样: 知道了这一点,内核的结构就好理解了:一个汇编入口加一个C文件。为什么还要一个汇编入口?因为C编译器并不保证kernel_main会被放在二进制文件的哪个位置,而引导程序是“盲跳”到0x8000的——所以我们用链接顺序保证kernel_entry.asm排在最前面,让0x8000处的第一条指令正好是call…
