☰
VirtualBox搭建FreeDOS+DJGPP复古开发环境:安装配置与避坑指南
2026/10/1 23:49:03 网站建设 项目流程

前阵子翻旧书柜,翻出一张十几年前的光盘,里面是一份用 Borland C++ 写的俄罗斯方块源码。Windows 上试了一圈,装个现代 IDE 太肿,用编辑器打开又全是编码乱码。后来灵机一动,与其在现系统里模拟老 DOS 编译器,不如直接在虚拟机里装一套原汁原味的 DOS 系统,再把 DJGPP 这个老牌开源 C/C++ 编译器塞进去。折腾下来,不仅把老代码跑通了,还发现一套很舒服的复古开发工作流。这篇文章就完整拆开讲一讲,FreeDOS + DJGPP 在 VirtualBox 里的安装、配置和避坑过程。

1. 为什么还在折腾 DOS 下的编译器:场景、价值与这套环境的边界

先说个反直觉的结论:DOS 下的 C 语言编译环境,到今天不仅没死透,反而在某些场景里比现代工具链更顺手。比如你想研究几十年前的游戏程序、复刻 Doom 早期的开发方式、给学生演示 640KB 内存限制下的编程技巧,或者单纯想把手头的老源码在接近原始的环境里重新编译一遍,一套 DOS + DJGPP 的环境比装一台奔腾 3 老机器要省心得多。

DJGPP 的来历值得啰嗦几句。它是 DJ Delorie 维护的 GNU GCC 移植版,全称是 DJ's GNU Programming Platform,专门运行在 Intel 80386 及以上的 DOS 环境中。当年 id Software 开发 Doom 用的就是 DJGPP,Wolfenstein 3D 的后续作品也有它的身影。这意味着这套工具链的真实性能是被游戏工业验证过的,不是玩具。

它能突破 DOS 自身 640KB 常规内存的限制,靠的是 DPMI 机制。DOS 程序默认只能访问 1MB 地址空间,其中还只有 640KB 给应用使用,这在今天听起来像笑话,但在当年是硬约束。DJGPP 生成的是 32 位保护模式程序,通过 DPMI 服务访问所有可用内存,编译大型项目时不会三下两下就 Out of memory。这一点比 Turbo C 的 16 位实模式体验强太多。

FreeDOS 这边,它是一个还在持续维护的开源 DOS 实现,ISO 镜像在官网直接能下,装进虚拟机完全合法、没有老系统那些安装盘找不到的问题。加上 VirtualBox 的快照功能,环境一旦搭好,随便折腾都不怕搞坏。

这套环境的适用边界我也说清楚:它适合命令行程序、学习底层原理、跑老代码,不适合搞 Windows GUI、桌面程序或者对图形界面有要求的现代项目。开个很小的模拟器写个俄罗斯方块、做做算法题、写写汇编实验,这才是它的主场。

下表是 DJGPP 和另外两个常见 DOS 编译器的对比,方便你根据需求选型:

编译器运行模式内存访问现代 C 标准许可与现状
DJGPP32 位保护模式突破 640KB,依赖 DPMI支持 C99/C11 大部分GPL,持续维护
Turbo C++ 3.016 位实模式受限 640KB老式 C/C++,兼容性差停止维护
Open Watcom16/32 位都有支持 DOS/Windows支持部分 C99开源但配置复杂

2. VirtualBox 虚拟机参数:让 FreeDOS 顺畅跑起来的关键项

VirtualBox 的版本选择是个容易被忽略的细节。如果你用的是比较新、支持硬件虚拟化的机器,直接装当前最新版本就行;如果你手里的是老电脑或 Windows XP/2003 这类老系统,VirtualBox 5.2.44 反而是个非常稳妥的版本。它是 5.2 系列的最后一个小版本,对老 CPU 兼容性好,对 DOS 这种老系统的支持也完善。新版本里如果创建虚拟机时动了 UEFI 设置的默认项,反而可能引导不了。

新建虚拟机时,类型选择上有个很关键的坑。VirtualBox 向导里的操作系统类型列表里有 DOS 这个选项,你可以直接选它。但更重要的是创建完成之后要进“设置”检查存储控制器。默认情况下 VirtualBox 新建虚拟机时会给一块 SATA 控制器,而 FreeDOS 内核不带 SATA 驱动,你在 SATA 接口上挂硬盘,安装程序可能根本看不到硬盘,就算装完了也可能无法引导。正确做法是删掉默认的 SATA 控制器,换成 IDE 控制器(PIIX4 型号),再把虚拟硬盘和光驱都挂到 IDE 控制器上。

