DOS 起源考古 · 第一册

诞生那一刻

1981 年 4 月 28 日,86-DOS 1.00 长什么样
行号基准:Paterson-Listings 3_source_code/86-DOS_1.00/86DOS.ASM(MIT)· 2026-09-13

证据分档:源码实证=源码文本,附文件与行号,读者可直接打开核对;数据实证=由 _evidence/vol1/ 下的脚本算出、可复跑的统计与验证;外部资料=当事人邮件、官方发布说明、扫描原件等,附出处;推断标为作者推导。说明性比喻与事实证据分开呈现。本册没有把源码汇编并启动,原因与替代路径见第 8 章。
本册行号以哪棵树为准。全部 86DOS.ASM:行号 引自 Paterson-Listings 仓库的 3_source_code/86-DOS_1.00/86DOS.ASM(MIT 许可)。另有两处容易混用:2_printed_files/ 是打印原件的转录,行号带打印行编号(形如 00520);86-DOS-0.11 是社区逆向重建(法律灰色),写于 1980 年(具体月份无据)。不要拿这两处核对本册行号。
0 一叠纸 · 1 九个锚点 · 2 一个文件的内核 · 3 两个大门 · 4 四十二个槽 · 5 盘上的新世界 · 6 定版之后 · 7 开机那一刻 · 8 交房图纸

0 · 一叠纸

2026 年 4 月 28 日,微软把一份打印清单放到了网上

2026 年 4 月 28 日,微软在开源博客宣布公开一批早期 DOS 打印清单及其转录源码。按微软的说法,这是目前发现的最早 DOS 源码材料;这一天也正是 86-DOS 1.00 定版 45 周年。这些材料不是一份从磁盘镜像中恢复出来的完整发行版,而是 Tim Paterson 保存的连续纸清单。研究者找到原件、完成扫描和转录之后,代码才变成今天可以检索、复核和装配的文件。

先看材料形态 这批材料更像一份开发现场档案,而不是一张完成品说明书:打印清单、修订记录、不同时间点的快照和手写批注,仍然保存在同一条材料链上。

先把材料说清楚,因为它和通常的「开源源码」不是一回事。这叠纸里不能简单认定存在一份代表正式发行时刻的唯一源码副本,现存的是一批打印清单(assembler listing):汇编器把源码打印在连续纸上时,会在每行前面印上行号和地址,旁边配上当时的注释。程序员在纸上改,再把改完的内容敲回机器。Paterson 在 2025 年 4 月 24 日回复考证者 Yufeng Gao 的邮件里,自己把这叠纸清点了一遍:高 3.5 英寸(他顺手解释了一句,「我原以为有六英寸,那是底下垫了一叠白纸」),一共十捆,里面有 86-DOS 1.00 的内核、几个 PC-DOS 1.00 的开发快照、CHKDSK,还有汇编器自身的清单,以及他在微软负责的 BASIC 编译器运行库。

IBM PC 主机与显示器、键盘,桌上摊着绿色横纹连续纸打印清单,旁边放着计算尺与计算器
1981 年前后的一台 IBM PC,与它旁边摊开的打印清单
微软开源博客配用的这张照片,标题是「80 年代初在 IBM PC 上做 DOS 开发是什么样子」,摄影 Rich Cini。画面中可以看到 IBM PC、连续纸清单、计算尺和计算器;屏幕上的绿色字符对应本册第六章要读的那份清单。
Tim Paterson 的肖像照
Tim Paterson(86-DOS 作者)
Tim Paterson。1980 年他在西雅图电脑产品公司(Seattle Computer Products)写这个系统时,它叫 QDOS;改名 86-DOS 后,1981 年 4 月 28 日定版 1.00。照片来自清单仓库的公开页。

材料为什么有用

这批材料的价值不只在于年代早,更在于它把版本过程保留下来:同一内核在不同时间点的快照、记录文件差异的清单(diff:把两份文件逐行比较,只列出不同之处),以及 Paterson 写在纸边的批注,都还可以相互对照。微软把它称作「一个 Git 仓库的打印版提交历史」(a printed commit history of a Git repository)。这个比喻并非泛泛而谈:Git 中由程序计算的差异,在这里变成了打印出来的文本,有时旁边还留着手写说明。

这套软件后来进入了 IBM PC 的产品谱系:86-DOS 1.00 定版三个月后,IBM PC 出货,其操作系统称为 PC-DOS 1.00;微软随后继续发展同一血统的 MS-DOS。但在 1981 年 4 月 28 日这个时间点,眼前的仍是一份属于 Seattle Computer Products 的产品:源码文本约六万字节,整个内核集中在一个文件里。

本册的任务

这一册围绕一个问题展开:1981 年 4 月 28 日定版的 86-DOS 1.00,究竟是什么样子?回答这个问题时,需要把源码、快照、差异清单和手写批注分开使用。正文会依次检查三件事:哪些功能已经有实现,哪些仍然只是占位;哪些接口沿用了 CP/M 的约定,哪些内部结构是为 8086 和新的磁盘组织方式重新设计的;定版之后,系统又沿着什么方向变化。

材料清单与使用方法

本册引用的所有行号,都指向 Paterson-Listings 仓库里 3_source_code/86-DOS_1.00/86DOS.ASM(MIT 许可)。这个文件的第一行就是自我声明:「86-DOS High-performance operating system for the 8086 version 1.00 04/28/81」。读者想在本地复核,把仓库取下来、打开这个文件、按行号跳过去即可。

另有两个来源容易与本册混用,先说明白。一是同仓库的 2_printed_files/,那是打印原件的转录,行号带打印行编号(形如 00520),和定版源码的行号不是一套;二是网上流传的 86-DOS 0.11 社区逆向重建版(文件名后缀是 .A86),它比 1.00 更早,README 只说写于 1980 年、具体月份无据,重建者自己在 README 里写明「法律地位可能不明确」。本册只在需要对照时才提它,每次都标注,也不拿它的行号当基准。

一叠纸,四个点,一条链 86-DOS 0.11 1980 年(月份无据)· 早期对照 社区逆向重建 法律灰色 · 只对照 内核 2,259 行 不是本册主角 86-DOS 1.00 1981-04-28 · 本册主角 Paterson 清单原件 MIT 许可 · 可装配 内核 3,622 行 本册行号基准 PC-DOS 1.00 1981-06/07 快照 两个开发快照 + 版本间 diff 3,888 / 3,901 行 第六章比较 MS-DOS 1.25 / 2.0 / 4.0 微软官方源码 MIT 许可 4,031 行 链的末端 这批材料的独特之处:它同时保留了源码和时间线 · Paterson 保存了四十四年的连续纸打印清单,高 3.5 英寸(他本人 2025 年邮件里的原话) · 里面有内核的两个快照(6/16、7/7,后者于 7/8 打印)、一份版本间 diff、CHKDSK、汇编器自身、BASIC 运行库 · 纸上有他当时随手写下的批注,本册第六章要用到其中两条,它们把时间线钉在了纸面上 · 微软官方给的比喻:「可以把它看成一个 Git 仓库的打印版提交历史」 纸堆已捐给 Interim Computer Museum;代码经 Yufeng Gao、Rich Cini 团队转录后以 MIT 许可发布 灰色声明:0.11 为逆向重建,作者自述法律地位不明,本系列只作对照,不作行号基准
图 0 四个时间点、两种许可状态、以及这叠纸为什么是一条时间线。
本章证据
档位事实出处复核方式
外部资料2026-04-28 发布、45 周年、「打印版提交历史」的比喻、MIT 许可、清单捐给 Interim Computer Museum、考证团队名单微软开源博客 Continuing the story of early DOS development(2026-04-28,作者 Stacey Haffner / Scott Hanselman)原文直引,链接见参考文献 [5]
外部资料清单高 3.5 英寸、十捆内容清点、5/5/81 时间戳口径、BASIC 运行库是他在微软期间的职责Tim_Paterson_24Apr2025_emails.txt(随仓库发布的四封往来邮件)邮件全文在引用基准仓库根目录
源码实证基准文件第一行自证版本与日期86DOS.ASM:1直读

