十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

《Orange‘s》随书光盘实践指南:从引导扇区到内核调试

《Orange‘s》随书光盘实践指南:从引导扇区到内核调试 简介《Oranges一个操作系统的实现》随书光盘是一份面向操作系统初学者的学习资料包围绕进程管理、内存管理、中断处理、文件系统等关键概念提供配合图书阅读的源码与实验材料帮助读者通过实际操作理解理论。压缩包共2347个文件约12.86MB文件以C源码、H头文件、ASM汇编、INC包含文件为主体并包含Makefile构建脚本、Bochs虚拟机配置和磁盘镜像IMG便于搭建编译与运行环境这些实验配套资料覆盖了从汇编启动到内核模块的完整链路可循序渐进地推进学习。已有397人学习/下载适合希望以动手实践方式入门操作系统原理的读者。通过阅读源码与完成实验读者可以逐步了解引导加载、进程调度、内存分配、设备驱动等实现细节并尝试构建一个简单的操作系统内核亲手编写和调试代码的过程也能有效锻炼问题排查与系统调试能力为后续深入钻研复杂系统奠定基础。1. 这本书到底在讲什么为什么值得折腾如果你是个计算机专业的学生或者半路出家的嵌入式、底层开发爱好者大概率在某个深夜翻过《Orange’s: 一个操作系统的实现》。我一直觉得于渊写的这本书是中文世界里少有的“能让人真正动手把操作系统做出来”的教材而不是那种讲了一堆进程调度算法结果连一个引导扇区都没跑起来的理论书。书名里的“Orange’s”就是书中一步步实现的那个操作系统它不是一个玩具而是一个具备保护模式、内存分页、中断处理、任务调度、文件系统、系统调用这些核心模块的真实内核。随书光盘里装的是各章节对应的源代码、可执行镜像、Bochs 虚拟机配置以及一些工具链。说白了这本书的价值不在于“读”而在于“照着敲、亲手跑、改出问题再修”。适合谁来折腾我觉得有三类人最合适一是刚学完计算机组成原理、操作系统理论课想看看真实内核长什么样的学生二是做嵌入式、驱动开发天天跟启动流程和寄存器打交道想系统补一遍底层知识的工程师三是纯粹的好奇心驱动型选手就想知道电脑开机那几秒到底发生了什么。无论哪一类只要你能坚持做到第三章很多原来觉得玄乎的概念比如 GDT、LDT、特权级、任务切换都会变得具体到能摸能碰。光盘里的代码虽然距今有些年头但操作系统的基本原理没变过。你不需要最新最强的硬件也不用装什么重型 IDE一台普通的 x86 机器、一个虚拟机软件、一个文本编辑器就够了。这也是我喜欢这本书的地方门槛低到不可思议却能带你走到“内核开发者”的门口。2. 随书光盘里到底装了什么先说结论光盘里的内容就是这本书的“完整可运行工程包”。理解它比直接抄代码重要得多。2.1 光盘内容速览光盘根目录下一般会有这几个东西src/各章节源代码按章节组织比如chapter0/、chapter1/、chapter2/……一直到文件系统和 shell 部分。image/编译好的软盘镜像或硬盘镜像拿来就能用 Bochs 直接启动省去自己编译的麻烦。bochs/Bochs 虚拟机软件的 Windows/Linux 版本以及现成的配置文件bochsrc。tools/一些辅助工具比如软盘镜像写入工具、Makefile、链接脚本等。还有一份README或者目录说明通常会写明每个目录对应的章节和运行方式。我建议你拿到光盘后的第一件事不是急着看代码而是先按 README 把某个章节的镜像跑起来比如第二章或第三章的能够在 Bochs 里看到橙色窗口、打印出字符、响应键盘输入那种“我的机器上跑着一个我自己可控的内核”的获得感是支撑你继续学下去的巨大动力。2.2 源代码目录结构解读以书中后期比较完整的章节为例它的kernel/目录大致长这样kernel/ ├── start.asm ; 入口切换到保护模式、设置段寄存器 ├── protect.c ; GDT/IDT 初始化、描述符操作 ├── kernel.asm ; 中断入口、任务切换辅助汇编 ├── main.c ; kernel_main 初始化流程 ├── i8259.c ; 8259A 中断控制器驱动 ├── clock.c ; 时钟中断 ├── keyboard.c ; 键盘驱动 ├── tty.c ; 终端字符输出 ├── console.c ; 文本模式显存操作、滚动 ├── process.c ; 进程/任务管理调度器 ├── mm.c ; 内存管理分页 ├── fs.c ; 简易文件系统 ├── syscall.c ; 系统调用分发 └── ...这个结构就是一颗“微型内核”的心智模型。你会在start.asm里体会到什么叫“从实模式到保护模式”在i8259.c里搞清楚为什么通常要屏蔽所有中断再逐个打开在process.c里的那几十行调度代码中理解“时间片轮转”到底转的是什么。每读一个文件操作系统教科书上的一个章节就会“落地”一次。需要提醒一点光盘里的代码是那个年代的编译器风格写的有些地方拿到现在的 GCC 下编译会报警告甚至报错。这不是书的问题而是工具链演进了。遇到这种情况别急着怀疑代码先查编译选项和语法兼容性后面我会专门写一段排查经验。3. 搭建可复现的实验环境3.1 为什么运行首选 Bochs而不是 VMware 或 VirtualBox光盘里附带 Bochs 配置这不是随便选的。Bochs 是一个 x86 模拟器它纯粹靠软件模拟 CPU、内存、外设不依赖硬件虚拟化。好处有三个一是可以精确控制 CPU 的执行节奏方便调试启动流程二是它的调试器非常强大能在实模式和保护模式之间、在每一条指令之后停下来观察寄存器、内存、栈、中断三是它对“从软盘启动”这类古老但最贴近原理的启动方式支持非常好。VMware 和 VirtualBox 是“虚拟机”而非“模拟器”它们的侧重点是运行操作系统而不是让你调试内核。写操作系统的时候你需要的不是“跑得飞快”而是“看得清楚”。Bochs 可以做到让内核在每一行汇编上停下来再一步步走下去。这也是为什么该书和很多其他操作系统教程都默认用它。当然我个人在现代 Linux 主机上做实验时也会用 QEMU 作为补充——它启动快、控制台输出方便。但首推仍然是 Bochs尤其是跟着书一步步走时Bochs 的调试体验无可替代。你可以在 Bochs 里随时按CtrlC暂停运行然后输入info registers、x /20bx 0x10000这类命令查看现场这种“打断内核运行看内幕”的能力对排查启动崩溃来说简直是救命稻草。3.2 环境准备清单以现代 64 位 Linux 发行版为例你需要装这几样东西- Bochs带调试器支持比如 bochs-sdl 或 bochs-x11 - NASM 2.x - GCC、as、ld32 位编译支持比如 gcc-multilib - make在 Debian/Ubuntu 上一条命令就能装齐大部分依赖sudo apt-get install build-essential gcc-multilib nasm bochs bochs-x注意很多发行版的默认bochs包并不包含调试器模块以--enable-debugger编译才有。我个人是建议自己从源码编一个带调试器的版本步骤也很简单wget https://sourceforge.net/projects/bochs/files/bochs/2.7/bochs-2.7.tar.gz tar zxf bochs-2.7.tar.gz cd bochs-2.7 ./configure --enable-debugger --enable-iodebug --enable-x86-64 --with-x11 make -j4 sudo make install这样编出来的 Bochs 在启动时会进入调试命令行你就能用它做断点、单步、内存查看这些操作了。如果遇到编译依赖缺失把常见开发库装上比如libx11-dev、libncurses5-dev即可。3.3 跑通光盘里的第一个镜像先用光盘中现成的镜像跑一遍验证环境没问题。在image/目录下找到某个软盘镜像比如boot.bin或a.img然后写一个简单的bochsrc.txtmegs: 32 romimage: file/usr/local/share/bochs/BIOS-bochs-latest, address0xf0000 vgaromimage: file/usr/local/share/bochs/VGABIOS-lgpl-latest floppya: 1_44./a.img, statusinserted boot: a log: bochsout.txt mouse: enabled0注意romimage和vgaromimage的路径要改成你机器上真实存在的 BIOS 文件路径。然后启动bochs -q -f bochsrc.txt这里的-q表示跳过交互式菜单直接读取配置并启动。如果一切正常你能看到一个虚拟屏幕弹出来橙色的几个字符在屏幕上滚动或者出现一串系统启动信息。第一次见到自己的“操作系统”跑起来的时候那种奇妙的成就感我能记很久。如果启动黑屏或没有输出优先检查配置文件中的路径是否写对以及磁盘镜像路径是否可读。这是所有新手踩的最多的坑没有之一。4. 实践路径从最小的引导扇区到进程与文件系统光盘里的代码是按章节开放的从最简单的Hello, OS!到最后的 shell一共经历了好几个阶段。这个阶梯对我来说就是一部底层编程进阶史。4.1 阶段一引导扇区与实模式书的第 1 章或者叫第 0 章会带你写一个 512 字节的引导扇区它做的事情非常朴素将一段字符串写到显存然后死循环。不要觉得它简单这一个扇区就发生在开机后的最初几微秒内它展示了 x86 处理器如何从实模式启动、BIOS 如何把引导扇区加载到0x7C00以及 CPU 如何逐条执行机器指令。光盘里chapter1/下的代码你第一眼看到会觉得非常无聊org 07c00h mov ax, cs mov ds, ax mov es, ax call DispStr jmp $但用 NASM 编译并用dd写入软盘镜像后在 Bochs 里跑起来屏幕上出现了字符串。这一刻原来“操作系统启动”不再是教材上那幅晦涩的流程图。书上配套的Makefile里把编译、写入镜像、启动模拟器这些步骤都做好了你要做的是把每一步都手动执行一遍把中间产物都看一遍而不是只敲make。4.2 阶段二保护模式、GDT 与内存寻址的质变到第 3 章进入保护模式时我明显感觉到难度上一个台阶。这里要理解 x86 的分段机制是怎么在保护模式下变成“段描述符表 段选择子”的然后一步步打开 A20 地址线、设置 CR0 的 PE 位、重新加载段寄存器。这个过程就像是从“16 位的小屋子”搬进了“32 位的大楼”但搬家手续繁杂。GDT 里的每个描述符需要你亲手填段基址、段界限、粒度、特权级、类型位。光盘里的protect.c会给你一个完整的init_protect()流程你不需要背描述符格式但一定要对着表逐字段核对把每一个位都理解清楚。相信我这一步如果偷懒后面中断、任务切换会加倍还回来。一个经典的调试场景加载 GDT 后执行一条跳转指令jmp dword SELECTOR_KERNEL_CS:label然后 Bochs 立刻报CPU exception。原因通常是你手动填的段描述符中的基地址写错了或者段界限和实际的段大小不匹配。此时在 Bochs 调试器里查看gdtr寄存器和对应内存就能定位问题。这种“寄存器一级的排错经验”是任何虚拟化实验都替代不了的。4.3 阶段三中断、时钟与任务切换进入第四、五章后系统的“活”感就出来了。8259A 中断控制器负责把内外中断翻译成 CPU 能识别的向量号时钟中断每 10 毫秒触发一次在中断处理函数中切换任务。这段代码里你会看到汇编和 C 的“跨界合作”用汇编宏或asm段保存/恢复现场通用寄存器、段寄存器、栈指针。中断服务程序结束后执行iretd返回。任务结构体中保存了tss、内核栈指针、以及各个寄存器的初始值。光盘里process.c中的schedule()函数和时钟中断配合实现了一个简单的轮转调度器。它的代码量不大但你把断点设在切换点逐次观察current和next两个任务指针怎么交换才真正明白“上下文切换”就是换栈 恢复寄存器。这个印象比任何图表的说服力都强。这个阶段里我特别推荐你把书里的clock_handler修改一下比如让时钟中断每触发 5 次才切换一次任务亲眼看多任务系统的“节奏感”被改变。这种小实验会让你对操作系统的时间管理建立直觉而不是死记时间片长短。4.4 阶段四文件系统与系统调用到书的最后部分你会实现一个极简的文件系统以及让用户进程通过中断门调用内核服务的机制。这大概是整本书最能体现操作系统的“完整性”的地方。因为至此内核不再是打印一些字符和来回切换进程而是能真正通过磁盘介质保存数据、在用户模式和内核模式之间穿梭。光盘里的代码在这个阶段往往变得密集一些.c文件长度超过 500 行。我的建议是不要试图一次看完先把fs.c里的“超级块—目录项—文件分配”抽象整理成一张草图再对照代码看每一步操作的是哪个数据结构。至于系统调用重点看sys_call是通过哪个中断向量号引入内核的又是如何在syscall.c中分发的。到这一步你再回头看最开始那个 512 字节的引导扇区会有一种“当初那么小的一颗种子最后长成了这么大的树”的感觉。操作系统所有核心模块你已经亲手经历过一遍这个层面的理解深度单独靠看书是学不出来的。5. 随书光盘实践中的典型问题排查这部分内容是我这些年带朋友、带学员跑这套代码时踩过的坑集锦。遇到问题不要慌绝大多数都是环境问题和操作系统原理关系不大。5.1 光盘读不出来或找不到代码光盘是当年的介质现在的笔记本光驱越来越少见拿到数据后的第一件事建议先把整个光盘内容复制到一个目录中并做一次完整性检查。时间太久的光盘可能会因为物理损坏或反射层老化而读不出内容。这种情况没法硬修只能靠网上能找到的随书源码镜像或者从同学那拷贝一份。一个比较靠谱的办法把光盘内容做成 ISO 镜像再挂载到虚拟机中读取数据dd if/dev/cdrom oforange.iso bs2048 mkdir /mnt/orange sudo mount -o loop orange.iso /mnt/orange这样即使光盘有轻微磨损也更容易反复尝试读出数据。拿到后的内容结构、代码行号如果和书不一致多半是版本问题要优先弄清楚这是第几版对应的代码因为书在后续印刷中修过不少 bug。5.2 编译报错GCC 版本引发的问题老代码拿到新工具链上最常见的错误有以下几类错误现象常见原因解决办法undefined reference to __alloca缺少-fno-stack-protector或栈相关选项在 Makefile 里加上-fno-stack-protectorincompatible pointer type警告函数指针类型隐式转换增加-fpermissive或手动强转错误未知的操作码NASM 语法新旧版本差异调整指令写法比如[mem]和mem的使用方式.code16不受支持老代码里没加.code16在汇编入口处显式加.code16ld: 无法链接脚本输出格式错误链接脚本里指定了旧格式检查OUTPUT_FORMAT改为elf32-i386这些问题的本质是工具链在演进。遇到后我的建议是从 Makefile 的编译选项入手而不是去改代码逻辑。把编译选项改成“最朴素的 32 位编译”往往就能解决gcc -m32 -c -fno-builtin -fno-stack-protector -nostdinc -I./include -o kernel.o kernel.c ld -m elf_i386 -Ttext 0x30400 -o kernel.bin kernel.o start.o5.3 Bochs 启动后黑屏、卡住或直接退出这类问题排查我的经验是先看bochsout.txt的日志再逐步缩小范围如果日志报cannot open a.img那就是镜像路径不对检查相对路径是不是在bochsrc所在目录下。如果日志停在booting from floppy...之后就黑屏通常是引导扇区的魔数0x55AA没写好或者镜像的引导扇区位置偏移不对。如果你看到PANIC: Legacy memory size exceeds 1MB多半是 Bochs 配置里megs: 32和真实硬件地址空间有冲突可用megs: 16试试。另外用-q参数启动时Bochs 默认从配置文件加载磁盘如果配置文件里写的是相对路径而你在另一个目录里执行命令也会出现“找不到文件”的问题。最简单的做法是把bochsrc.txt和所有镜像放在同一目录然后在那里执行bochs -q -f bochsrc.txt。5.4 断点与调试用 Bochs 调试器观察内核Bochs 调试器是这本书的隐形宝藏。跟着代码实验时别只会run要学会在关键位置下断点。比如想看引导扇区加载到0x7C00后发生了什么break 0x7C00 continue断点命中后可以单步执行step查看寄存器info cpu查看内存x /10bx 0x7C00甚至查看栈print-stack。我个人的习惯是在进入保护模式的jmp指令处设一个断点如果跳转后直接exception就把 GDT 描述符的内容打出来看。这种现场观察能力能让你少掉很多头发。还有个小技巧如果你想跟踪内核某个 C 函数是在哪一行出错可以在 Bochs 调试器里先break到那个函数的入口地址然后对照nasm或objdump反汇编文件按行号定位。虽然不像现代 IDE 那样丝滑但足够用而且会让你对“程序入口地址”这个概念有更深的肌肉记忆。5.5 代码量变大后文件系统镜像怎么更新实验做到文件系统那几章需要往软盘镜像里放入用户程序。这时候你会发现直接修改a.img并不容易因为镜像格式是 FAT12 变种或书自定义的格式。光盘里的tools/目录通常带有镜像写入工具可以按 README 操作。我踩过的坑是在 Linux 下挂载 FAT12 镜像后写入文件看似成功但 Bochs 启动后却读不到文件。原因是写入时没有同步刷盘或者镜像的引导扇区被挂载工具改写了引导参数。解决方法是不要通过宿主机挂载修改而是用书中的用户程序编译后直接 dd 到指定扇区或者使用那套“往镜像中写文件”的专用工具并且写完后重新校验镜像的引导扇区是否还满足书中的格式要求。这不是 Linux 的问题而是自定义镜像对格式的校验非常严格。6. 进阶方向光盘跑完之后还能做什么把光盘里的代码完整跑通你已经站在了所有“操作系统理论课满分但从未写过内核”的同学前面。这时候我建议你思考一个问题把别人的操作系统做成自己的从哪里入手第一启动顺序完全一致但把 Fat12 文件系统换成 FAT32 或扩展分区格式。难度不小需要你来设计多个扇区的索引结构但这个过程能让你真正理解“文件系统为什么需要多层索引”。第二把原来交替运行的“任务”改成带优先级的调度器比如把进程结构体里加上静态优先级和动态优先级在时钟中断里实现优先数递减动态提升的机制。这样你对调度的理解就不再停留在“时间片轮转”的字面含义上。第三给内核加入一个简单的内存分配器。现在很多代码里用的还是直接对物理地址进行操作没有真正的kmalloc。你可以在mm.c里实现一个伙伴系统或 slab 分配器然后把内核栈、页表的分配都改到上面去。这件事非常有价值因为你在亲手实现所有现代操作系统“内存管理”的地基。不过我也要提醒一句这些扩展要建立在“理解当前代码”的基础上而不是机械地往里面加功能。每加一个功能前先画一张数据结构和调用关系的草图再动代码。一旦出现莫名其妙的问题先回滚到上一个可运行版本逐步排查。这个过程比把整个书抄一遍都要管用。7. 写在最后的一些体会操作系统的实验和自己平时写业务代码完全不一样。业务代码出错无非日志报个异常你修就是了操作系统的代码出错很多时候是直接一黑屏没有任何提示你需要从 CPU 的视角去审视每一个字节的流动。也正因如此这门手艺练出来的东西是那种“我可以造一个世界”的信心。我在带人跑这本书时最常告诉他们的是一件小事光盘里《Orange’s》的每个章节都像一块积木前一块搭好了后一块才放得上去。如果你在某章卡了三五天大概率不是笨而是前面某个概念偷懒跳过了。回头再看看那一段的描述符和寄存器往往一下就能打开局面。最后再分享一个我这几年都在用的小技巧每完成一章把你自己修改过的代码文件备份一份文件名加个日期比如process_v2_local_20240115.c。到整个系统做完时往回翻你会看到自己从只敢动注释到敢改调度算法再到敢重写内存管理的成长轨迹。这比任何证书都能说明问题。本文还有配套的精品资源点击获取
返回列表