内存我建议给 512MB,不用更多。原因有两个:一是 DOS 下不管是 FreeDOS 内核还是 DJGPP 工具,本身对内存的需求很小,512MB 编译几个小项目绰绰有余;二是 DPMI 在分配内存时如果系统资源太复杂,反而容易引发一些兼容性诡异问题。硬盘给 1GB 就够用,用 VDI 格式、动态分配。后面要装 DJGPP 全套、写代码、存点小项目,1GB 空间完全够,做好 FAT16 分区时也不会有 2GB 以上分区的麻烦。

虚拟机的其他设置按“极简主义”来。网卡不需要,这台机器又不联网,去掉网卡反而少很多麻烦;声卡如果不需要就别加,省得 SB16 IRQ 和 DMA 冲突;显存给 16MB 就够,3D 加速完全用不上,显卡类型保持 VGA 兼容模式就行;USB 控制器、共享文件夹、剪贴板共享这些统统关掉,DOS 时代没有这些概念。

创建完成后,还要在“设置 - 系统 - 主板”确认一下“启用 EFI(只针对某些操作系统)”这个勾选框没被选中。FreeDOS 需要的是传统 BIOS 引导,UEFI 启动方式它不认。这个细节在 5.2.44 和 7.x 版本里都有,务必检查。

3. FreeDOS 系统安装与磁盘引导:分区、格式化和启动配置

去 FreeDOS 官网下载最新的完整版安装 ISO,注意选 Full CD-ROM 版本,里边带了安装程序、常用 DOS 工具和帮助文档,对新手友好。下载完挂载到刚才建好的虚拟光驱,启动虚拟机,屏幕上会出现启动菜单。一般选第一项直接进安装程序,然后选择 Install FreeDOS to hard disk。

这里有个很容易犹豫的问题:分区到底怎么分。安装程序的 FDISK 环节,你要在主分区表里创建一个主分区并把它激活(Set active),分区大小建议控制在 2GB 以内。为什么是 2GB?因为小于等于 2GB 的分区 FDISK 会默认格式化成 FAT16,FAT16 和传统 DOS 引导兼容性最好。如果分区大于 2GB,FDISK 会问你转成 FAT32,FAT32 虽然 FreeDOS 也支持,但和 DJGPP、某些老工具的兼容性偶尔会冒小问题,没必要冒险。创建好主分区、激活、格式化完成之后,安装程序会把 FreeDOS 系统文件复制到 C 盘,写完 MBR 引导记录,重启就能从硬盘启动。

第一次启动进入 FreeDOS 后,你会看到 FDCONFIG.SYS 里的启动菜单,这是 FreeDOS 的 CONFIG.SYS 等效物。默认配置一般会加载 HIMEMX.EXE 和 JEMM386.EXE,前者是 FreeDOS 的扩展内存管理器,后者提供 EMS 和 DPMI。这里留个心理准备:JEMM386 提供的 DPMI 服务和 DJGPP 的 CWSDPMI 偶尔会打架,后面编译如果遇到奇怪崩溃,优先怀疑它,把 FDCONFIG.SYS 里的 JEMM386 那行注释掉重启就行。

安装完成后建议做一个快照,备份一个干净系统状态。之后配置编译器时如果折腾乱了,一个快照回滚比重新安装省事太多。Edit 编辑器是 FreeDOS 自带的,后面写代码和改配置文件都用它,语法高亮虽然别指望,但应付小文件够了。

启动配置里还有个小经验:如果安装完以后重启出现 Boot failed 或者 could not find the FreeDOS kernel 之类提示,十有八九是分区没有激活。回到安装环境重新跑 FDISK,把主分区标记成 Active 即可。

4. 宿主机到虚拟机的文件传输:四种方案与最推荐的做法

DOS 虚拟机最大的使用痛点之一就是文件怎么传进去。总不能对着键盘把几百 KB 的编译器一行行敲吧。我把实际试过的方案按可靠性排个序,你照着选。