1 · 九个锚点

文件开头先记录版本变化

打开 86DOS.ASM,头十七行里没有一条指令。第 1 行声明身份,第 4 行到第 17 行是一份修订台账:Paterson 用注释写的版本记录,从 1980 年 12 月 29 日的 0.34 排到 1981 年 4 月 28 日的 1.00,九个锚点。开头第一行原文是全大写的命令「EVERY change must noted below!!」,每一处改动都必须记录在下面。这是这份 86-DOS 1.00 源码里能直接看到的最早一层版本记录。

绿色横纹连续纸的打印清单原件扫描件,底部有打印元数据与手写批注
86-DOS 内核清单的一页打印原件(6 月 16 日打印)
这份清单的一页。页脚印着两行打印元数据,第二行是本册最要紧的:Created: 15-Jun-81 03:18:51 Printed: 16-Jun-81 15:17:10,与仓库 README 里 6 月 16 日快照的记录一致到分钟;秒位纸面为 :10、README 为 :17,相差 7 秒,原因不明。纸的下沿有一行手写批注:PC-DOS 1.0 NO DATE/TIME FUNCTIONS(PC-DOS 1.0,没有日期时间功能)。另一页上还有一条方向相反的批注,第六章会把两条并排看。

台账全文

; 0.34 12/29/80 General release, updating all past customers
; 0.42 02/25/81 32-byte directory entries added
; 0.56 03/23/81 Variable record and sector sizes
; 0.60 03/27/81 Ctrl-C exit changes, including register save on user stack
; 0.74 04/15/81 Recognize I/O devices with file names
; 0.75 04/17/81 Improve and correct buffer handling
; 0.76 04/23/81 Correct directory size when not 2^N entries
; 0.80 04/27/81 Add console input without echo, Functions 7 & 8
; 1.00 04/28/81 Renumber for general release
86DOS.ASM:7–15,逐行转录,未作改写

这九行可以当作一份简短的版本时间表。0.34 的说明是面向既有客户的通用发布;0.42 把目录项从 16 字节扩到 32 字节;0.56 允许记录大小和扇区大小变化;0.60 调整 Ctrl-C 的退出路径;0.74 让 I/O 设备可以用文件名表示;0.80 加入无回显的控制台输入。最后一行 1.00 只写着 Renumber for general release。这说明台账把 1.00 记作一次面向发布的重新编号,但不能据此断定源码没有其他变化。

0.80 条目还特别点名了「功能 7 和 8」。第四章会把这两个编号与 CP/M 的对应功能放在一起比较,因为同一个编号在两套系统中并不总是代表同一项服务。

台账与当事人邮件对得上

考证者曾问 Paterson:清单里的 5 月 5 日是否代表发布前最后一轮修 bug。Paterson 的回答是,这只是文件最后一次改动时留下的时间戳,并不对应某个发布或版本;他认为 5 月 5 日的文件应当与 4 月 28 日发布的 v1.00 非常接近,也可能只是上传到微软服务器的日期。因此,源码台账提供了 4 月 28 日的版本锚点,邮件则解释了为什么清单里还会出现 5 月 5 日。

本章证据
档位事实出处复核方式
源码实证修订台账九个版本锚点,1.00 的说明是 Renumber for general release86DOS.ASM:7–15直读;行号范围 :4–17 含标题行
外部资料5/5/81 是文件时间戳、不对应发布;与 4/28 的关系Tim_Paterson_24Apr2025_emails.txt(第三、四封)邮件原文
源码实证0.80 条目点名「功能 7 和 8」86DOS.ASM:14直读,第四章回看
外部资料打印元数据 Created/Printed 两项与仓库 README 一致Paterson-Listings README「Bundle 04」条目 + 打印页原件扫描(bundle04 第 1 页)扫描件裁切放大后逐字核对,避免缩略图误读

2 · 一个文件的内核

源码文件 3,622 行、60,124 字节,代码和数据住在一起

86-DOS 1.00 的内核源码本身,集中在一个汇编文件里:86DOS.ASM。这份源码文件 3,622 行、60,124 字节,注意这是源码文本的大小,不是编译出来的内核大小(后者见第 8 章)。它没有被拆成多个源文件,也没有头文件;模块边界主要靠标号、分区注释和调用关系维持。模块化不等于多文件,这一点后面会反复看到。全文只有一处分区记号,在第 3206 行:;* DATA AREA *。这一行把文件划成两个区域:前 3,205 行是代码区(其中也包含注释、标号和定义),后 417 行是内核集中声明的可写数据区。

先说明行数口径

同一个文件用两把尺子量,会得到两个数:Python 按行切开数,是 3,622 行;Unix 的 wc -l 数换行符,得 3,621,因为这个文件的最后一行没有行尾换行符。本册统一用 3,622。这一处先讲明白,读者自己一量才不会有疑问。

六个区

自我介绍区(1–56 行):版本声明、九个版本的台账、中断入口常量、功能号上限。文件虽小,开头先把自己的来历交代清楚。数据结构区(58–185 行):文件控制块(FCB)的 32 字节布局、目录项的字段说明、磁盘参数块、BIOS 入口表、保存用户寄存器的位置表,还有一张行编辑器的按键表。第五章会回到这里。大门与走廊(186–301 行):开机横幅、两条系统调用入口、保存九个寄存器和中断控制。第三章将详细说明入口路径。总机接线板(302–356 行):DISPATCH 跳转表,共 42 个功能槽,其中 5 个仍是空壳。第四章会逐槽比较它们的语义。功能实现区(357–3205 行):FAT 查表与改写、目录搜索、文件的开读写删、字符输入输出、Ctrl-C 处理、报错。DATA AREA(3206–3622 行):当前盘号、扇区大小、FAT 计数、目录缓冲、内核自己的栈。

DATA AREA 有 417 行,约占整个文件的九分之一。当前盘号、扇区大小、FAT 状态、目录缓冲和内核栈等运行时状态,都在这里留下位置。这不是说内核只会写这一区域,而是说源码把主要的可变状态集中放在了这里;代码区负责执行,数据区负责保存执行过程中的状态。

单文件不是偷懒

现有 README 能直接证明的是构建流程:用 SCP ASM 汇编一个源文件,得到 Intel HEX 目标文件,再用 HEX2BIN 转成二进制;示例命令是 A:ASM 86DOS,随后执行 A:HEX2BIN 86DOS,汇编器最后报告 Error Count = 0。这条流程与单文件内核能够很好地配合,但源码和 README 没有直接说明「为什么采用单文件」。从源码本身看,模块边界主要依靠标号、数据区和调用关系来维持。

