_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 年(具体月份无据)。不要拿这两处核对本册行号。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 编译器运行库。
这批材料的价值不只在于年代早,更在于它把版本过程保留下来:同一内核在不同时间点的快照、记录文件差异的清单(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 里写明「法律地位可能不明确」。本册只在需要对照时才提它,每次都标注,也不拿它的行号当基准。
| 档位 | 事实 | 出处 | 复核方式 |
|---|---|---|---|
| 外部资料 | 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 | 直读 |
打开 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 源码里能直接看到的最早一层版本记录。
这九行可以当作一份简短的版本时间表。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 release | 86DOS.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 页) | 扫描件裁切放大后逐字核对,避免缩略图误读 |
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 没有直接说明「为什么采用单文件」。从源码本身看,模块边界主要依靠标号、数据区和调用关系来维持。
| 档位 | 事实 | 出处 | 复核方式 |
|---|---|---|---|
| 源码实证 | 全文唯一分区记号在第 3206 行,其后为 DATA AREA | 86DOS.ASM:3206 | vol1_map.py 分节标记扫描 |
| 数据实证 | 源码文件 3,622 行 / 60,124 字节 / 代码 3,205 行 + 数据 417 行 | vol1_map.py 输出 | splitlines 计数与实际文件大小;wc -l 少 1 的原因已说明 |
| 外部资料 | ASM + HEX2BIN 的两步构建流程 | Paterson-Listings README「Compiling/Assembling」一节 | README 原文 |
从应用程序的公开调用界面看,86-DOS 提供两条进入内核的路径:传统的 CALL 5,以及通过中断进入的 COMMAND 入口。中断可以理解为 CPU 按约定跳到系统代码处理请求,处理完再返回原处。两条路径最后都进入 SAVREGS(第 219 行)之后的保存现场和分发流程,但可接受的功能号范围不同:CALL 5 的上限是 36(0–36,共 37 个),中断入口的上限是 41(0–41,共 42 个)。
在 CP/M 中,程序通常把功能号放进 C 寄存器,再执行 CALL 5;地址 5 是 BDOS 的入口。许多 CP/M 程序把这个调用约定直接写进了机器码。如果 8086 上的新系统希望接住这批程序,就必须提供相同的入口位置和寄存器约定。至于入口之后如何实现,应用程序并不需要知道。
这段入口代码先处理 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 双栈注释;出口九个寄存器 + IRET | 86DOS.ASM:212 / :25–29 / :271–300 | 直读 |
| 外部资料 | CALL 5 是 CP/M 时代系统调用入口的约定俗成 | CP/M 2 参考资料(seasip.info BDOS 汇总;classiccmp 邮件列表的 CP/M 2 分发表片段) | 该约定在 CP/M 文档中反复出现,见参考文献 [4] |
功能号进入 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。
「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 假设、内存布局、参数格式和错误处理方式仍可能不同。因此,这份对账衡量的是接口层的对应关系,不是完整的程序兼容率。
42 格里有 5 格的函数体只有两行:把 AL 清零,然后返回。上面七个标号中,GETIO 与 SETIO 没有被分发表引用,真正占着功能槽的是另外 5 个,其中包括 12 号的 VERSION。在 1.00 里,「请报出你的版本号」得到的答案是 0。
从行为看,VERSION 是一个兼容占位:编号先保留下来,函数体只返回 0。第五、六章会追踪这些空壳的去向,有的后来长出了新实现,有的则被删除。
| 档位 | 事实 | 出处 | 复核方式 |
|---|---|---|---|
| 源码实证 | DISPATCH 跳转表 42 格,注释分为标准与扩展两段 | 86DOS.ASM:302–346 | vol1_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→32 | vol1_cpm.py 输出 | 判定口径写在脚本头部:以「功能对象是否相同」为准,名字不同不影响判定 |
上一章有一个槽位没有展开:27 号功能在 CP/M 中返回分配图地址,在 86-DOS 1.00 中则返回 FAT 指针。这个差别正好把两套文件系统的内部组织区分开:CP/M 用静态位图记录盘块是否占用,86-DOS 用 FAT 把一个文件的簇串成链。应用程序看到的调用编号仍然相近,盘上的数据结构已经换了一套。
程序与内核之间关于文件的部分状态,放在 FCB(文件控制块)里。1.00 的主体区域占 32 字节,后面还有 1 字节的当前记录号 NR 和 3 字节的随机记录号 RR,合计 36 字节。FCB 的前部布局沿用了 CP/M 约定,但 RECSIZ、FDATE 和 FIRCLUS 等字段已经体现出 86-DOS 自己的文件系统设计。
前 12 字节保存盘号和 8.3 文件名,这是 CP/M 程序熟悉的布局。后面的字段则加入了 86-DOS 的信息:RECSIZ 允许记录大小变化,FDATE 保存最后写入日期,FIRCLUS 保存文件的首簇号。EXTENT 这个旧名称仍在,FIRCLUS 这样的新字段也已经出现;同一个结构里可以同时看到兼容接口和新的盘上组织方式。
FAT 是文件分配表。源码注释把每个表项称为一个 allocation unit;为方便叙述,本文沿用常见说法,把它称作簇。表项中的值通常是下一个簇的编号,文件因此可以由多个簇串成一条链。1.00 的每个表项占 12 位,两个表项打包进三个字节。这会让读取和写入都多一步位操作,源码注释随后给出了完整算法:
最容易卡住的是第一步:为什么要乘 1.5?因为每个表项占 12 位,也就是 1.5 字节。第 N 个表项从第 1.5N 个字节开始;N 为奇数时,起点落在一个字节的高、低半区之间,所以要把取出的 16 位值右移四位。源码用移位和加法代替乘法,最后用 AND DI,0FFFH 保留 12 位。这种两个 12 位表项共用三个字节的布局,后来通常被称为 FAT12。
第一次读 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 年。把多个小字段塞进同一组二进制位,叫作位场。
文件头第 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 系统自己的需要展开。
| 档位 | 事实 | 出处 | 复核方式 |
|---|---|---|---|
| 源码实证 | 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 打包算法这一条路径,不汇编源码也不启动系统 |
1.00 定版之后,同一条材料链上还留下了三个可比较的时间点:6 月 16 日快照、7 月 7 日快照,以及后来的 MS-DOS 1.25 源码。把四个文件放在一起,先比较行数和字节数,再比较分发表和开关,可以看到系统在定版之后增加了哪些能力,又放弃了哪些兼容路径。
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 日快照中,而且作者明确把它标为人为增大系统长度的操作。
这组材料同时提供了四个源码时间点和一份版本间差异清单。因此,哪些功能在某个时间点已经出现、哪些开关后来被关闭,可以回到具体文件、行号和脚本逐项核对;至于改动动机,仍要依靠批注、当事人材料或明确标注为推断。
| 档位 | 事实 | 出处 | 复核方式 |
|---|---|---|---|
| 数据实证 | 四个时间点的行数、字节数、功能槽数、开关取值、空壳功能的去向、对齐槽数走向 | 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 larger | PC-DOS_1.00_dev/86DOS_1981-07-07.ASM:19 | 全树 grep 确认四个点的分布 |
| 外部资料 | 两条手写批注的原文与所在页的打印日期 | archive.org item paterson_listings 的 images/listings.jpg 与 bundle04 第 1 页扫描件 | 扫描件裁切放大后逐字确认;页脚元数据与仓库 README 交叉核对 |
内核里有一段开机时要打印的文本,位于第 186–193 行。它包含版本号和一行版权声明:「Copyright 1980,81 Seattle Computer Products, Inc.」。对 1981 年 4 月 28 日的 1.00 源码来说,屏幕上出现的是 Seattle Computer Products 的名称,而不是微软。
开头的 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 + 微软开源博客 | 邮件与博客原文 |
回到系列的问题:一个据称七周写出来的系统,怎么长成统治 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 为社区逆向重建,法律灰色,已声明只作对照 |
DISTBL 分发表,格式与 86-DOS 的 DISPATCH 表同构。本册只用其编号与名称,不用其代码。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 年历史发行物。_evidence/vol1/ 下的脚本算出,命令与输出可复跑。两条手写批注的原文经扫描件裁切、放大、逐字确认。「七周」传说、FAT 更早来历、公司间交易细节、IBM 定制层的边界不在源码文本内,本册未作断言。行数统计口径:Python splitlines(),wc -l 因末行无换行符会少 1。本页为自包含 HTML:无外部请求,无脚本,图片和样式均内嵌。