先说我认为最稳的方案:做一张 ISO 工具盘。在宿主机上建一个放文件的目录,把 DJGPP 的 ZIP 包、写好的源码、需要的工具都放进去,然后用命令行工具把这个目录制作成 ISO 镜像,再挂载到虚拟机的光驱里。Linux 下用 genisoimage 一行搞定,Windows 下可以用 UltraISO 或者 VirtualBox 自带的虚拟光驱配合 WinISO 之类的工具。FreeDOS 启动后,光驱盘的盘符一般是 D:,除非你有多块硬盘或光驱,否则不会认错。要注意的是,FreeDOS 系统对光驱的访问需要加载光驱驱动,安装程序在装系统时如果自动配置了,那进系统就能直接读盘;如果没有,就得在 FDCONFIG.SYS 里加载 XCDROM.SYS,再配合 SHSUCDX 分配一个盘符。这个方案的好处是 ISO 镜像内容不会因为 DOS 不认识 NTFS 或 ext4 而丢失,只要 ISO 文件名符合 8.3 短文件名规范,DOS 就能读。

第二种方案是软盘镜像。在宿主机上创建一个 1.44MB 的虚拟软盘镜像文件,用 mtools 或写镜像工具把文件放进去,再挂载为虚拟机的软驱。这个方法在 DOS 里的兼容性无敌,但缺点是容量实在太小,适合偶尔传个几百 KB 的小程序,塞 DJGPP 安装包完全不可能。

第三种方案是直接在宿主机上操作虚拟硬盘镜像。先用 VBoxManage clonehd 把 VDI 格式转换成 raw 镜像,然后用 mtools 工具把文件写进镜像里的分区,再转回 VDI 格式挂载进虚拟机。这个方法的效率最高,十几秒就能把整个 DJGPP 目录全部塞进去,不用启动虚拟机,但操作步骤里有个容易踩的坑:mtools 默认从整块磁盘镜像的文件头开始算分区,而 raw 镜像是整块磁盘,你需要先用 fdisk 之类工具查一下第一个分区在镜像中的偏移量,然后在 mtools 的配置里设置 offset 参数。格式又比较苛刻,转换工具也不一定每个电脑都有,适合批量操作时用。

至于 VirtualBox 的共享文件夹和拖放功能,在 DOS 系统里基本别指望。VirtualBox 的 Guest Additions 没有 DOS 版本,共享文件夹客户端在 DOS 下找不到靠谱实现,折腾的性价比很低。早期 DOS 用户常用的网卡驱动 + 网络启动也是一条路,但网卡驱动在虚拟环境里的兼容性问题太多,不推荐给新手。

我自己日常用的就是第一种 ISO 工具盘方案。准备一个 tools.iso,里面固定放 DJGPP 安装包、UNZIP 和常用工具,每次开新虚拟机都挂载它,用起来特别顺手。

5. DJGPP 编译环境的安装与配置:解压方式、环境变量与 DPMI 内存

DJGPP 的下载不要直接去 GitHub 乱翻,DJGPP 官网专门做了一个 Zip Picker 页面,你在上面勾选需要的组件,它自动生成一个下载清单。对于基础 C 编译环境,至少要勾这几类:Base 开发文件、GCC 编译器、Binutils 二进制工具、GNU Make、C++ 支持(G++)。官网会把对应的 ZIP 包名列出来,一般以 djdev 开头的是基础开发文件,gcc 开头的是编译器,bnu 开头的是 binutils,mak 开头的是 Make,gpp 开头的是 C++。把这些包下载下来,按照官方推荐顺序放进工具盘里。

这里有个特别重要的原则:不要用 Windows 或 Linux 的解压工具把这些 ZIP 解压之后再去复制。DJGPP 的压缩包里目录结构、文件名大小写非常讲究,Windows 资源管理器解压会把目录名截断成 8.3 短文件名,导致整个工具链路径错乱。正确做法是把 ZIP 原封不动地传进 DOS 虚拟机,然后在 DOS 里用 UNZIP 命令逐个解压到 C 盘的 DJGPP 目录。FreeDOS 自带的 UNZIP 工具是 16 位版本的,专门为 DOS 环境优化,解压时能完整保留目录结构和文件名大小写。

安装步骤不复杂。首先在 C 盘根目录创建 DJGPP 目录,把所有 ZIP 包复制进去,进入 C:\DJGPP 后执行unzip djdev*.zip,然后按同样方式解压 GCC、Binutils 等后续包。解压顺序建议是从基础包到编译器,避免某些文件被覆盖。解压完成后,C:\DJGPP 下面会形成 bin、lib、include 等标准目录结构,其中根目录下会有一个 DJGPP.ENV 文件,这个文件是后续配置的核心。