一个文件的内核:86DOS.ASM 的 3,622 行 1–56 自我介绍 版本声明(1.00 04/28/81)· 九个版本的修订台账 · 中断入口常量 · 功能号上限 58–185 数据结构 FCB 的字段布局 · 目录项说明 · 磁盘参数块 · BIOS 入口表 · 寄存器保存位 · 行编辑键表 186–301 大门与走廊 开机横幅 · CALL 5 与 INT 两条入口 · 保存九个寄存器 · 关中断后才敢开工 302–356 总机接线板 DISPATCH 跳转表:42 个功能槽 · 其中 5 个槽后面还只是空壳 357–3205 功能实现区 FAT 查表与改写 · 目录搜索 · 开读写删文件 · 字符输入输出 · Ctrl-C 处理 · 报错 3206–3622 DATA AREA 内核自己的可写数据:当前盘号 · 扇区大小 · FAT 计数 · 目录缓冲 · 栈 代码与数据住在同一个文件里,装进内存后各就各位;分区行号由 vol1_map.py 扫描得出
图 2 内核地图。第 3 章从蓝框的两条入口读起,第 4 章读绿框的接线板。
本章证据
档位事实出处复核方式
源码实证全文唯一分区记号在第 3206 行,其后为 DATA AREA86DOS.ASM:3206vol1_map.py 分节标记扫描
数据实证源码文件 3,622 行 / 60,124 字节 / 代码 3,205 行 + 数据 417 行vol1_map.py 输出splitlines 计数与实际文件大小;wc -l 少 1 的原因已说明
外部资料ASM + HEX2BIN 的两步构建流程Paterson-Listings README「Compiling/Assembling」一节README 原文

3 · 两个大门

CALL 5 与 INT:一个内核,两套门铃

从应用程序的公开调用界面看,86-DOS 提供两条进入内核的路径:传统的 CALL 5,以及通过中断进入的 COMMAND 入口。中断可以理解为 CPU 按约定跳到系统代码处理请求,处理完再返回原处。两条路径最后都进入 SAVREGS(第 219 行)之后的保存现场和分发流程,但可接受的功能号范围不同:CALL 5 的上限是 36(0–36,共 37 个),中断入口的上限是 41(0–41,共 42 个)。

CALL 5 是什么

在 CP/M 中,程序通常把功能号放进 C 寄存器,再执行 CALL 5;地址 5 是 BDOS 的入口。许多 CP/M 程序把这个调用约定直接写进了机器码。如果 8086 上的新系统希望接住这批程序,就必须提供相同的入口位置和寄存器约定。至于入口之后如何实现,应用程序并不需要知道。

ENTRY: ;System call entry point and dispatcher
POP AX ;IP from the long call at 5
POP AX ;Segment from the long call at 5
SEG CS
POP [TEMP] ;IP from the CALL 5
PUSHF ;Start re-ordering the stack
DI
PUSH AX ;Save segment
SEG CS
PUSH [TEMP] ;Stack now ordered as if INT had been used
CMP CL,MAXCALL ;This entry point doesn't get as many calls
JA BADCALL
MOV AH,CL
86DOS.ASM:206–218,注释原文点明 CALL 5,也点明这道门能点的功能少一些

这段入口代码先处理 CALL 5 带来的栈布局:它取出远调用留下的段地址和偏移,再把栈整理成与中断入口相同的形状。随后,功能号从 C 寄存器移到 AH(MOV AH,CL),这样两条路径就可以共用后面的分发逻辑。CMP CL,MAXCALL 则把 CALL 5 的功能号限制在 36 以内。

两个上限,两种身份

文件头部并排放着两个常量:MAXCALL 等于 36,MAXCOM 等于 41(第 44–45 行)。CALL 5 检查前者,中断入口 COMMAND 检查后者。因此,37–41 号虽然出现在分发表中,却不属于 CALL 5 这条兼容入口;它们要通过更宽的中断入口才能到达。

进门先关中断

入口在重排栈时执行 DI(关中断,第 212 行),避免现场尚未整理好就被再次打断。栈切换完成后,代码在第 254 行执行 EI;返回阶段又在第 272 行暂时关中断,恢复保存的栈和寄存器,最后用 IRET 返回。文件头的 DSKTEST 开关(第 25–29 行)还说明,打开它后,磁盘 I/O 可以使用另一套栈,从而具备注释所说的可重入条件。默认值为 0 时,内核仍按共享栈工作;这正是入口需要先保护栈状态的原因。

本章证据
档位事实出处复核方式
源码实证CALL 5 入口重排栈、功能号从 CL 搬进 AH、上限 36 拦截86DOS.ASM:206–218直读;MAXCALL/MAXCOM 定义于 :44–45,COMMAND 入口的上限检查在 :199–201
源码实证入口立即关中断;DSKTEST 双栈注释;出口九个寄存器 + IRET86DOS.ASM:212 / :25–29 / :271–300直读
外部资料CALL 5 是 CP/M 时代系统调用入口的约定俗成CP/M 2 参考资料(seasip.info BDOS 汇总;classiccmp 邮件列表的 CP/M 2 分发表片段)该约定在 CP/M 文档中反复出现,见参考文献 [4]

4 · 四十二个槽

从 DISPATCH 表看 1.00 提供了哪些接口

功能号进入 AH 后,分发器把它扩展成表内偏移,再通过 DISPATCH 表找到对应的处理例程。相关代码在第 256–270 行,表本身从第 302 行开始,共有 42 个表项。Paterson 用 Standard Functions 和 Extended Functions 两个注释区分标准功能与扩展功能;这张表因此清楚地展示了 1.00 对外提供了哪些编号。

四十二格里有什么

0 号终止程序、1 号读键盘、2 号写屏幕、3 号读带机、4 号送凿孔机、5 号送打印机,3、4 两号是纸带机与凿孔机的功能,1981 年已属文物,但兼容层不问过时不过时,只问对方有没有。接着是 9 号打印字符串、10 号读一行、11 号看键盘有没有按键;13 号复位磁盘系统、14 号选盘,15 到 23 号是文件操作:开、关、找、删、顺序读写、建、改名;26 号设置数据交换地址、27 号取 FAT 指针、33 到 36 号是随机读写与文件大小;37 号往后五格是扩展功能:置中断向量、置新基段、块读、块写、构造 FCB。

和 CP/M 的分发表逐格对账

「86-DOS 兼容 CP/M」仍然太笼统。本节只比较它与 CP/M 2 的系统调用槽位,不把这个数字扩展成对所有 CP/M 版本或所有程序行为的判断。本册使用留存的 CP/M 2 分发表片段和功能清单,与 86-DOS 的 DISPATCH 表逐格比较。比较的不是标号名字是否相同,而是同一个编号在两边是否代表同一项服务:例如都表示「返回当前盘」时,即使名字写成 CURDRV 或其他形式,也算作同一语义。

