简介:《Orange's:一个操作系统的实现》随书光盘是一份面向操作系统初学者的学习资料,配套书籍讲解进程管理、内存管理、中断处理、文件系统等核心概念。压缩包共2347个文件,以C源码(858个)、汇编(382个)、头文件(576个)、包含文件(244个)为主,另有实验镜像、Makefile等构建与运行配置,整体仅12.86MB,便于下载与本地实验。目前已有397人学习过,适合对操作系统实现感兴趣、希望动手编写与调试内核的读者。光盘内容围绕实验环节展开,包含可直接编译运行的示例代码、模拟器配置(bochsrc)及少量工具脚本,读者可据此搭建环境,从引导加载(loader)开始逐步跟踪内核初始化、中断与内存管理等关键流程,弥补纯理论学习的不足,加深对操作系统工作原理的直观理解。 拿到这本书的光盘,我的第一反应是“这玩意儿还能用吗”——毕竟书是2009年前后的,光盘在2024年的电脑上读不读得出来都是个问题。但说实话,如果你想真正理解操作系统,不是靠背面试题,而是亲手写一个能跑的内核,那这张随书光盘的价值到今天依然没有被替代。它里面是于渊老师一路从引导扇区写到进程调度、内存管理和文件系统的全部源码,配合那本书,构成了国内少有的“手把手带你从零写OS”的完整路径。
如果你只是看完书觉得“挺有道理”,那还远远不够。这张光盘的意义在于:它让你不用从空目录开始摸着黑写,而是有一个可编译、可运行、可调试的完整工程做参照。这篇文章我就详细聊聊光盘里到底有什么、为什么这个环境设计得这么“老派”、怎么把它在当代电脑上跑起来,以及怎么顺着代码把操作系统最核心的几个机制吃透。
1. 光盘里到底装着什么——不只是源码
1.1 目录结构与示例工程
光盘解开后,你首先会看到一组按章节组织的目录,从第1章的引导扇区开始,到后面的保护模式、分页、中断、进程调度、内存管理、文件系统,每一步都有对应的示例代码。这不是那种“书中片段摘录”式的光盘,而是每个章节独立可编译的工程。比如第一步的boot.asm,就是一个几百字节的汇编文件,编译出来就是一个512字节的引导扇区,运行起来屏幕上会打印一串字符。往后走,第5章的代码已经是一套完整的中断处理框架,第6章开始出现任务和调度,第9章的代码规模已经像一个小型操作系统了。
我建议拿到光盘后先不要把每个目录都点开看一眼就关掉,而是对照书的目录,把代码按照“引导扇区、进入保护模式、分页、中断、进程、文件系统”这个顺序过一遍。这个顺序就是操作系统启动的真实逻辑,也是你以后读Linux内核源码时的导航图。
1.2 自带的工具链
光盘里还集成了一整套开发工具,包括NASM汇编器、Bochs模拟器,以及读写软盘映像的辅助工具。在当年的学习环境下,Windows用户拿到光盘基本能直接开工,不需要自己再折腾一套交叉编译环境。这里有一个很多人会忽略的点:它自带的是Bochs模拟器,不是QEMU,也不是VMware。
Bochs和QEMU虽然都能模拟x86机器,但Bochs有一个不可替代的能力——内置调试器。你在被调试的操作系统里下断点、单步执行、查看寄存器和内存,就像在调试一个普通应用程序一样方便。做OS开发,光能跑起来是不够的,出问题的时候你需要在指令级别上观察执行流,Bochs在这方面是教学环境的最佳选择。
1.3 版本差异
光盘有不同批次的印刷,目录命名可能略有差别,有的版本第1章到第3章放在一起,有的版本每一章单独一个目录。但源码内容基本一致。如果你手上这张盘的Bochs在Windows 10/11上运行报错,最省事的办法是去Bochs官网下载新版,然后用光盘里的BIOS文件和虚拟机配置跑新版的Bochs。我在后面的实操部分会完整说一遍怎么做。
2. 为什么是这套“老环境”——环境选型背后的原理
2.1 为什么用Bochs而不是直接在VMware或VirtualBox里跑
很多人第一次接触这本书时会有个疑问:现在的虚拟机软件这么成熟,直接用VMware建一个虚拟机,挂载一个软盘映像不就行了吗?
可以,但你会失去调试能力。VMware和VirtualBox都定位在“运行一个完整操作系统”的层面,它们不会提供“查看当前GDT内容”“在0x7c00处暂停”“单步执行某条指令”这类设施。而写操作系统的过程,尤其是在早期阶段,几乎就是“改了代码、跑起来、挂掉、分析为什么挂”。每次挂掉你都要知道是在哪条指令出错的,访问了哪个地址,GDT基址和段选择子是什么。Bochs的调试器就像是给这个黑盒装了一个探针,你可以随时停下CPU,查看所有寄存器的状态,甚至直接修改内存。
我当年有一次在进入保护模式时总是触发异常,Bochs的调试窗口显示EIP停在一个完全没法理解的地址,后来用寄存器和线性地址一算,才发现GDT的基址在加载ldtr的时候被算错了。这种问题放到QEMU里,只能靠加打印信息慢慢试,但Bochs下几行命令就定位了。
2.2 为什么用NASM而不是用C或GCC
看完前几章你会发现,书里的代码几乎全是汇编,NASM语法。这不是因为作者偏爱汇编,而是引导扇区这个阶段,你得精确控制每一条指令,让CPU去执行你指定的字节序列。C编译器会帮你做很多事情,但也会在背后插入很多你可能完全没有意识到的动作,这些动作在一个没有操作系统支持的裸环境里,往往是致命的。NASM的语法相对直观,没有AT&T格式那种源操作数和目的操作数反着的别扭之处,对齐、填充等操作也非常灵活。
到了后面的章节,代码里开始有C语言写的部分,但编译C代码的时候已经使用了特殊的链接脚本和启动代码。第一次接触这个工程时,你会觉得为什么点灯的代码要写得这么繁复。当你把编译、链接、生成的二进制文件一节一节拆开看,就会明白这些步骤里藏着的都是“怎么让编译好的程序和裸机环境衔接”的细节。
2.3 软盘映像不是老古董
光盘里的代码编译出来之后,通常还需要写入到一个软盘映像文件(a.img),再由Bochs模拟器把这个映像当作软盘启动。这在今天看起来有点绕,但用“虚拟软盘”这个抽象,完美避开了“怎么把操作系统装进虚拟机”这个前置问题。你只需要把编译好的二进制文件写到映像的特定扇区,然后开机,CPU就会按照启动协议,把这片代码加载到内存并执行。
理解了这一步,你对“计算机是怎么启动的”这个问题的理解,会比绝大多数只会用系统的人清晰得多。因为“启动扇区”不是一个抽象概念,而是你亲手写进一个512字节块里的真实代码。
3. 把第一个引导扇区跑起来——环境搭建与运行全流程
3.1 Linux下的快速搭建
如果你用的是Linux,整个环境搭建其实非常简单。我之前在Ubuntu上测试,只需要安装两个包:nasm和bochs。
sudo apt install nasm bochs bochs-x bochsbios安装完成后,进入光盘里第一章的目录,找到boot.asm,编译并写入镜像:
nasm boot.asm -o boot.bin dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc这里有个细节容易踩坑:dd命令中如果不加conv=notrunc,写入后会把你原本的a.img截断成512字节,后面os代码需要多扇区的时候就会出错。所以只要是从镜像文件开始改,都要保留这个参数。
然后看一下工程里自带或光盘根目录下的bochsrc配置文件,确认软盘路径指向a.img,再启动Bochs:
bochs -q -f bochsrc如果一切正常,模拟器窗口里会出现一个黑底白字的输出,提示你已经进入了自写的引导程序。这个过程跑通之后,你就完成了一个现代程序员很少经历的飞跃:从开机第一条指令开始,整个执行流程都清楚地在你的控制之下。
3.2 Windows下的运行方式
在Windows上,光盘自带的Bochs版本在Windows 10/11上容易出现界面异常或直接崩溃的问题。我常用的方法是把Bochs升级到新版:
- 下载新版Bochs的Windows安装包,安装。
- 从光盘里找到BXBIOS和VGABIOS两个文件(一般在Bochs安装目录或工具目录中),把新版Bochs安装目录下对应的BIOS文件替换成光盘版本,或者反过来,把bochsrc文件里的路径指向光盘里的对应文件。
- 确认bochsrc里
vga: extension=vbe的配置和新版Bochs兼容,必要时删掉不认识的配置项。 - 双击运行Bochs,选择bochsrc文件。
这样做的原因在于,书里的操作是基于特定Bochs版本写的,新版Bochs如果换用不同的BIOS,显示和中断行为可能有细微差别,替换成光盘里的BIOS文件能最大程度还原书里的运行效果。
3.3 配置文件的关键参数
bochsrc是Bochs的配置文件,初学者不需要深究每个参数,但有几个必须看明白:
romimage:指定BIOS的路径。vgaromimage:指定VGA BIOS的路径。floppya:指定软盘映像。boot:指定启动设备,一般是软盘。
我在调代码时,经常会临时加一行debug: action=ignore来屏蔽Bochs的调试暂停,或者反过来用调试模式设置断点。理解这几个参数,比背下几百行配置更实用。
4. 从引导扇区到进程调度——书里代码怎么读最有效率
4.1 第一行汇编发生了什么事
第1章的boot.asm里第一个关键指令是org 07c00h,它告诉汇编器,后续代码将被加载到内存地址0x7c00处。这是BIOS加载引导扇区时的固定地址,IBM PC的设计规范里写死的。紧接着你会看到一段把段寄存器指向0x7c00所在段的代码,然后调用了BIOS的中断打印字符串。
这里我建议你逐行推演,而不是只满足于看到屏幕上有字打出来。理解“CPU从实模式的0xFFFF0地址开始执行BIOS代码,BIOS自检后读入硬盘第一个扇区到0x7c00,然后跳转到0x7c00执行”这个完整过程,你的计算机启动就不再是一个黑箱。
4.2 第3章的GDT是通往保护模式的门钥匙
整个工程最重要的分水岭出现在第3章,这一部分引导系统从实模式切换到保护模式。保护模式是后续所有内存管理、分页、多任务的基础。书里会引入GDT(全局描述符表)和段描述符的概念,代码里会有大量看起来模板化的数据结构初始化。
读这部分代码时,不要纠结每一条指令的具体字节,而要用一个模型来理解:实模式下的CPU用段基址加偏移访问内存,保护模式下的CPU要先查GDT表,通过段选择子找到对应的描述符,再由描述符里的段基址和段限长度量一次线性地址。这个从“段寄存器是地址的一部分”到“段寄存器是一个表的索引”的转变,是整个OS课程里第一个真正抽象的地方。代码里setup_gdt和加载gdtr的指令就是在做这件事。
我当时读完这一章后,在调试器里手动查看过GDT表的内容,逐个字节地对照代码里定义的各个描述符,确认基址和限长都与预期一致。这个过程耗时但极其值得,它会让你彻底摆脱对“操作系统自动完成一切”的误解。
4.3 第6章的进程切换值得反复读三遍
很多人在学操作系统理论时,最难跨过去的就是进程上下文切换:一个进程运行到一半,怎么被换成另一个进程?书里的第6章用最简单的代码实现了这个机制:两个任务轮流执行,每次切换时保存当前任务的寄存器到它的结构体里,再从另一个任务的保存数据中恢复寄存器,然后跳转执行。
这里的关键在于栈的变化。任务体的TSS(任务状态段)、内核栈、用户栈,三个栈的关系搞明白之后,你会突然觉得进程调度也不那么玄乎。我建议你把代码里保存寄存器序列的每一个push、pop都对应到任务结构体的字段上画一张图,标记出切换过程中ESP和EIP的变化。这张图画完,你对操作系统的理解会上升一个层次。
4.4 建议的阅读顺序与心态
我看到的很多读者,是拿书当小说从第1章读到第15章,然后发现代码完全连不起来。正确的方式应该是“每读完一章,就跑一遍这章的代码,改一处,观察变化”,比如把打印的字符改掉、把内存地址改掉、把任务数量从2个改成3个,看会发生什么。光盘里的代码足够完整,允许你在这个基础上做各种“破坏性实验”,坏了就重新复制一份,成本几乎为零。
另一个建议是:不要跳步。第3章的GDT没搞懂就跑第6章的调度,能跑通,但会出现大量“为什么”的未解之谜。你可以在第6章的代码里暂时“用起来”,但事后一定要回头补齐第3第4章的知识点。整个光盘代码串起来的逻辑主线就是操作系统启动与运行的真实路线:引导、模式切换、分页、中断、任务调度、内存分配、磁盘与文件系统。顺着这条线走,每一章都会成为下一章的铺垫,不存在“学了用不上”的内容。
5. 我踩过的坑和给你省下的时间
5.1 新版Bochs打开旧配置直接出错
这是最常碰到的问题。新Bochs对bochsrc的语法要求更严格,光盘里的旧配置文件里有些参数在新版本里被改名或移除了,比如旧的vga_update_interval配置可能引发提示,旧的display_library也要重新设置。我的处理方式是用新版Bochs自己生成的默认配置做底板,把光盘保留下来的关键项(软盘映像路径、BIOS文件路径、启动设备)抄进去,而不是反向去改旧文件。这样折腾5分钟,就能得到一个兼容的配置。
5.2 调试器比运行窗口更重要
初学的时候,Bochs的调试窗口会被当作一个“弹出来的烦人黑框”直接关掉。这真的很可惜。Bochs调试器里最有用的几条命令是:
b 0x7c00:在引导扇区加载地址下断点。c:继续执行。info gdt:查看GDT表。xp /100bx 0x0:查看内存内容。sreg:查看段寄存器和描述符缓存。regs:查看所有寄存器。
每当代码跑飞(屏幕上出现奇怪的乱码或者无限重启),你有两条路:一是瞎试,二是在调试器里单步走,看EIP跳到了哪里。我选后者。调试器定位出的问题,往往比翻书更快,因为你在“真实执行现场”看到了原因。
5.3 不要在物理机上尝试写盘
这个必须说。曾经非常好奇,在真实机器上能不能用这张光盘的引导扇区启动。结果发现代价远超预期:你需要一台支持软驱的老机器,或者想办法把a.img写入U盘再引导,而现代UEFI固件很多根本不支持直接从引导扇区启动。更关键的是,当时为了折腾这事,差点把一台电脑的启动记录搞坏。建议别碰这条线,Bochs里能100%还原体验,没必要让物理硬件承担风险。
5.4 编译报错的常见原因与快速排查
书里提供的是NASM汇编指令集,用新版NASM编译旧代码时,如果编译器提示指令或语法问题,优先检查是否是版本对指令的兼容性变化。例如,某段代码用的lidt描述符定义方式在NASM 2.x和旧版本之间有差异,如果你不在行的,就换用光盘自带版本安装,保证和作者当时环境一致。
还有一个常见的隐蔽问题:链接和写入软盘映像时,偏移量没对齐。有些章节的代码不只一个扇区,编译后生成的二进制文件是多扇区的,写入镜像的时候必须从第2个扇区开始按顺序写,不然引导扇区后面的代码就会错位。我至少有两次栽在这个地方,后来养成了写完总是用xxd查看头部字节的习惯,确认第一行就是我期望的跳转指令。
5.5 一个减少折磨的工作流
最后分享我觉得最顺手的工作流。我习惯给每一章建一个独立的工作目录,里面复制光盘里该章的源码,但这个目录与书附光盘的原结构隔离。每次改代码前先编译一遍,确认baseline是好的,然后再动手,出了问题就在Bochs调试器里观察。如果代码改到完全不可控,直接从光盘重新复制一份原版,把改动重做一遍。这个过程看似多花了一点时间,但它让“试错”的成本降到了最低,也不会因为自己的实验把原始工程搞坏。
如果你也能跑通第一章的引导扇区,并且亲眼看到自己写的代码在模拟器里执行起来,你就会明白为什么那么多人说操作系统的学习是“一定要动手”的。这张光盘里的代码不会替你做实验,但它是你能找到的最可靠的导航图,值得你认认真真地把里面的每一条汇编指令都跑一遍。
本文还有配套的精品资源,点击获取