接下来配置环境变量。用 EDIT 打开 C:\AUTOEXEC.BAT,在文件末尾加入两行:

set DJGPP=C:\DJGPP\DJGPP.ENV set PATH=C:\DJGPP\BIN;%PATH%

第一行的作用很多新手不理解。DJGPP 工具链启动时,需要 DJGPP.ENV 告诉它默认的 target platform、头文件路径、库路径这些信息。如果这个变量没设,你运行 gcc 直接会报错 saying environment variable DJGPP not set。第二行就不多解释了,把编译工具所在目录放到 PATH 里,否则每次都要写全路径。

配置完重启虚拟机,打开命令行,输入gcc -v验证。如果能看到版本信息往下刷,就说明编译器已经安装成功。

关于内存,再补充一点。DJGPP 编译出来的程序运行在 32 位保护模式下,需要 DPMI 服务。如果你在 FDCONFIG.SYS 里只加载了 HIMEMX,那么程序运行时会自动调起 DJGPP 自带的 CWSDPMI.EXE 作为 DPMI 服务端。如果加载了 JEMM386,它会优先作为 DPMI 宿主,这就可能和 CWSDPMI 产生冲突。我在实际测试中发现,启用 JEMM386 时编译某些文件会偶发 “DPMI initialization failed” 的错误,去掉 JEMM386 后问题消失。建议你安装完编译器后立刻做一次完整编译,如果遇到诡异错误,优先处理 JEMM386。

6. 编译实测:从 Hello World 到 DOS 中断调用

环境配置好了,来实际编译几个程序验证。先写个经典 Hello World,顺便验证整个工具链能否正常链接输出可执行文件。用 EDIT 新建 hello.c:

#include <stdio.h> int main(void) { printf("Hello from FreeDOS + DJGPP\n"); return 0; }

在命令行下执行:

gcc -O2 hello.c -o hello.exe

注意 DJGPP 的编译选项和现代 GCC 没什么区别,-O2开启优化,-I、-L等参数也都支持。编译完成生成 hello.exe,直接运行,如果屏幕上打出预期的字符串,就说明编译器、链接器、运行时库全部正常。

再写一个更“DOS”的程序,调用 INT 21H 取 DOS 版本号,验证 DPMI 下直接调用系统中断的能力。这个例子的意义在于:在保护模式下,你不能像实模式那样直接用 int86 传一个远指针,要借助 DJGPP 的__dpmi_int接口:

#include <stdio.h> #include <dpmi.h> int main(void) { __dpmi_regs r; r.h.ah = 0x30; /* 获取 DOS 版本号 */ __dpmi_int(0x21, &r); /* 调用 INT 21H */ printf("DOS version: %d.%d\n", r.h.al, r.h.ah); return 0; }

编译运行,屏幕上会显示 DOS 版本号。这个程序虽然简单,但把 DJGPP 最核心的保护模式 + DPMI 中断调用机制演示透了。以后你想写任何直接调用 DOS 中断或 BIOS 中断的底层代码,都可以在此基础上扩展。

我再建议你测一个稍微吃内存的程序,比如开一个大数组做冒泡排序,观察一下编译和运行时的内存表现。我在测试时开了一个 10 万元素的 int 数组,差不多 400KB,这在 16 位实模式下根本不敢想,DPMI 保护模式下毫无压力。这个测试能直观体会为什么当年 Doom 可以用 DJGPP 写出如此复杂的游戏。

DJGPP 自带的 C++ 编译器 g++ 也能用,G++ 的包安装完之后,AUTOEXEC.BAT 里 PATH 已经包含了它的路径,直接g++ test.cpp -o test.exe即可。

7. 踩坑实录:搭建过程中的九个典型问题与排查思路

这套环境我反复搭过不下十次,每次换系统、换 VirtualBox 版本都会碰到不同的小毛病。把印象最深的几个坑集中列出来,每个都按“现象 - 原因 - 解决”的思路说,方便你对照排查。

第一个是最常见的:安装 FreeDOS 时看不到硬盘。现象是安装程序提示没有可用磁盘。原因基本是 VirtualBox 默认的 SATA 控制器 FreeDOS 不认。解决方式前面已经说过,进“设置 - 存储”,删掉 SATA 控制器,新建 IDE 控制器,把虚拟硬盘挂上去。这个坑的变种是:硬盘在 SATA 上、光驱在 IDE 上,安装程序能跑但重启后找不到引导盘。无论如何,把硬盘和光驱都放到 IDE 控制器上最稳妥。