结果是:42 格里有 34 格对得上,编号相同,而且功能对象相同(名字可以不一样,比如 CP/M 叫「返回当前盘」,86-DOS 叫 CURDRV,是一件事)。4 格被征用:编号还在,里面换成了别的东西。2 格填空:CP/M 在那个编号上没有定义过东西,86-DOS 拿来用了。1 格同一个位置换了对象:27 号,两边都是「取地址」,但 CP/M 取的是分配图地址,86-DOS 取的是 FAT 指针,第五章详谈。1 格超出 CP/M 表的范围:41 号构造 FCB,是 86-DOS 自己加的。判定怎么定的:看的是功能对象是否相同,优先依据两边的调用约定与文档描述,名字不同不影响判定,也不做字符串匹配。这条判定协议写在 vol1_cpm.py 的头部,读者可以照它逐格复核,也可以不同意其中某一格的归类。

被征用的那几格,正是线索

7 号和 8 号:CP/M 在这里放的是「读/写设备重定向字节」,86-DOS 换成了无回显的键盘输入。回头对第一章那份台账,0.80 那行写的是「加上无回显控制台输入,功能 7 和 8」,台账里的「功能 7 和 8」,就是这两个被征用的编号。37 号:CP/M 的 Reset Drive,86-DOS 换成了置中断向量。40 号:CP/M 的「随机写并零填充」,换成了块写。

这些差异会影响实际兼容性:如果 CP/M 程序调用 7 号功能来读取 I/O 重定向字节,在 86-DOS 上得到的却是另一种输入操作。同时,34 个对齐槽仍然构成了最主要的接口骨架,开文件、读写记录和删除文件等常用服务都在其中。不过,槽位和功能对象对齐,并不能保证某个具体 CP/M 程序直接运行;BIOS 假设、内存布局、参数格式和错误处理方式仍可能不同。因此,这份对账衡量的是接口层的对应关系,不是完整的程序兼容率。

空壳标号与功能槽

VERSION:
GETIO:
SETIO:
WRTPROT:
GETRDONLY:
SETATTRIB:
USERCODE:
MOV AL,0
RET
86DOS.ASM:348–356,七个标号共用同一个函数体,全文照录

42 格里有 5 格的函数体只有两行:把 AL 清零,然后返回。上面七个标号中,GETIO 与 SETIO 没有被分发表引用,真正占着功能槽的是另外 5 个,其中包括 12 号的 VERSION。在 1.00 里,「请报出你的版本号」得到的答案是 0。

从行为看,VERSION 是一个兼容占位:编号先保留下来,函数体只返回 0。第五、六章会追踪这些空壳的去向,有的后来长出了新实现,有的则被删除。

1.00 的 42 个功能槽,与 CP/M 分发表逐槽对账 34 槽:对齐 连编号都沿用 CP/M 0 终止 · 1 读键 · 2 显示 9 打印串 · 10 读一行 · 11 看按键 15 开文件 · 16 关文件 · 19 删 20/21 顺序读写 · 22 建 · 23 改名 26 置交换地址 · 28 写保护盘 29 只读位图 · 30 文件属性 31 盘参数 · 32 用户号 · 33-36 随机 接口骨架对齐,不等于程序一定能跑 4 槽:被征用 CP/M 原有定义被替换 7/8 设备重定向 → 无回显输入 37 Reset Drive(复位驱动器)→ 置中断向量 40 随机写补零 → 块写 其余 4 槽 1 槽同位置换了对象(取分配图→取 FAT) 2 槽填空(CP/M 当年未定义) 1 槽超出 CP/M 表(自己新加) 空壳标号 标号存在,函数体只返回 0 它们的函数体只有两行: MOV AL,0 RET 包括槽 12,报版本号。 在 1.00 里它返回 0, 就是「版本号是零」。 这些槽位先保留了编号: 其中 5 个在分发表内仍是空壳。 对账方法:CP/M 2 分发表槽位(seasip.info 汇总 + classiccmp 邮件列表留存的 CP/M 2 分发表源码片段) 与 86-DOS 的 DISPATCH 表(源码 :302–346)逐槽比对,脚本 vol1_cpm.py 可复跑;「对齐」指功能对象相同,名字可以不同 还要看这些槽位在后续版本如何变化 与 CP/M 对齐的槽数:1.00 → 6/16 快照 → 7/7 快照 → MS-DOS 1.25 34 → 33 → 33 → 32 这里只表示槽位语义的对齐数量下降
图 4 上:与 CP/M 的对账结果;下:对齐槽数在四个时间点上的走向。
本章证据
档位事实出处复核方式
源码实证DISPATCH 跳转表 42 格,注释分为标准与扩展两段86DOS.ASM:302–346vol1_evolve.py 提取 + 直读
源码实证七个标号共用 MOV AL,0 / RET(五格在分发表内),其中包含 12 号报版本号86DOS.ASM:348–356直读
外部资料CP/M 2 功能分发表的编号与名称seasip.info BDOS Function Summary;classiccmp.org 邮件列表留存的 CP/M 2 分发表源码片段两份来源的编号与名称一致;本册只用其编号与名称,不用其代码
数据实证34 对齐 / 4 征用 / 2 填空 / 1 同位换对象 / 1 新增;对齐槽数 34→33→33→32vol1_cpm.py 输出判定口径写在脚本头部:以「功能对象是否相同」为准,名字不同不影响判定

5 · 盘上的新世界

接口层沿用 CP/M,文件系统内部另起炉灶

上一章有一个槽位没有展开:27 号功能在 CP/M 中返回分配图地址,在 86-DOS 1.00 中则返回 FAT 指针。这个差别正好把两套文件系统的内部组织区分开:CP/M 用静态位图记录盘块是否占用,86-DOS 用 FAT 把一个文件的簇串成链。应用程序看到的调用编号仍然相近,盘上的数据结构已经换了一套。

FCB:36 字节的结构

程序与内核之间关于文件的部分状态,放在 FCB(文件控制块)里。1.00 的主体区域占 32 字节,后面还有 1 字节的当前记录号 NR 和 3 字节的随机记录号 RR,合计 36 字节。FCB 的前部布局沿用了 CP/M 约定,但 RECSIZ、FDATE 和 FIRCLUS 等字段已经体现出 86-DOS 自己的文件系统设计。

ORG 0
DS 12 ;Drive code and name
EXTENT: DS 2
RECSIZ: DS 2 ;Size of record (user settable)
FILSIZ: DS 4 ;Size of file in bytes
FDATE: DS 2 ;Date of last writing
FILDIRBLK:DS 2 ;Location in directory
FIRCLUS:DS 2 ;First cluster of file
LSTCLUS:DS 2 ;Last cluster accessed
CLUSPOS:DS 2 ;Position of last cluster accessed
DIRTYFIL:DS 1 ;File has been written to if <>0
ORG 32
NR: DS 1 ;Next record
RR: DS 3 ;Random record
86DOS.ASM:60–73,FCB 布局定义,全文照录

前 12 字节保存盘号和 8.3 文件名,这是 CP/M 程序熟悉的布局。后面的字段则加入了 86-DOS 的信息:RECSIZ 允许记录大小变化,FDATE 保存最后写入日期,FIRCLUS 保存文件的首簇号。EXTENT 这个旧名称仍在,FIRCLUS 这样的新字段也已经出现;同一个结构里可以同时看到兼容接口和新的盘上组织方式。

十二位一格,两个格子挤三个字节

FAT 是文件分配表。源码注释把每个表项称为一个 allocation unit;为方便叙述,本文沿用常见说法,把它称作簇。表项中的值通常是下一个簇的编号,文件因此可以由多个簇串成一条链。1.00 的每个表项占 12 位,两个表项打包进三个字节。这会让读取和写入都多一步位操作,源码注释随后给出了完整算法:

