ARTICLE DETAIL

资讯详情

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

Redhat 9.0 编译 Linux 0.01 与 Bochs 启动实战

Redhat 9.0 编译 Linux 0.01 与 Bochs 启动实战 简介基于Redhat 9.0平台实现Linux 0.01编译与运行的完整技术方案以单篇PDF文档形式呈现适合Linux初学者及操作系统爱好者学习参考。Linux 0.01是Linus早期编写、仅约9000行代码的系统具备多任务、多用户、进程/设备/内存管理和文件系统等基础能力但由于历史环境差异原始代码无法直接编译运行。文档围绕编译环境选择、GNU工具链与ATT语法汇编、源代码语法修改、动态链接系统调用及根文件系统搭建等关键环节展开说明并对比了Linux 0.01与0.11在文件系统实现上的异同读者可借此理解早期内核的启动流程与构建思路。资源共1个PDF文件大小211KB已有348人学习内容紧凑、要点明确对入门操作系统原理和内核分析具有实际参考价值。1. 为什么 2025 年还要在 Redhat 9.0 上编译 Linux 0.01看到这个标题第一反应是一个 2003 年的发行版配一个 1991 年的内核这套组合在今天还能不能跑通答案是能而且它的价值远比“考古”大。Linux 0.01 是 Linus 当年在 MINIX 上徒手写出来的第一版内核整个系统只有几十个 C 文件和汇编文件没有设备驱动、没有网络栈、没有虚拟内存代码量小到一个人能在几天内读完。把这样一份源码在 Redhat 9.0 上编译出来、再用虚拟机启动等于把《编译原理》的符号表、链接脚本和《操作系统》的启动流程两门课一次性做实。适合三类人被“现代内核太大读不完”劝退的学生、想给课程设计找一个有真东西的题目的人、以及单纯想看看 30 年前的操作系统到底长什么样的工程师。2. 在 Redhat 9.0 里准备“能做考古编译”的环境2.1 为什么选 Redhat 9.032 位工具链与 0.01 的兼容半径Linux 0.01 是 1991 年的代码它假设编译它的编译器是那个年代的产物。现代的 gcc 13、clang 17 也能编 C 语言但 0.01 的 Makefile 里写的编译选项早就被删掉了比如-m386在 gcc 4.x 就没了-traditional也在 gcc 4.3 之后被移除。硬要在 Ubuntu 24.04 上编光改 Makefile 就能改出一堆编译期异常。Redhat 9.0内核 2.4.20、gcc 3.2.2、glibc 2.3.2是最后一个“插上光盘就能编 0.01”的发行版。它的 gcc 3.2 还认得-traditional它的 binutils 还带as86和ld86——这两个是 16 位汇编器专门用来编 bootsect.s 和 setup.s现代发行版早就把它们从 binutils 里踢出去了。更关键的是RH9 默认是纯 32 位系统链接出来的内核镜像不会遇到“64 位 ld 拒绝链接 32 位目标文件”这种问题。环境项Redhat 9.0现代 CentOS / Ubuntugcc 版本3.2.2支持-traditional11-traditional已移除as86 / ld86在 bin86 包里光盘自带无需要自己找源码编默认位数32 位64 位编 32 位要装 multilib-m386选项不支持需改-marchi386不支持同样要改内核 2.4.20原生支持 RH9 硬件现代虚拟机里跑不了如果你在国产 Kylin V10 上编过 gcc 12应该能体会这种工具链年代错位的感觉老代码不是不能编而是它要的那套“方言”编译器不认了。RH9 的价值就是把这个错位感降到最低。2.2 虚拟机与系统安装要点RH9 不要装在物理机上驱动和硬件支持都是 2003 年的水平现代主板大概率起不来。用 VMware Workstation 或 QEMU/KVM 开一台虚拟机最省事。虚拟硬件要刻意做“老”网卡选 AMD PCnet 或 e1000RH9 内核自带驱动磁盘用 IDE 不要用 SCSI内存给 256MB 就够别给超过 1GB——2.4.20 内核的内存管理在高内存压力下容易出奇怪问题。安装时用 RH9 的安装光盘 ISO图形安装界面虽然是古董级的但流程和现代发行版一样分区、选时区、设 root 密码。关键是软件包选择这一步默认的“Workstation”装法不会带开发工具你要在包选择界面勾上“Development Tools”和“Kernel Development”否则后面连 make 都没有。如果安装时漏了进系统后挂载光盘补装也可以后面 2.3 节会说。装完系统先确认三件事uname -r显示 2.4.20、gcc --version显示 3.2.x、rpm -qa | grep bin86有输出。前两个不对说明系统装错版本了bin86 没装没关系下一节处理。2.3 补齐编译 0.01 需要的三个“老家伙”Linux 0.01 的构建链由三部分组成16 位实模式汇编器as86/ld86编 bootsect.s 和 setup.s、gcc编内核主体的 C 代码、ld链接 system 镜像。RH9 光盘自带 gcc但最小安装默认不带 bin86。# 挂载 RH9 第一张安装光盘 mount /dev/cdrom /mnt/cdrom # 找 bin86 的 rpm 包 ls /mnt/cdrom/RedHat/RPMS/ | grep bin86 # 安装版本号以光盘实际文件名为准 rpm -ivh /mnt/cdrom/RedHat/RPMS/bin86-*.i386.rpm # 验证 as86 --version ld86 --versionbin86 包提供的就是as86和ld86这两个工具专门生成 16 位代码。0.01 的 bootsect.s 和 setup.s 是实模式下的引导代码必须在启动早期用 BIOS 中断读磁盘、进入保护模式这段代码不能用 gcc 编因为 gcc 生成的是 32 位代码。装完后顺手确认一下/usr/bin/as86存在因为它不在 PATH 里的情况我也遇到过。2.4 拿到 0.01 源码并确认目录结构源码从 kernel.org 的 Historic 归档区下载文件名类似linux-0.01.tar.gz或者从 GitHub 上的镜像仓库拉。拿到后解压到/root/src下先花十分钟把目录结构看一遍这比直接 make 重要得多。mkdir -p /root/src cd /root/src tar xzf linux-0.01.tar.gz cd linux ls -l0.01 的源码树极短没有现代内核的arch/、drivers/、net/这些目录全部家当就这么几个目录/文件作用编译产物boot/bootsect.s引导扇区512 字节负责加载 setup 和 systemboot/bootsectboot/setup.s读系统信息、切到保护模式boot/setupboot/head.s内核入口初始化段寄存器和栈链接进 tools/systeminit/main.c内核 main 函数链接进 tools/systemkernel/进程调度、信号、系统调用入口链接进 tools/systemmm/内存管理链接进 tools/systemfs/文件系统MINIX链接进 tools/systemlib/内核态字符串函数链接进 tools/systeminclude/内核自身头文件仅编译期使用tools/build.c把三段拼接成最终镜像 Imagetools/build拿到源码后先别急着 make打开 Makefile 看一眼CFLAGS这一行你会看到-m386和-traditional这两个选项。-traditional在 gcc 3.2 里还认-m386已经要被废掉了这就是 3.3 节要处理的第一件事。3. 编译 Linux 0.01Makefile 读透再动手3.1 0.01 的 Makefile 在编什么三段式镜像0.01 的编译流程和现代内核完全不同。现代内核编译出来是一个 ELF 文件由 GRUB 之类的引导器加载0.01 是把自己整个“烧”进一张软盘的镜像。这个镜像是三段拼出来的顺序不能乱第一段是boot/bootsect编译产物严格 512 字节是软盘的第 0 扇区。它做的事情就是把 setup 和 system 从软盘读进内存然后跳过去。第二段是boot/setup负责读取内存大小、硬盘参数之类的 BIOS 信息然后切换到保护模式。第三段是tools/system这是整个内核的主体由 head.s、main.c、kernel/ 下所有模块链接而成运行在 32 位保护模式下。tools/build.c这个单独的 C 程序就是干拼接活的它把 bootsect 读到 512 字节处检查长度把 setup 读进来检查扇区数再把 system 接在后面最后写入一个叫Image的文件。所以整个构建流程有一个隐藏依赖必须先编译出tools/build才能生成最终镜像。还有个细节值得注意build.c 会把 setup 的扇区数写回 bootsect 的特定偏移处并在 bootsect 末尾写上启动设备号。这意味着 bootsect 不是“编译一次就固定了”它每次 build 时都会被修正。如果你手动修改了 bootsect.s 里表示 setup 长度的常量却没有走 build 流程启动时就会因为读不到足够多的扇区而卡死。3.2 最小编译命令与逐步执行在源码根目录直接执行 make正常情况下会一路编到底。但为了能看清每步在干什么我习惯把编译拆开来做cd /root/src/linux # 第一步编译 tools/build拼接工具 gcc -o tools/build tools/build.c # 第二步编译引导扇区16 位实模式代码只能用 as86 as86 -0 -a -o boot/bootsect.o boot/bootsect.s ld86 -0 -o boot/bootsect boot/bootsect.o # 第三步编译 setup同样是 16 位 as86 -0 -a -o boot/setup.o boot/setup.s ld86 -0 -o boot/setup boot/setup.o # 第四步编译内核主体这一步建议直接用 Makefile make第二步里as86的-0参数指定生成 8086 兼容的 16 位代码-a是让输出格式能被ld86识别。如果这一步报语法错误先确认你用真的是 bin86 的 as86有些机器上同时装了 GNU as两者的语法完全不同。第四步的make会处理所有的 C 文件和 head.s最后用ld链接出tools/system。全部执行完后检查一下产物ls -l boot/bootsect boot/setup tools/system Image file Imagebootsect必须是 512 字节整多一字节少一字节都不行这是软盘引导扇区的硬性大小。setup一般是 1 到 4 个扇区也就是 512 到 2048 字节。Image是最终镜像几十 KB 级别具体大小看你的编译结果。file输出应该是x86 boot sector之类如果不是这个格式说明 build 过程有问题。3.3 必须先动手改的三个兼容参数RH9 的 gcc 3.2 虽然对 0.01 已经很友好但-m386这个选项在 3.2 里已经被-marchi386取代了。不改的话make 会在编译第一个 C 文件时就报gcc: unrecognized option -m386。这是整个编译过程里最容易翻车的地方也是网上教程大量失败的原因——很多人卡在这里就放弃了其实只差一行替换。cd /root/src/linux # 备份原 Makefile cp Makefile Makefile.bak # 替换 -m386 为 -marchi386 sed -i s/-m386/-marchi386/g Makefile # 确认修改结果 grep -n march\|m386\|traditional Makefile修改后 Makefile 里的相关行应该是类似这样的形态CFLAGS -Wall -O -marchi386 -traditional -Iinclude CPP gcc -E -nostdinc -Iinclude AS86 as86 -0 -a LD86 ld86 -0这里每个参数都有它的来头-Wall开警告0.01 的代码在 gcc 3.2 下会有一堆“函数未声明”之类的警告不用管但不建议关掉因为有些警告其实指向了真正的链接问题-O是优化等级0.01 的代码里有些内嵌汇编依赖固定语义别用-O2否则优化器可能把__asm__重排到出问题-traditional保留它让 gcc 用 KR 风格解析代码0.01 的代码就是 KR 时代的写法-nostdinc -Iinclude告诉编译器不要用 glibc 的头文件只用 0.01 自带的 include/ 目录这样 RH9 的 glibc 版本再新也影响不到内核编译。-marchi386是唯一必须改的地方。-traditional在 gcc 3.2 上还在as86和ld86也都在所以 RH9 上只需要这一处替换。如果你是在更现代的发行版上编要改的地方会多得多这也是我开头说“RH9 是最后舒适区”的原因。3.4 编译产物核对Image 的尺寸与段位置改完 Makefile 重新 make成功后别急着跑先做两个核对。第一是看Image的头部bootsect 在软盘镜像里就是前 512 字节它的第一条指令是跳转指令。用xxd看前 16 个字节xxd Image | head -4正常的 bootsect 开头是一条短跳转指令十六进制是eb 08加上后面的 NOP90表示跳过后面的一段头部数据直接到执行代码。如果你看到的开头是全零或者其他随机数据说明 build 时 bootsect 没被正确写入大概率是 tools/build.c 没编对。第二是看 system 的入口。tools/system 被链接到内存地址 0 处理论上它的前几个字节是 head.s 的第一条指令。用objdump反汇编看一眼objdump -D -b binary -m i386 -M addr16,data16 tools/system | head -20你看到的第一条指令应该是设置段寄存器的 mov 指令而不是一堆无意义的字节。这一步能提前暴露链接脚本的问题免得后面启动时黑屏再回来查。我见过有人在这一步发现 system 居然是空的——因为 make 没跑完就中断了Image 里只有 bootsect 和 setup后面的 system 区域全是零。4. 把它跑起来Bochs 里的启动验证4.1 为什么用虚拟机而非 RH9 真机RH9 时代还有软驱但现在这台虚拟机里就算加了软驱设备宿主机也没有软盘能插进去。所以运行这一层最实际的做法是在 RH9 里编出 Image把它做成虚拟软盘镜像然后在宿主机上用 Bochs 或 QEMU 启动这个镜像。有人会问为什么不在 RH9 虚拟机里直接装个 Bochs可以但意义不大。RH9 上要编译那个年代的 Bochs依赖的老库和 X11 开发包又是一堆坑而且 Bochs 这种模拟器本身就是“宿主机上的一个普通程序”在 RH9 里跑和在宿主机里跑模拟出来的硬件环境完全一样。更关键的是Bochs 的调试器bochs-dbg能对 0.01 做单步调试这个能力在 RH9 和宿主机上都一样。所以我一般把 RH9 的编译产物拷到宿主机用宿主机上的 Bochs 来跑省掉 RH9 里再编一个 Bochs 的时间。4.2 写一份能启动老内核的 bochsrc 配置Bochs 的配置文件是启动时读取的0.01 需要的东西很朴素一块 1.44MB 软盘、16MB 内存、一个 VGA 显示器和 BIOS。现代 Bochs 版本2.6 以上的仍然能模拟这些老硬件。# 1. 先把 RH9 里编出的 Image 拷到宿主机 # 在 RH9 里执行IP 换成宿主机的 # scp /root/src/linux/Image user192.168.1.100:/tmp/ # 2. 在宿主机上创建 1.44MB 的虚拟软盘镜像 dd if/dev/zero oflinux0.01.img bs1024 count1440 # 3. 把 Image 写入虚拟软盘的最开头 dd ifImage oflinux0.01.img bs512 convnotrunc第二、三步的顺序不能反。如果先把 Image 写进文件再扩大文件大小Image 后面的内容其实没区别但如果你先把文件建小了再写bootsect 会被截断。bs512是软盘扇区大小convnotrunc保证 dd 只覆盖开头不截断整个文件。写入后可以用dd iflinux0.01.img bs512 count1 | xxd验证第一个扇区确实是 bootsect 的开头。bochsrc文件这样写# bochsrc for Linux 0.01 config_interface: textconfig memory: guest16, host32 romimage: file/usr/local/share/bochs/BIOS-bochs-latest vgaromimage: file/usr/local/share/bochs/VGABIOS-lgpl-latest floppya: 1_44linux0.01.img, statusinserted boot: a log: bochsout.txt mouse: enabled0 display_library: sdl关键参数只有三个floppya指定虚拟软盘镜像路径statusinserted表示软盘在驱动器里boot: a告诉 Bochs 从第一软驱启动漏了这条它会尝试从硬盘启动然后停在“Booting from Hard Disk”的提示上log指向一个输出文件启动日志会实时写到这个文件里排查问题比看屏幕有用得多。在冒号那一层 Bochs 会自动检测虚拟机的磁盘控制器执行上面命令时需保持在当前镜像和配置文件的同目录下推荐把linux0.01.img和bochsrc放在同一个文件夹里。4.3 启动日志里看什么从 BIOS 到 kernel main启动 Bochs 后屏幕上会先出现 BIOS 自检信息然后是 bootsect 打印的加载提示接着内核开始接管。整个启动过程分三个阶段每个阶段能在日志里看到对应的标志阶段屏幕/日志表现说明BIOS 启动Bochs 蓝色屏幕、Bochs BIOS 版本串模拟器在按真实 PC 的流程引导bootsectLoading system ...引导扇区正在把 setup 和 system 读入内存setup head屏幕切换进入 32 位模式段寄存器、栈、内存管理开始初始化mainLinux version 0.01及相关打印内核 C 代码的第一行输出Loading system ...这行字在 bootsect 里是硬编码的看到它就说明 512 字节的引导扇区被 BIOS 正确加载并执行了。再往后一点日志里出现Linux version 0.01开头的信息说明 setup 和 system 都被正确读入保护模式切换成功main 函数开始跑 C 代码。0.01 没有现代内核的 log 系统和设备模型它的输出就是往屏幕直接打印。启动到某个位置时打印会停下来这在没有根文件系统的情况下是正常的。0.01 的启动流程最后要挂载 MINIX 根文件系统我们的虚拟软盘里只有内核没有文件系统所以会看到挂载失败或者 panic 类信息。注意这不是编译失败而是“运行验证的正常终态”——你已经成功把 1991 年的内核从源码变成了可以在模拟器里启动的镜像。4.4 有了内核启动经验后怎么用 Bochs 调试器单步如果只是想看它跑起来上面几步就够了。但 0.01 真正值钱的地方是它的代码量小到可以逐行读、逐行调试。Bochs 的调试版bochs-dbg或者以 debugger 模式启动的版本允许你在任意内存地址下断点然后单步运行和 gdb 调试用户程序差不多。# 用 debugger 模式启动 Bochs bochs -dbg -f bochsrc进入调试器后主要的命令就几个b 0x90000在物理地址 0x90000 下断点setup 代码被加载到的位置0.01 的引导流程会把 setup 读到这个地址c继续运行s单步执行一条指令r查看寄存器状态。在 bootsect 打印Loading system ...之后敲c等它停在断点上然后用s一步一步走汇编指令会逐条显示。用这个方法可以把 setup.s 的每一行汇编和内存里的实际执行对应起来比对着源码空想要直观得多。5. 编译与运行的避坑清单5 个高频踩坑点5.1as86: command not found现象执行 make 后立刻报错shell 提示找不到 as86 命令。原因RH9 最小安装没有 bin86 包。0.01 的引导代码只能用 as86 编GNU as 帮不上忙。解决挂载 RH9 安装光盘安装bin86rpm。装完确认as86 --version能输出版本号。如果光盘丢了去网上找 rh9 时代的 bin86 rpm 包这是独立于 RH9 的小工具装上就能用。5.2gcc: unrecognized option -m386现象编译内核 C 文件时gcc 直接拒绝接受-m386选项并终止编译。原因gcc 3.2 的处理器型号选项已经改成-marchi386旧选项被移除了。0.01 的 Makefile 是 1991 年写的用的还是老名字。解决sed -i s/-m386/-marchi386/g Makefile。注意只替换 Makefile 里的 CFLAGS 相关行不要碰-traditional它还能用。5.3 Bochs 黑屏停在 “Booting from Hard Disk”现象Bochs 窗口不显示Loading system ...反而提示从硬盘启动失败。原因最常见的是boot: a没写进 bochsrc或者写成了boot: c。Bochs 默认从硬盘启动找不到引导扇区就报这个提示。解决确认 bochsrc 里有boot: a且floppya指向的镜像文件存在。再检查镜像文件本身是不是 1.44MB 整ls -l linux0.01.img应该显示 1474560 字节。如果大小不对重新执行dd if/dev/zero oflinux0.01.img bs1024 count1440再写一遍 Image。5.4 启动到一半卡死屏幕只有光标在闪现象Loading system ...打印之后屏幕变黑或光标闪烁没有内核输出。原因两个可能。一是 Image 里的 setup 扇区数不对bootsect 按照错误的扇区数去读盘读回的 setup 是残缺的二是 system 被strip过或者链接地址不对跳进去执行的第一条指令就是垃圾。第二种情况多发生在有人对系统镜像做了 strip 操作0.01 的 system 是裸二进制不能像 ELF 那样裁剪。解决回到 RH9 里重新 make确保用的就是 Makefile 直接产出的 Image不要对它做任何后处理。然后用 3.4 节的xxd方法核对 Image 头部是不是eb 08 90开头。Booch 的日志文件bochsout.txt会记录异常打开看最后几行如果有“unexpected exception”之类基本就是执行了非法指令。5.5 编译过程中ld86: cannot find -lc或链接失败现象链接 setup 或 bootsect 时报找不到库文件。原因RH9 的 ld86 默认会在标准路径找库但 0.01 根本不需要链接任何库是 Makefile 里的LD86变量缺了-0参数导致的。ld86 -0明确告诉链接器生成 16 位代码少了这个参数它会按 32 位方式链接自然找不到合适的库。解决检查 Makefile 里LD86 ld86 -0确认-0存在。如果你手动执行 ld86务必带-0。这个问题在 0.11、0.12 时代的编译教程里同样常见属于老内核编译的经典坑。5.6 启动日志里全是乱码现象屏幕上出现中文乱码或奇怪的字符看起来像内核在“说话”但完全读不懂。原因0.01 的输出走的是 BIOS 中断和直接写显存两条路。而在 Bochs 的某些显示模式下字符编码或光标位置会被 VGA 寄存器状态干扰。这不是内核坏了是显示层的小问题。解决在 bochsrc 里把vgaromimage路径指到正确的 VGABIOS 文件或在显示库上换用display_library: xLinux 宿主机重试。当然最可靠的信息来源还是bochsout.txt日志文件内核往屏幕写的字节它都有记录乱码不影响日志里的明文。6. 还能拿 0.01 做什么改资料串、反汇编验证启动闭环编译运行一旦通手上就有了一条从源码到可启动镜像的完整工具链。这时候最值得做的第一件事是验证“改源码 → 重新编译 → 重新启动”的闭环是否真的成立。找init/main.c里打印版本串的那一行把Linux version 0.01改成一个自己的标记比如Linux 0.01 compiled on RH9保存后重新make把新 Image 写进虚拟软盘再用 Bochs 启动。日志里出现你写的字符串时说明这一整套流程的每个环节都真实生效了——而不是某个教程里的镜像在运行。# 在 RH9 里重新编译并核对改动生效 cd /root/src/linux grep -n Linux version init/main.c # 修改后执行 make # 重新写入虚拟软盘假设镜像在宿主机的挂载目录里 dd ifImage oflinux0.01.img bs512 convnotrunc这个实验虽然简单但它建立的反馈循环是后面读内核源码的底气。接下来可以试着在 bootsect.s 的汇编里改打印字符观察引导扇区行为或者把 CFLAGS 里的-O改成-O0对比编译产物大小和执行行为的差异。这些都是零风险的改动每一步都能通过启动日志验证。另一个值得做的是反汇编核对。用objdump看看 tools/system 的入口再翻到init/main.c编译出来的汇编代码你会直观看到“C 代码 → 汇编 → 机器码”的编译原理实验流程一个函数调用对应几条 push 和 call一个全局变量对应内存中的固定位置。0.01 整个内核就几十 KBobjdump 的输出也不过几千行完全能打印出来对着源码读。我自己的习惯是每次改完代码先把 Image 的 MD5 记下来启动成功后再算一次确保跑的就是刚编出的新镜像。这种老掉牙的流水账做法救过我好几次——有回改完 main.c 忘了 make在 Bochs 里盯着旧内核看了十分钟。希望你不用像我一样踩这个坑。这套 RH9 0.01 的组合编译一次加启动一次熟练后十分钟能跑完整轮值得动手做一遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表