字数 6801,阅读大约需 35 分钟
搭建一个稳定可靠的 Linux 0.1x 实验环境,准备一次深入古老丛林的科学考察。你不仅需要合适的地图(源码),还需要专业的装备(工具链),更需要一位经验丰富的向导(调试系统)。本章将为你提供完整的环境搭建方案,让你能够在现代操作系统上与这个三十年前的内核进行对话。
我们将从源码获取开始,详细讲解如何在Ubuntu 16.04系统上构建完整的编译调试环境。这个过程中你需要理解它的原始设计,找到与现代工具兼容的方法,同时保持其历史原貌不被破坏。
官方原始的 Linux 0.11 和 Linux 0.12 版本无法在当前的Linux操作系统中直接编译运行。这两个诞生于 1990 年代初期的内核是专为当时的硬件环境和开发工具设计的,与现代系统存在根本性的兼容障碍。
当前 Linux 系统中的工具链和原始代码时期的工具链之间存在着很深的代沟。原始代码基于 GCC 1.40 和早期 C 标准编写,采用宽松的 K&R 语法风格。而当前Linux 系统中使用的 GCC 遵循严格的 C11 标准,对隐式函数声明、类型转换和安全检查都更加严格,导致编译时大量报错。
为了不让读者陷入解决历史代码与现代工具的兼容性问题中,开启繁琐的兼容性调试过程,我将 GitHub 上已经修复兼容性的维护版本进行了整理,并通过本文分享出来,从而帮助读者专注于欣赏 Linux 0.1x 内核的设计思想,而不是被繁琐的编译错误困扰。
Linux内核官网保存的历史源码档案,构成了操作系统演进的完整记录。从1991年的初始版本到各个里程碑 release,这样一个档案库为大家提供了绝佳的纵向研究素材,让我们能够清晰地追踪一个现代操作系统从雏形到成熟的全过程。

喜欢原汁原味的读者可以直接从 Linux 官网下载原版代码,原版代码能够帮助你理解最原始的设计决策和代码风格,也能直接反映 Linus 早期开发它的架构思路。
# 官方源码下载方法wget https://www.kernel.org/pub/linux/kernel/Historic/old-versions/linux-0.11.tar.gztar -xzf linux-0.11.tar.gzwget https://www.kernel.org/pub/linux/kernel/Historic/old-versions/linux-0.12.tar.gztar -xzf linux-0.12.tar.gz在搭建Linux 0.11和 0.12 实验环境的过程中,我系统性地尝试了 GitHub 上多个经过修复的版本,包括hukex、karottc等主流仓库。实践发现,这些版本虽各有优化,但均存在不同程度的问题。
我对这些代码库进行了横向对比分析,提取各版本的有效修复补丁,同时结合对早期GCC编译特性的研究,重新整合了一套完整的构建方案。最终完成了支持GDB完整调试的实验环境。
# 可运行、调试源码下载git clone https://github.com/hsujee/linux-0.1x.gitlinux-0.1x 仓库中包含 Linux 0.11 和 0.12 的源码,特别值得一提的是,0.12 版本的源码中有前辈高人添加的中文注释,这大大的降低了我们查看源码的难度,让我们在这里向前辈们致敬。
我将这两套 Linux 0.1x 源码的编译脚本进行了整合修改,因此它们的运行环境、编译以及调试方法完全一致,后续相关的环境配置,编译指令,调试方法完全适用于它们。这种一致性设计让你可以在不同版本间自由切换,比较它们之间的差异,从而更好的理解Linux早期的演进过程。
为了更加灵活方便的开启实验,建议使用虚拟环境,虚拟机中安装 Ubuntu 系统跟主机操作系统类型(Windows、Mac、Linux)和版本无关,并且当前主流的虚拟机软件都是全平台支持的,你可以根据当前使用的主机选择对应的虚拟机安装即可。
虚拟机推荐使用 VirtualBox,因为它是一款功能完善且完全免费开源的虚拟化软件。它完美支持Ubuntu Linux发行版,提供了完整的硬件虚拟化、网络配置和快照管理功能,能够稳定运行QEMU、GCC等传统工具链,最关键的是无需承担任何许可费用,是个人学习和项目开发的理想选择。
# Ubuntu 系统安装 VirtualBoxsudo apt install virtualbox# VirtualBox 官网https://www.virtualbox.org/# 清华源 VirtualBox 软件仓库https://mirrors.tuna.tsinghua.edu.cn/help/virtualbox/Ubuntu 系统的主机可使用 apt 安装 VirtualBox 虚拟机,其他系统请到官网或国内源的软件仓库下载安装即可,如果遇到虚拟机安装问题,请自行网络搜索解决。
在VirtualBox 虚拟机环境中,你可以在这个环境中随意实验,即使系统崩溃,也只需要恢复快照就能回到之前的状态。这种安全性对于内核学习尤为重要,因为在内核层面犯错误往往会导致系统无法启动。
选择 Ubuntu 16.04 作为编译环境,是基于其在工具链演进史中的独特定位。该版本搭载的 GCC 5.4、GDB 7.11 等组件恰好处于一个关键的“兼容性窗口期”,既包含了现代调试器和编译器的稳定功能,又尚未引入高版本中对传统代码更为严格的语法约束和安全机制。
相较于更新的 Ubuntu 版本, Ubuntu 16.04 默认的工具链即可满足 Linux 0.1x 源码的编译和调试,无需任何复杂的工具链降级或版本切换动作。
# Ubuntu 16.04 下载https://releases.ubuntu.com/16.04/# 阿里巴巴开源镜像站https://mirrors.aliyun.com/ubuntu-releases/16.04/在VirtualBox中安装Ubuntu系统时,建议分配至少20GB硬盘空间和2GB内存。虽然Linux 0.12本身只需要16MB内存就能运行,但我们的编译环境和调试工具需要更多资源。设置用户名和密码时,建议使用简单易记的组合,在后面的实验过程中会频繁进行权限操作。
注:关于 VirtualBox 虚拟机中安装 Ubuntu 系统的方法,网上有很多讲解的文章,大家可以通过网络搜索来学习。
在开始编译和调试 Linux 0.1x 代码前,首先需要安装和配置编译/调试工具链,这套工具链可以帮助你完成从源码到可执行系统再到调试分析的全过程。
# 安装编译调试工具链sudo apt-get updatesudo apt-get install qemu gdb make git bin86 vim tree libc6-dev-i386编译/调试工具说明
as86、ld86 等专用工具,用于编译实模式引导代码,处理 bootsect.s、setup.s 等汇编文件的编译和链接。你也可以在下面调试环境流程图中,对照这些工具所处的位置和作用。