; The File Allocation Table uses a 12-bit entry for each allocation unit on
; the disk. These entries are packed, two for every three bytes. The contents
; of entry number N is found by 1) multiplying N by 1.5; 2) adding the result
; to the base address of the Allocation Table; 3) fetching the 16-bit word at
; this address; 4) If N was odd (so that N*1.5 was not an integer), shift the
; word right four bits; 5) mask to 12 bits (AND with 0FFF hex). Entry number
86DOS.ASM:87–92,六行整行转录,步骤一到五是 Paterson 自己编号的;:92 行尾的 Entry number 接下文(EOF 陷阱说明),与本章无关,未转录

最容易卡住的是第一步:为什么要乘 1.5?因为每个表项占 12 位,也就是 1.5 字节。第 N 个表项从第 1.5N 个字节开始;N 为奇数时,起点落在一个字节的高、低半区之间,所以要把取出的 16 位值右移四位。源码用移位和加法代替乘法,最后用 AND DI,0FFFH 保留 12 位。这种两个 12 位表项共用三个字节的布局,后来通常被称为 FAT12。

为什么第一个簇叫「簇 2」

第一次读 FAT,很多人都会在这里停下来:0 号和 1 号表项做什么用?为什么第一个能存数据的簇不叫「簇 0」,而叫「簇 2」?Paterson 在注释里给了答案。0 号表项既是系统用来触发文件结束处理的陷阱,也在 SMALLDIR 打开时充当目录项大小标志;1 号表项保留。真正分配给文件的表项从 2 号开始,所以第一个可用簇就叫簇 2。注释特意写道:even though it is the first, is called cluster 2。

0 表示该簇未分配,普通值表示下一簇的编号。源码注释写的是「大于 0FF8H 为文件结束标记」;实际遍历代码在比较 0FF8H 后使用 JAE(见 86DOS.ASM:1496),所以从实现看,0xFF8 及以上都被当作结束标记。这里把注释和执行路径并列写出,避免混淆两种边界口径。

目录项里的日期,是被位运算塞进去的

1.00 的目录项有 32 字节,偏移 24(从 0 起算,即第 25 个字节)开始的两个字节保存写入日期。源码注释把位分配写得很清楚:bit 0–4 是日,bit 5–8 是月,bit 9–15 是 year-1980(86DOS.ASM:83)。也就是说,年份存成相对于 1980 年的偏移,而不是直接存四位数;7 个 bit 在表示层面最多可以覆盖到 2107 年。把多个小字段塞进同一组二进制位,叫作位场。

SMALLDIR 为什么存在

文件头第 19–22 行定义了 SMALLDIR。注释说,打开它可以同时接受旧的 16 字节目录项。0.42 的修订台账记录了目录项从 16 字节扩到 32 字节;目录项变宽后,目录占用的扇区和数据区起点都会变化。因此源码在开关打开时保留了两套磁盘参数:一套对应 16 字节目录,一套对应 32 字节目录(第 118–123 行)。1.00 的开关值为 1,说明这两种格式在编译时都被纳入考虑;它后来何时退出,见第六章。

接口层与盘上结构

把前面的证据放在一起,可以看到两层设计。在程序可见的边界上,86-DOS 保留了 CALL 5、功能号、FCB 前部布局和 $ 结束字符串等 CP/M 约定;在文件系统内部,它改用了 FAT 链、32 字节目录项和可变记录大小。因此,兼容主要发生在接口层,文件组织则按照 8086 系统自己的需要展开。

FAT 条目的五步查表算法与独立复核 盘上最小的分配单元,源码注释里叫 allocation unit(本文简称簇) 簇 2(12 位) 簇 3(12 位) 两个 12 位 = 24 位 = 三个字节 源码注释里的五步(86DOS.ASM:87–92) ① 簇号 N 乘 1.5  ② 加表格起始地址 ③ 从该地址取一个 16 位的字(两字节) ④ N 为奇数(乘 1.5 出小数)则把字右移四位 ⑤ 与 0FFF 掩码,只留 12 位 唯一会绊人的就是第 ① 步:每个条目 1.5 字节,所以会算出半个字节 簇 0 · 簇 1 被内核征用了 所以第一个可用簇叫簇 2 一次查表的完整路径 簇号 N 乘 1.5 得到字节位置 取 16 位字 读两个字节 N 为奇数? 右移四位 AND 0FFF 得到这一格的值 FAT12 查表与写入算法的独立复核(vol1_fat_check.py) 注释五步 与 源码指令模拟:对 4,096 个簇号逐条比对,不一致 0 条 写入后再读回:随机 1,000 次,读回不一致 0 次,相邻格子被破坏 0 次 按注释原文写入一条链 簇 2 → 簇 3 → 簇 5(5 号写 0FF8 结束标记),能原样读回 验证范围:只覆盖 FAT12 打包算法这一条路径;不汇编源码,也不启动系统。 脚本为纯 Python,无外部依赖,读者可直接重跑。
图 5 上:12 位的打包方式与注释里的五步;中:一次查表的完整路径;下:把这五步写成可执行模型后跑出来的结果。
本章证据
档位事实出处复核方式
源码实证FCB 主体 32 字节 + NR/RR 共 36 字节;EXTENT 与 FIRCLUS 两种命名并存86DOS.ASM:60–73(EXTENT 在 :62,FIRCLUS 在 :67)直读
源码实证FAT 12 位条目、查表五步、簇 0/1 用途、第一个可用簇叫簇 2;实现按 0xFF8 及以上判断结束86DOS.ASM:87–98、1496注释原文与 CMP 0FF8H/JAE 实现对照;打包实现见 :369–432 的 UNPACK/PACK
源码实证目录项偏移 24(第 25 个字节)起的两个字节存日期,位场划分86DOS.ASM:83直读;源码位置按从 0 起算的目录项偏移解释
源码实证SMALLDIR 开关与两套磁盘参数(16/32 字节目录各一套)86DOS.ASM:19–22 与 :118–123直读
数据实证FAT12 查表算法的可执行验证:注释五步与源码指令模拟对 4,096 个簇号逐条一致;写后读回 1,000 次零失败且未破坏邻格;按注释原文写入的簇链 2→3→5 能原样读回vol1_fat_check.py 输出纯 Python、无外部依赖、可复跑;验证范围只覆盖 FAT12 打包算法这一条路径,不汇编源码也不启动系统

6 · 定版之后

四个快照,看一个系统怎样开始变形

1.00 定版之后,同一条材料链上还留下了三个可比较的时间点:6 月 16 日快照、7 月 7 日快照,以及后来的 MS-DOS 1.25 源码。把四个文件放在一起,先比较行数和字节数,再比较分发表和开关,可以看到系统在定版之后增加了哪些能力,又放弃了哪些兼容路径。

