用C语言手写微型OS,从引导到进程调度,新手也能跑通
只会CRUD的我,被劝退三次后,手写了一个操作系统?
我是个普通的C语言程序员,工作两年,每天就是写增删改查。最近觉得这样下去没意思,脑子一热,决定手写一个微型操作系统。结果被劝退了三次,差点放弃,但最后还真让我搞明白了点东西。

先说环境搭建。网上教程一堆,但光装工具就折腾了我两天。GCC、NASM、QEMU、GDB这四件套,听着简单,但交叉编译链这东西我头一回听说。系统自带的gcc编译出来的代码在裸机上跑不了,必须用x86_64-elf-gcc。我照着教程配好环境,写了个空内核,在QEMU里看到屏幕亮了一下,什么都没显示,但至少没报错。那一刻,我知道自己没装错。
然后是引导程序。加电后BIOS会找引导扇区,这个扇区只有512字节,最后两个字节必须是0xAA55。我用汇编写了个boot.s,把CPU寄存器初始化好,然后跳到C语言的main函数。要在屏幕上打印字符,得直接往显存地址0xB8000写数据。我试了半天,才打出第一行“Hello World”。虽然很丑,但那是我的代码第一次在裸机上干活。
接着是内核入口。链接脚本要把kernel_main函数定位到1MB地址处,因为保护模式的标准加载地址就是那里。写了个kernel.c,就一个死循环加打印字符。编译链接后,在QEMU里跑起来,看到那个字符一直在屏幕上闪,我高兴得差点跳起来。
但真正难的是从实模式切换到保护模式。实模式只能访问1MB内存,还没保护,多任务想都别想。保护模式能访问4GB内存,还有分段分页和特权级保护。切换需要三步:关中断、设置GDT、设置CR0寄存器。GDT就是全局描述符表,相当于给内存画了个地图,每个段定义好起始地址和权限。我照着教程写了个GDT,然后用汇编代码执行cli、lgdt、设置CR0,最后远跳转。在保护模式入口调用C函数,打印出32位环境下的字符,证明切换成功。这一步我卡了整整一天,因为一个寄存器没设置对,QEMU直接黑屏。
让操作系统“活”起来,需要处理中断、内存和进程调度。中断就像电话铃声,硬件打电话给CPU,CPU放下手头活去接。我写了个irq.c,用内联汇编的inb/outb读取键盘扫描码,然后回复EOI信号。按一下键盘,屏幕就显示一个字母,第一次觉得计算机这么听话。
内存管理我实现了个最简单的链表分配器,把空闲内存块串起来,每次分配找第一个够大的。malloc和free的原理原来就这么回事,以前学C语言时觉得指针抽象,现在亲手操作内存块,一下子全通了。
进程调度是最难的部分。每个进程有个进程控制块(PCB),记录它的状态、栈指针、指令指针。我写了个轮流调度算法,每次从就绪队列里找下一个进程,然后用内联汇编保存当前进程的寄存器,恢复下一个进程的。实现上下文切换时,我对着寄存器和栈的图看了半天,才明白为什么保存和恢复的顺序不能错。调试时一跑就崩溃,后来发现是栈指针没对齐,改完之后两个进程轮流打印数字,虽然只是简单的“A”和“B”,但那感觉,就像自己造了个小世界。
现在回头看,这个微型操作系统连个玩具都算不上,不能上网,不能跑程序,只能在QEMU里自己玩。但它让我彻底明白了几件事。第一,C语言的指针和内存管理不再是书本上的概念,而是我亲手操作、能直接导致屏幕输出或崩溃的武器。第二,一个printf背后,是显存、中断、进程调度的协同工作,以前写代码根本不会想这些。第三,这个经历让我在面试底层岗位时能聊出点干货,不再是只会CRUD的普通程序员。
当然,它也有现实的一面。门槛确实高,我C语言基础还行,但汇编也折腾了很久。而且这个微型OS不能用于生产环境,就是学习用的。如果你只写Web应用,不打算深入底层,花几个月学这个可能不划算。但如果你像我一样,想突破职业天花板,或者单纯想挑战自己,那手写一个OS是值得的。真正的收获不是那个能跑的系统,而是调试过程中查Intel手册、看反汇编、分析崩溃的循环。那个过程比任何书本都深刻。
现在AI辅助编程越来越普及,很多人觉得底层知识没用了。但我觉得,手写OS不是“屠龙之技”,而是理解计算机本质的捷径。你知道了硬件怎么工作,写出来的代码才会更靠谱。如果你也想试试,推荐从搭建环境开始,跑通第一个引导程序,然后在评论区告诉我,你被劝退了几次?
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)