Linux-0.1x 源码文件中提供了 Makefile 脚本,该脚本文件经过多位大佬的修复和改进,因此才可以在 Ubuntu 16.04 中进行编译运行,并兼容 GCC 4.3.2 版本。该文件中提供了丰富的编译选项,如图 2.1所示,你可以在 Linux-0.1x 目录下运行 make help 命令以查看这些编译选项的使用方法以及说明。

常用的编译选项说明
Image,文件系统位于源码根目录下。Image 镜像文件以及软盘文件。理解Makefile的结构对于深入学习源码很有帮助。让我们查看一个关键片段的源码:
# Makefile关键部分分析# 生成内核镜像的主要规则Image: boot/bootsect boot/setup tools/system build @echo "============= Build System Image =============" @$(BUILD) boot/bootsect boot/setup tools/system $(ROOT_DEV) > Image @sync @echo "System Image has been built successfully." @echo ""# 编译系统核心tools/system: boot/head.o init/main.o \ $(KERNEL_OBJS) $(MATH_OBJS) $(DRIVER_OBJS) $(LIBS) @$(LD) $(LDFLAGS) boot/head.o init/main.o \ $(KERNEL_OBJS) \ $(DRIVER_OBJS) \ $(MATH_OBJS) \ -o tools/system -lgcc @nm tools/system | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | sort > System.map这段代码展示了Linux构建系统的核心逻辑。首先编译各个模块,然后将它们链接成完整的系统,最后生成包含引导扇区、设置代码和内核主体的完整镜像。这个过程就像组装一台精密的机器——每个零件单独制造,然后按照设计图装配成整机。
如图 2.2 所示,运行 make 编译 Linux-0.11 源码,你会看到终端输出详细的编译信息。
首先是引导扇区代码的编译,这部分使用as86汇编器,生成512字节的引导扇区。接下来是setup.s的编译,这段代码负责从实模式切换到保护模式。然后是最核心的部分——内核主体的编译,包括进程管理、内存管理、文件系统等各个模块。
最后生成 Image 镜像文件,使用file命令行工具你会看到Image文件大约144KB大小,file命令会显示它是"DOS/MBR boot sector"格式。这意味着这个文件不仅包含内核代码,还包含了完整的主引导记录,可以直接被BIOS识别和引导。