绿色横纹连续纸的打印清单,页面底部有手写批注一行
7 月 8 日打印的清单页,下沿有手写批注
纸下沿的手写批注:DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK。页脚的打印元数据写着 Date 08-Jul-81 13:49:36、Created 07-Jul-81 17:06:59、Printed 08-Jul-81 13:49:52。这一页与上一页出自同一叠连续纸,同一种绿白横纹。
IBM PC 显示器上显示 86-DOS 源码清单,右侧是同一份内容的打印原件
IBM PC 屏幕上显示的清单内容,与旁边的打印原件对照
屏幕上是这份源码的头部:版本行、修订台账、以及 EXCESS: EQU 511 ;Make 86-DOS artificially larger 与 SMALLDIR: EQU 0 两行,这正是 7 月 7 日快照的两个特征值,也就是说屏幕里跑的是本册讲的第三个点。右边摆着的打印件,就是屏幕上这份内容的纸质版。

先看四个数

源码文件的大小依次是:1.00 为 3,622 行、60,124 字节;6 月 16 日快照为 3,888 行、66,985 字节;7 月 7 日快照为 3,901 行、67,261 字节;MS-DOS 1.25 为 4,031 行、114,253 字节。从 1.00 到 1.25,行数增加 409 行;从 4 月 28 日到 6 月 16 日增加 266 行,从 6 月 16 日到 7 月 7 日增加 13 行,之后再增加 130 行。这些数字只能说明源码体量的变化,不能直接换算成开发时间或代码工作量;「7 月阶段更接近收尾」只是根据体量和功能槽变化作出的概括,不是台账原话。

功能槽的变化,比行数更说明问题

跳转表的格子数:42 → 46 → 46 → 47。6 月 16 日那一跳加了四格,全部是日期与时间:取日期、置日期、取时间、置时间。与此同时,12 号「报版本号」那一格里的功能被删掉了,从 6 月 16 日起,内核里再也没有 VERSION 这个标号(标号是给代码位置起的名字,方便在别处按名字引用它),腾出来的 12 号位置给了「清键盘缓冲」(FLUSHKB)。一个 CP/M 定义过、86-DOS 1.00 也占着位的功能,就这样从表里消失了。

到了 1.25,槽位变化继续:25 号功能由 CURDRV 改名为 GETDRV;28 号由 WRTPROT 换成 GETFATPTDL,用于返回 FAT 和磁盘参数表相关信息;末尾新增 46 号 VERIFY,用于写盘后的校验。1.25 的修订台账在 1.20 条目中还记下了「separate FAT from DPT; Kill SMALLDIR」。

四个时间点之间,和 CP/M 语义对齐的槽位从 34 个变成 32 个。不过变化并不是每个阶段都发生:6/16 和 7/7 的分发表相同,主要变化集中在 1.00 到 6/16,以及 7/7 到 1.25。可以确认的是,常用接口被保留的同时,部分旧语义被替换,新的系统需求被加入。

两条批注与源码时间线

日期时间功能在清单上留下了两条批注,但两条批注对应的材料层次不同。

第一条出现在 6 月 16 日打印的 1.00 基线清单页下沿:「PC-DOS 1.0 NO DATE/TIME FUNCTIONS」。第二条出现在 7 月 8 日打印、文件创建于 7 月 7 日的快照页下沿:「DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK」。

源码本身显示,1.00 的 42 个槽里没有日期时间接口,而 6 月 16 日快照已经新增 42–45 四个入口,并且包含完整的取值和设置例程。可以确定的是:接口在 1.00 基线之后、6/16 快照中已经出现,7 月的批注又把这项工作明确联系到 IBM 时钟。至于第一条批注究竟是对哪一份 PC-DOS 成品的回看、两条字迹是否由同一人写下,现有页面没有提供更完整的说明;本文不再从批注之外推断更多。

两个开关的命运

SMALLDIR 的状态可以沿四个时间点追踪:1.00 为 1,6 月 16 日和 7 月 7 日为 0;到 MS-DOS 1.25,定义已经消失,修订台账在 1.20 条目中写着 Kill SMALLDIR(1981 年 12 月 31 日)。从保留旧目录格式,到关闭编译期开关,再到删除相关定义,这条线清楚显示了一个兼容选项如何退出。

另一个开关只出现在 7 月 7 日快照中:EXCESS 的值为 511,注释是 Make 86-DOS artificially larger。它在数据区预留 511 字节的填充,但现有注释没有说明为什么要这样做。可以确认的只有三点:它确实存在,只出现在 7 月 7 日快照中,而且作者明确把它标为人为增大系统长度的操作。

这条时间链能回答什么

这组材料同时提供了四个源码时间点和一份版本间差异清单。因此,哪些功能在某个时间点已经出现、哪些开关后来被关闭,可以回到具体文件、行号和脚本逐项核对;至于改动动机,仍要依靠批注、当事人材料或明确标注为推断。

定版之后:四个点,都是可装配的源码 4/28 定版 86-DOS 1.00 3,622 行 6/16 快照 PC-DOS 1.00 3,888 行 7/7 快照 PC-DOS 1.00 3,901 行 1.25 MS-DOS 4,031 行 功能槽:42 → 46 → 46 → 47 6/16 一口气加了四个日期时间槽, 同时删掉了报版本号那个槽, 位置给了「清键盘缓冲」。 与 CP/M 对齐的槽:34 → 33 → 33 → 32 每往前一步,就有 CP/M 的功能号 被换掉或被删掉。 兼容层不是原样保存,是有取有舍。 两个开关的兴衰(1.00 → 6/16 → 7/7 → 1.25) 兼容老式 16 字节目录的开关 SMALLDIR:1 → 0 → 0 → 被删(1.25 台账原话 Kill SMALLDIR) 让内核「人为变大」的填充量 EXCESS:无 → 无 → 511 → 无(7/7 快照独有,注释原文 Make 86-DOS artificially larger) 行数口径:Python splitlines();字节数取文件实际大小;全部由 vol1_evolve.py 输出
图 6 四个时间点的行数、功能槽与两个开关的走向,数字全部由脚本算出。
本章证据
档位事实出处复核方式
数据实证四个时间点的行数、字节数、功能槽数、开关取值、空壳功能的去向、对齐槽数走向vol1_evolve.py 与 vol1_cpm.py 输出splitlines 计数;DISPATCH 表由脚本提取;四点源码均在 3_source_code 下
源码实证6/16 起 VERSION 标号消失、12 号槽改为 FLUSHKB;新增四格日期时间功能且实现完整PC-DOS_1.00_dev/86DOS_1981-06-16.ASM(GETDATE 实现见该文件 :3373–3384)脚本比对 + 直读
源码实证1.20 台账「Add time to FCB; separate FAT from DPT; Kill SMALLDIR」MS-DOS_1.25/MSDOS.ASM:23 附近的修订台账(共 20 个锚点)直读
源码实证EXCESS 只在 7/7 快照出现,值 511,注释 Make 86-DOS artificially largerPC-DOS_1.00_dev/86DOS_1981-07-07.ASM:19全树 grep 确认四个点的分布
外部资料两条手写批注的原文与所在页的打印日期archive.org item paterson_listings 的 images/listings.jpg 与 bundle04 第 1 页扫描件扫描件裁切放大后逐字确认;页脚元数据与仓库 README 交叉核对

7 · 开机那一刻

横幅上写着一家西雅图的小公司

内核里有一段开机时要打印的文本,位于第 186–193 行。它包含版本号和一行版权声明:「Copyright 1980,81 Seattle Computer Products, Inc.」。对 1981 年 4 月 28 日的 1.00 源码来说,屏幕上出现的是 Seattle Computer Products 的名称,而不是微软。