第二个是分区后无法引导。现象是安装完重启提示 Boot failed。原因是主分区没有激活。用安装盘重新引导,进 FDISK,把 C 分区设为 Active,再重启。

第三个是 gcc 启动时报 environment variable DJGPP not set。原因很简单,环境变量没配或者 DJGPP.ENV 文件路径写错。检查 AUTOEXEC.BAT 里set DJGPP=的路径是否指向真实的 DJGPP.ENV 文件。

第四个是编译出来的程序一运行就提示需要 DPMI,或者 DPMI initialization failed。这个在前面提过,多半是 JEMM386 的 DPMI 和 CWSDPMI 冲突。解决办法是打开 FDCONFIG.SYS,在加载 JEMM386 的那行前面加个 REM 注释掉,重启后再试。同理,如果加载了其他内存管理工具(如 EMM386),也一并先屏蔽。

第五个是光驱盘符看不到。现象是 ISO 工具盘挂载了,进 DOS 后 D: 访问不了。原因可能是光驱驱动没加载或者分配盘符失败。FreeDOS 安装程序在安装系统时通常会生成光驱配置,但如果你手动改过 FDCONFIG.SYS 就得自己补配置。常见写法是在 FDCONFIG.SYS 里加一条DEVICE=C:\FDOS\BIN\XCDROM.SYS /D:FREEDOS,在 AUTOEXEC.BAT 里加一条C:\FDOS\BIN\SHSUCDX /D:FREEDOS。具体路径以你系统里的文件为准。

第六个是 VirtualBox 宿主机启动虚拟机时报kernel driver not installed (rc=-1908)。这是 VirtualBox 在 Windows 宿主机上的内核驱动没装好。解决方法是重新运行 VirtualBox 安装程序,选择修复安装,或者手动重新安装 VirtualBox USB/内核驱动服务。这个报错在 5.2.44 上比较常见,新版 7.x 相对少一些。

第七个是 VirtualBox 报E_FAIL (0x80004005)。这个问题往往出现在虚拟机快照恢复、硬盘被其他进程占用或者驱动异常时。优先尝试重启 VirtualBox 主服务和所有虚拟机进程,再不行就重启宿主机。如果换电脑后把 VDI 文件复制过来也出现这个报错,记得检查 VDI 文件是否只读。

第八个是在 Windows 宿主机上用非字节流编辑器或资源管理器解压 DJGPP 的压缩包,导致目录结构损坏。压缩包在 DOS 里解压后 gcc 路径指向 find 不到头文件。这个问题不是命令行配置的问题,而是宿主操作系统文件名大小写处理方式不同导致的。我已经强调过多次:DJGPP 的所有 ZIP 包都要在 DOS 里用 UNZIP 解压,不要在宿主机上解压完再拷贝。

第九个是编译较大的项目时提示virtual memory exhausted或类似 Out of memory。原因不是系统内存不够,而是 DPMI 服务端分配内存受限。解决办法有两个方向:如果加载了 JEMM386 就先去掉;如果没加载,确认 HIMEMX 是否正常工作(执行himemx /v查看扩展内存状态)。大多数场景下,去掉 JEMM386 后一切恢复。

我把这几个坑的排查优先级排个序:先检查虚拟硬盘控制类型,再确认分区激活状态,然后处理内存管理工具的冲突,最后才是编译器环境变量这类小问题。按这个顺序走,百分之八九十的翻车现场都能原地恢复。

最后再说几句实操体会

整套环境搭完以后,我最大的感受是 DOS 这种系统虽然老,但它那种“一切尽在掌握”的开发方式,反而让编译这件事变得极其透明。没有 IDE 在背后偷偷做各种事情,每一步都要你亲自动手:文件传进去、压缩包解开、环境变量写上、命令行编译、运行看结果。这种直白的编译流程对理解 C 语言编译原理非常有帮助。

我现在在 VirtualBox 里始终留着一个装好 FreeDOS + DJGPP 的虚拟机,偶尔写点小游戏练手,或者把刚学编程时的代码翻出来重新编译一下。如果你也想折腾老代码,或者想感受一下上世纪程序员在 640KB 限制下的开发体验,照这个流程走一遍,基本两小时就能跑通。另外提一句,这套虚拟机完全离线运行,快照也做了,随便折腾,坏了立刻回滚,比在实机装 DOS 省心太多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询