编译生成镜像文件之后,执行 make start 命令在 QEMU 上运行 Linux 0.11 内核,如果一切顺利你将看到图 2.3 所示的 Linux 0.11 内核启动窗口,至此恭喜你成功运行了一个三十年前的操作系统内核!

编译运行流程如下图所示:

GDB 远端调试是一种允许开发者在本地 GDB 客户端调试运行在远程目标机(或模拟器)上程序的机制。其核心原理如下,即在目标系统(如:QEMU)上运行一个调试桩(GDB Server 或 QEMU 的调试后端),它负责直接控制被调试程序的执行、处理断点和异常、访问内存和寄存器。而在开发主机上运行的 GDB 客户端则通过 TCP/IP 或串口等通信链路与调试桩建立连接,发送调试命令并接收目标程序的状态信息。
具体的调试过程始于在目标环境启动调试服务。例如,在 QEMU 中使用 -s -S 参数启动模拟器,-s 表示在 1234 端口开启 GDB 调试服务,-S 则用于暂停(挂起) CPU 初始化,并等待调试器连接,Linux 0.01 中的 make debug 调试指令如下所示,你可以在 Makefile 脚本文件中找到这条指令。
debug: @echo $(OS) @qemu-system-i386 -m 16M -boot a -fda Image -hda $(HDA_IMG) \ -s -S正确执行 make debug 指令之后,QEMU 将被启动,但是由于 CPU 被挂起,因此你只能看到全黑的 QEMU 运行窗口。随后,在开发主机启动 GDB 并执行建立连接,此时 GDB 客户端获取目标系统的初始状态(如架构、寄存器值),但尚未加载符号信息。
你需要手动加载带有调试符号的可执行文件,从而将源代码与机器指令关联起来。随后便可设置断点、单步执行、查看变量内存等一系列调试动作。
下面列出了常用的 GDB 调试命令,关于更多的 GDB 调试知识,请查看我写的《Linux 调试技术》系列文章。
# 在另一个终端中启动GDBcd linux-0.1x/linux-0.12gdb# 在GDB中执行以下命令(gdb) # 连接到远程调试目标(gdb) target remote localhost:1234(gdb) # 设置当前目录为源码目录(gdb) directory .(gdb) # 加载符号表文件(gdb) file tools/system(gdb) # 设置成i8086模式,用来调试16位实模式代码(gdb) set architecture i8086# 设置成 i386 运行模式,用来调试 32 位保护模式代码(gdb) set architecture i386# 将汇编显示成 INTEL 格式,默认为 AT&T 格式(gdb) set disassembly-flavor intel(gdb) set disassembly-flavor att# 打开 regs (寄存器)界面,方便观察寄存器的值(gdb) layout regs(gdb) # 在引导扇区入口设置断点(gdb) b *0x7c00(gdb) # 继续运行,直到断点(gdb) c现在,内核会在引导扇区入口暂停。你可以使用各种GDB命令来检查系统状态。让我解释一下为什么要切换架构模式:当CPU从实模式切换到保护模式时,它的工作方式发生了根本变化。
在实模式下,CPU使用16位寄存器和分段内存模型;在保护模式下,使用32位寄存器和分页内存管理。GDB需要知道当前处于哪种模式,才能正确解释寄存器和内存内容。
注:GDB 会将调试命令封装成特定协议(如 RSP 协议)报文发送给调试桩,调试桩执行具体操作后返回结果。这种架构的优势在于将符号处理和用户交互等复杂任务留在资源丰富的开发主机,而目标系统只需实现核心的调试控制功能,极大降低了对目标环境资源的要求。
GDB 中的 layout 命令提供了强大的文本用户界面(TUI)模式,能将终端窗口分割成多个面板,同时实时显示源代码、汇编指令、寄存器状态和调试命令窗口。这种可视化布局让开发者在单步执行或断点暂停时,能同步查看代码执行流、对应的机器指令变化以及寄存器数值的更新,极大提升了调试效率,特别适合在操作系统内核调试等复杂场景中直观地跟踪程序执行状态和硬件状态变化。
图 2.7 中展示了在 Linux 0.11 调试中启用了 layout 寄存器信息显示窗口。