横幅与它的开关

HEADER: DB 13,10,"86-DOS version 1.00"

IF DSKTEST
DB "D"
ENDIF

DB 13,10
DB "Copyright 1980,81 Seattle Computer Products, Inc.",13,10,"$"
86DOS.ASM:186–193,全文照录

开头的 13,10 是回车和换行的字符编码;末尾的 $ 则是这套打印约定使用的字符串终止符,打印例程遇到它就停止。DSKTEST 打开时,版本字符串后还会插入一个 D;这说明调试版本会在启动横幅上留下可识别的标记。

内建的行编辑器

第 170–184 行定义了 ESCTAB,其中有十组两字母控制序列,分别对应复制或跳过字符、复制或跳过到指定字符、复制整行、删除整行、重新编辑、退格,以及进入和退出插入模式。10 号功能读取一行时,行编辑器可以利用这些序列修改输入,错误处理因此由系统接口的一部分来承担,而不必由每个上层程序各自实现。

这一刻在历史里的位置

按材料上的日期排列,4 月 28 日是 1.00 定版日;5 月 5 日是 Paterson 解释过的文件时间戳,可能对应上传日期;6 月 16 日和 7 月 8 日分别是两批清单的打印日期,后一批文件创建于 7 月 7 日。8 月 IBM PC 出货时,使用的是由 86-DOS 演化而来的 PC-DOS 1.00。这些日期属于不同文件和清单,不能理解为同一个 86DOS.ASM 连续保存到了 8 月。材料还延伸到 1982 年;Paterson 当时已经在微软,清单中的后续部分包括他负责的 BASIC 编译器运行库。

本册停在 1.00 定版这个节点:源码文件约六万字节,内核集中在一个文件里,版权声明写着 Seattle Computer Products。IBM 的适配、微软取得相关权利之后的演化,留给后续各册。

本章证据
档位事实出处复核方式
源码实证开机横幅两行文字与 SCP 版权声明;DSKTEST 调试字母;$ 结束符86DOS.ASM:186–193直读
源码实证内建行编辑器的十项命令表(两字母指令)86DOS.ASM:170–184直读;表长由 :184 的 ESCTABLEN 自行计算
外部资料清单时间线(4/28 定版、5 月上传、6–7 月快照、8 月 IBM PC)、Paterson 在微软负责 BASIC 运行库Tim_Paterson_24Apr2025_emails.txt + 微软开源博客邮件与博客原文

8 · 交房图纸

结论,以及材料明确无法回答的问题

回到系列的问题:一个据称七周写出来的系统,怎么长成统治 PC 十几年的 MS-DOS。这一册只回答它的前半句,而且只是前半句里的一小段:把时间停在 1981 年 4 月 28 日,看它当时是什么样子。读完之后,那句话本身也需要修正一下。

源码证实了这五件事

从源码和逐槽对账可以得到五个较稳妥的结论。第一,程序可见的接口明显沿用了 CP/M 约定:CALL 5、与 CP/M 2 对齐的 34 个功能槽、36 字节 FCB,以及以 $ 结尾的字符串都保留了下来。第二,文件系统内部采用了另一套组织方式:FAT 链、32 字节目录项、可变记录大小和位打包日期字段。第三,置中断向量、置新基段等扩展功能,说明 8086 的原生需求已经进入接口。第四,版本台账、调试开关、分开的调用上限和入口处的栈保护,都是源码中可以直接看到的工程安排。第五,兼容并非整张表原样保留:4 个槽被判为征用,另有 1 个槽与 CP/M 同位但更换了对象,分发表内还有 5 个空壳;在后续时间点上,对齐槽数量从 34 个降到 32 个。

源码没有说的事

「七周」这个说法,现有材料既不能证实,也不能证伪。清单里最早的版本锚点是 0.34,日期为 1980 年 12 月 29 日;更早的 QDOS 阶段没有出现在这批纸面材料中。材料的边界到哪里,结论就到哪里。FAT 格式更早的来历也不在本册。1.00 的注释完整描述了它的布局和算法,却没有说明这套设计从哪一版开始形成。版权行能证明当时的权利归属写着 Seattle Computer Products,不能单凭代码推出公司之间的交易、渠道或价格。6 月和 7 月的快照已经进入 PC-DOS 开发阶段,但要判断哪些改动专门服务于 IBM PC,还需要逐条读完两个快照之间的 1,840 行差异清单,那是第二册的工作。

把本册的证据等级说清

本册的证据分成四类:源码实证,指向文件和行号;数据实证,由 _evidence/vol1/ 下脚本计算;外部资料,包括清单扫描、官方说明和 CP/M 参考资料;作者推导则明确标注为综合判断。vol1_fat_check.py 属于数据实证:它用 Python 模拟并检查 FAT12 的打包算法,并没有启动 86-DOS。至于把源码汇编、写入现代 IBM PC 适配复刻盘并在 DOSBox-X 中启动,那是公开项目 phyous/DOS-1.0 的执行路径,使用的盘来自 TheBrokenPipe 的现代复刻工程,不是 1981 年的历史发行物;完整的执行证据和限制见第四册。

后续的两条线索

同一棵素材树里还保存着 0.11 社区重建源码:内核 2,259 行,比 1.00 少 1,363 行。它可以作为早期状态的对照,但要逐行说明 0.11 到 1.00 增加了什么,还需要另写比较脚本;现有材料没有为这两个版本提供现成的差异清单。

另一条线索是 86DOS.DIF。它列出了 1.00 与 6 月 16 日快照之间的改动:按本册口径共 1,840 行,其中含 30 个打印换页符;按 wc -l 统计则是 1,810 行。文件头明确写着两个比较对象:86DOS.ASM created: 1056 05-MAY-1981 与86DOS.A86 created: 1425 16-JUNE-1981。第二册会把这份清单从总量拆到具体改动,讨论哪些变化是保留、替换、新增或移除。

本章证据
档位事实出处复核方式
作者推导五条结论(接口 CP/M 化 / 存储 FAT 化 / 原生层起步 / 工程习惯老练 / 兼容有选择)由第 3–6 章各证据条目合成综合判断;分项证据见对应章节
作者推导0.11 内核 2,259 行、1.00 与其相差 1,363 行;86DOS.DIF 共 1,840 行(splitlines 口径,含 30 个换页符)vol1_evolve.py 输出 + 差异清单文件行数统计splitlines 口径;0.11 为社区逆向重建,法律灰色,已声明只作对照

术语速览

打印清单(listing) 汇编器把源码打印在连续纸上时,每行带上行号、地址和注释的产物。当年程序员就在纸上读代码、改代码。
FCB 文件控制块。程序与内核之间关于一个文件的 36 字节合同:主体 32 字节,另有 1 字节记录号和 3 字节随机记录号。CP/M 提供了这套约定,DOS 沿用并扩展。
FAT 文件分配表。盘上记录文件的下一簇在哪儿的链式结构,是 DOS 文件系统的基础。
簇(cluster) 盘上分配空间的最小单位。FAT 的每个表项记录一个簇的下一站。
FAT12 每个表项 12 位的 FAT,两个表项打包进三个字节。后来的 DOS 文献通常把这种格式称为 FAT12。
目录项 目录里描述文件的一条记录。1.00 的目录项为 32 字节,包含文件名、首簇号、文件大小和写入日期。
位场(bit field) 把几个小数字塞进同一段二进制位里。日期字段就是按日 5 位、月 4 位、年 7 位分配。
BDOS / CALL 5 CP/M 的操作系统核心及其调用入口。程序把功能号放进寄存器,再执行 CALL 5 请求服务。
INT 指令 8086 的软件中断指令。86-DOS 的另一条系统调用入口通过中断向量进入内核。
跳转表(dispatch table) 一张地址清单。功能号乘二得到表内偏移,跳到对应表项就能找到处理例程。
空壳(stub) 占着功能号、但实现只返回默认值的函数。1.00 的分发表内有 5 个空壳槽,共享 MOV AL,0 / RET。
寄存器 CPU 内部用于暂存数字的小型存储单元,各有固定名称。程序与系统之间通常通过寄存器传参。
中断 送给 CPU 的控制信号。CPU 按向量跳到系统代码,处理完再返回原处。
可重入 代码执行到一半被打断后,仍能安全地再次进入。1.00 默认使用共享栈,因此入口先关中断;测试开关才会启用另一套栈。
快照(snapshot) 某个时间点保存下来的整套源码。1.00 之后,本系列还使用了 6/16 和 7/7 两份快照。
修订台账 源码文件开头的版本记录。86-DOS 的台账从 0.34 排到 1.00,每行写日期和改动摘要。
Provenance(来历链) 证据从保存、扫描、转录到发布的来源链。本册指 Paterson 保存清单、团队扫描转录和公开发布过程。
86-DOS / PC-DOS / MS-DOS 同一软件血统在不同产品和发行关系中的名称:SCP 时代称 86-DOS,IBM PC 版本称 PC-DOS,微软面向其他厂商销售的版本称 MS-DOS。

参考文献

[1](SRC)Paterson-Listings 仓库:3_source_code/(引用基准树)
Tim Paterson 提供原始清单;Yufeng Gao、Rich Cini 等考证团队转录;微软 2026-04-28 以 MIT 许可发布。github.com/DOS-History/Paterson-Listings。其中 86-DOS_1.00/86DOS.ASM 是本册行号基准(第一行自证 version 1.00 04/28/81);PC-DOS_1.00_dev/ 下是 6/16 与 7/7 两个快照;MS-DOS_1.25/MSDOS.ASM 是链的末端。
本册用途:全文所有源码实证与四个时间点的脚本比对。
[2](SRC)同仓库 2_printed_files/:打印原件转录(含打印行号)
同上团队;转录自 Paterson 的连续纸清单。bundle_02(7/8 打印的内核快照)、bundle_04(6/16 打印的内核快照)、bundle_08(86DOS.DIF,版本间差异,splitlines 口径 1,840 行)等。文件行号带打印行编号,与定版源码不是一套。
本册用途:本册第 6 章快照链的对照,以及第二册使用的差异清单素材。
[3](DOC)Tim Paterson 与 Yufeng Gao / Richard Cini 往来邮件(2025-04-24)
随 Paterson-Listings 仓库发布的誊本(Tim_Paterson_24Apr2025_emails.txt)。四封往来。Paterson 亲述清单堆高 3.5 英寸、十捆内容清点、5/5/81 只是文件时间戳不对应发布、BASIC 编译器运行库属他在微软的职责范围。
本册用途:第 0 章的证据形态、第 1 章的 5/5 口径、第 7 章的历史位置。
[4](DOC)CP/M 2 功能分发表与功能号清单
seasip.info「BDOS Function Summary」(编译者 Jim Lopushinsky);classiccmp.org 邮件列表留存的 CP/M 2 分发表源码片段。两份来源给出的 CP/M 2 功能编号与名称一致。邮件列表那份片段即 CP/M 2 的 DISTBL 分发表,格式与 86-DOS 的 DISPATCH 表同构。本册只用其编号与名称,不用其代码。
本册用途:第 4 章逐槽对账的外部依据。
[5](WEB)微软开源博客:Continuing the story of early DOS development(2026-04-28)
Stacey Haffner(微软 OSPO 主任)、Scott Hanselman。opensource.microsoft.com/blog/2026/04/28/...。发布说明:45 周年、「迄今为止发现的最早 DOS 源码」、「可以把它看成一个 Git 仓库的打印版提交历史」、考证团队名单、MIT 许可、清单捐给 Interim Computer Museum。
本册用途:第 0 章的发布事实与官方比喻。
[6](DOC)打印清单原件扫描(archive.org item: paterson_listings)
微软发布;扫描件由考证团队制作,其中工位照片摄影 Rich Cini。archive.org/details/paterson_listings。含十捆清单的 PDF/PNG 扫描与四张现场照片。本册引用的三处:6/16 内核清单页(页脚元数据 + 手写批注)、7/8 打印页(手写批注,文件创建于 7/7)、以及 1981 年工位照片。
本册用途:第 0、1、6 章的影像证据;手写批注的原文均经裁切放大逐字确认。
[7](SRC)MS-DOS 官方源码仓库(1.25 / 2.0 / 4.0)
Microsoft,MIT 许可。github.com/microsoft/MS-DOS。本册只用 v1.25 的 MSDOS.ASM 作为快照链末端,其修订台账含 20 个版本锚点,其中 1.20 一行写着 Kill SMALLDIR。
本册用途:第 6 章的链末端与 SMALLDIR 的最终命运。
[8](SRC)86-DOS 0.11 源码重建(社区逆向工程)
DOS-History 社区;README 自述为 reverse engineering,「法律地位可能不明确」。github.com/DOS-History/86-DOS-0.11。重建后可汇编回 SCP 出厂二进制。本册只在第 8 章把它作为系列后续对照,引用其行数(2,259 行,脚本实算)。
本册用途:仅作旁证,不作本册行号基准。法律灰色状态已在正文声明。
[9](WEB)phyous/DOS-1.0:从 Paterson 的源码构建并启动 86-DOS 1.00
phyous(项目作者);汇编器来自 pts/pts-86-dos-reconstruction。github.com/phyous/DOS-1.0。用 asm244l(对 SCP ASM v2.44 的重新实现)把 86DOS.ASM 汇编成 5,446 字节的内核,并将与现代 IBM PC 适配盘的 49 字节级差异写回后在 DOSBox-X 中启动;仓库提供 build.sh 与 run.sh。这里的构建项目是现代复现流程,其中使用的 IBM PC 适配盘来自 TheBrokenPipe/86-DOS_PCAdaptation,不是 1981 年历史发行物。
本册用途:第 8 章:编译出的内核大小;完整的执行证据和限制见第四册。
证据边界本册所有行号引用均按基准树直读核对(第 4 章跳转表、第 5 章 FAT 注释与 FCB 定义、第 3 章入口代码等段落为直接读取,非转述);四个时间点的行数、字节数、功能槽数与对齐格数全部由 _evidence/vol1/ 下的脚本算出,命令与输出可复跑。两条手写批注的原文经扫描件裁切、放大、逐字确认。「七周」传说、FAT 更早来历、公司间交易细节、IBM 定制层的边界不在源码文本内,本册未作断言。行数统计口径:Python splitlines(),wc -l 因末行无换行符会少 1。本页为自包含 HTML:无外部请求,无脚本,图片和样式均内嵌。