下面列出 layout 命令的使用方法。
Usage: layout prev | next | <layout_name># 显示源代码layout src# 显示汇编代码layout asm# 显示寄存器信息layout regs# 快捷键,用来退出 TUI 模式Ctrl + X, A当启用layout后,你的终端会分成几个区域。上部显示寄存器状态,中间显示源代码或汇编代码,下部是命令输入区域。当前执行的代码行会被高亮显示,寄存器值的变化会实时更新。这种即时反馈让你能够直观地理解代码执行对系统状态的影响。
内核符号就像建筑物的结构图,告诉你每个函数、每个变量的位置和用途。在内核调试中,正确加载和查看符号信息是理解系统行为的基础。
# 查看内核符号##############################################################(gdb) file tools/system # 加载内核符号表文件(gdb) info functions # 查看所有函数(gdb) list main # 查看main函数源码(gdb) disassemble # 查看汇编代码(gdb) info variables # 查看全局变量特别重要的是file toos/system命令。这个命令加载内核的符号表,将内存地址映射到具体的函数名和变量名。没有符号表,调试就像在陌生的城市没有地图——你知道自己在某个位置,但不知道这是什么地方,周围有什么建筑。
内存和寄存器是CPU工作的舞台。查看它们的状态就像检查机器的内部工作情况。
# 查看内存##############################################################(gdb) x/20xb 0x100000 # 查看物理内存(16进制字节)(gdb) x/10i main # 反汇编main函数(gdb) x/i $pc # 查看pc寄存器指向的指令(gdb) print init_task # 查看初始任务结构(gdb) info registers # 查看寄存器信息(gdb) x/20x $sp # 查看栈内容x命令(examine的缩写)是最强大的内存查看工具。它的格式是x/[数量][格式][单位] 地址。例如x/20xb 0x100000查看从0x100000开始的20个字节,以16进制显示。在内核调试中,这个命令经常用来查看数据结构的内容,比如任务控制块、内存页表、文件系统inode等。
Linux是多任务操作系统,进程管理是核心功能之一。调试进程相关代码需要特殊的命令和技巧。
# 进程调试##############################################################(gdb) break sys_fork # 在fork系统调用设断点(gdb) watch current # 监视当前进程指针(gdb) info frame # 查看frame信息(gdb) info threads # 查看线程信息(现代概念,0.12中实际是进程)(gdb) backtrace full # 查看完整的调用堆栈watch current命令特别有用。current是Linux内核中指向当前运行任务的指针。监视这个变量,你就能知道每次任务切换发生在什么时候。就像十字路口的摄像头,能够记录每辆车的通过时间。
在内核调试中,有些位置特别值得设置断点。这些位置通常是关键函数的入口,或者是重要的状态转换点。
# Linux 0.12 关键断点##############################################################break start_kernel # 内核正式启动break main # 主初始化函数break sys_fork # 进程创建break do_execve # 程序执行break do_exit # 进程退出break schedule # 进程调度break trap_init # 中断初始化break mem_init # 内存初始化设置这些断点后,你可以逐步跟踪内核的初始化过程。比如,在start_kernel处暂停,然后单步执行,观察内存如何初始化、中断如何设置、进程表如何建立。这个过程就像观察一个复杂机器的启动过程,看着各个部件依次开始工作。
如下错误是GDB调试Linux 0.11时常见的问题。主要原因是现代GDB与老版本内核在调试协议上的不兼容。具体来说,当CPU从实模式切换到保护模式时,寄存器状态信息的长度会发生变化。在实模式下,GDB期望接收312字节的寄存器信息;而在保护模式下,实际收到的是608字节。这种不一致导致协议错误。
Remote 'g' packet reply is too long (expected 312 bytes, got 608 bytes): 可以尝试通过使用下面提供的方法,设置 gdb 执行模式进行避免该错误的发生。
# 在GDB中手动执行set architecture i8086break *0x7c00continue# 出现错误后set architecture i386continue除了GDB协议错误,在环境搭建和调试过程中还可能遇到其他问题。让我分享一些常见问题及其解决方案:
问题1:编译时出现"implicit declaration"警告
# 这是现代GCC对K&R C语法的严格检查导致的# 解决方案是在Makefile中添加兼容性标志# 或者在源码中添加函数声明# 查看具体是哪个文件的问题,然后相应处理问题2:QEMU启动失败,找不到BIOS
# 安装seabios包sudo apt install seabios# 或者指定BIOS路径qemu-system-i386 -L /usr/share/qemu -m 16M -fda Image问题3:32位编译失败
# 确保安装了32位开发库sudo apt install libc6-dev-i386 gcc-multilib g++-multilib问题4:make命令找不到
# 安装build-essential包sudo apt install build-essential每个问题的出现都是学习的机会。通过解决这些问题,你不仅完成了环境搭建,还深入理解了工具链的工作原理。这就像学习修车——每次故障排除都让你对汽车结构有更深的理解。
现在,让我们通过一个完整的实验来巩固本章所学内容。这个实验将带你跟踪Linux 0.12从加电到进入内核的完整引导过程。
理解x86计算机的启动流程,从BIOS到引导扇区,再到内核初始化。
# 终端1:启动QEMU调试服务器cd linux-0.1x/linux-0.12make debug# 终端2:启动GDBgdb -q(gdb) target remote localhost:1234(gdb) file tools/system(gdb) set architecture i8086(gdb) b *0x7c00(gdb) c引导扇区代码位于boot/bootsect.s。让我们查看它的关键部分:
! bootsect.s关键代码分析.globl begtext, begdata, begbss, endtext, enddata, endbss.textbegtext:.databegdata:.bssbegbss:.textBOOTSEG = 0x07c0 ! 引导扇区加载地址INITSEG = 0x9000 ! 将移动到这里的地址entry startstart: mov ax,#BOOTSEG ! 设置DS寄存器 mov ds,ax mov ax,#INITSEG ! 设置ES寄存器 mov es,ax mov cx,#256 ! 移动256字(512字节) sub si,si ! 源索引清零 sub di,di ! 目标索引清零 rep ! 重复执行 movw ! 移动字 jmpi go,INITSEG ! 跳转到新位置在GDB中,我们可以单步执行这些指令,观察寄存器的变化:
(gdb) stepi ! 单步执行一条指令(gdb) info registers ! 查看寄存器状态(gdb) x/10i $pc ! 查看接下来要执行的指令当引导扇区完成它的工作后,会加载setup.s并跳转到0x90200执行。让我们在这个位置设置断点:
(gdb) b *0x90200(gdb) csetup.s负责收集硬件信息并切换到保护模式。这是启动过程中最复杂的部分之一。
保护模式切换的代码值得仔细研究。关键指令包括设置GDTR、开启A20地址线、设置CR0寄存器等。在GDB中跟踪这个过程,你会看到CPU工作模式的根本变化。
最后,内核开始执行startup_32函数,这是保护模式下的入口点。设置断点并跟踪:
(gdb) set architecture i386(gdb) b startup_32(gdb) c在实验过程中,建议记录以下关键信息:
通过这个实验,你将获得对计算机启动过程的直观理解。这种理解是深入学习操作系统的基础——你知道系统如何来到main()函数,就能更好理解main()函数中发生了什么。
在本章中,我们完成了Linux 0.12实验环境的完整搭建。这个过程不仅仅是安装软件,更是一次深入理解计算机系统的工作方式的学习之旅。
我们从获取源码开始,了解了原始代码与现代环境的兼容性挑战,以及如何通过修复版本解决这些问题。然后,我们搭建了虚拟化环境,选择了合适的工具链,理解了每个工具的作用。接着,我们编译并运行了内核,看到了三十年前的操作系统在现代硬件上复活。最后,我们掌握了调试技巧,学会了如何深入内核内部,观察它的每一个动作。

现在,你已经拥有了一个完整的内核学习平台。在接下来的章节中,我们将使用这个平台深入探索Linux的各个子系统。你会看到进程如何创建和调度,内存如何分配和管理,文件如何存储和访问。每一章都将结合理论分析和实际操作,让你在理解概念的同时,亲手验证这些概念的具体实现。
记住,环境搭建不是一次性的任务。随着学习的深入,你可能会需要调整配置,添加新的工具,甚至修改内核代码本身。这种持续优化和适应的过程,正是系统工程师日常工作的写照。
技术之路,始于环境;深入理解,终于实践。 现在,你的 Linux-0.1x 实验室已经准备就绪,让我们在下一章开始真正的内核探索之旅。
“千里之行,始于足下。”——老子《道德经》