边界

从四个版本的源码,看 86-DOS 如何把 CP/M 留在身后

《DOS 起源考古》第三册 · 构建自 _build/content_boundary.py(2026-09-27)

目次

  1. 为什么要做一套 8086 操作系统
  2. 把接口对应关系量化
  3. 槽 12:从 VERSION 到 FLUSHKB
  4. 日期:新增四个系统调用
  5. 6/16 与 7/7:分发表未变
  6. 1.00 的空壳从哪里来
  7. 槽 28:从 WRTPROT 到 GETFATPTDL
  8. 结论:兼容边界如何变化

为什么要做一套 8086 操作系统

1980 年春天,Seattle Computer Products(SCP)有一块难以销售的 8086 板卡。

那是 SCP 的 S-100 总线 8086 CPU 板,1979 年 11 月开始出货,比 IBM Personal Computer 早了将近两年。S-100 是当时连接 CPU、内存和外设板卡的一种总线标准;板卡使用的是 Intel 8086。

问题在于,8 位机时代的主流操作系统 CP/M 还没有可用的 8086 版本。Digital Research 正在开发 CP/M-86,但 SCP 不能一直等待:没有操作系统,板卡就缺少可以运行的应用软件。

1980 年 4 月,Tim Paterson 开始为 SCP 编写替代方案。QDOS 0.11 在同年完成,之后改名为 86-DOS。

接口继承与代码复制要分开讨论

早期 DOS 与 CP/M 的关系长期存在争议。现有公开材料至少把两个问题分开了:Bob Zeidman 的 2016 年源码和二进制比对报告称没有发现 DOS 复制 CP/M 代码的证据,同时确认大量系统调用号一致(文献 [2]);OS/2 Museum 则从 8086 分段架构、磁盘管理和工具命名等角度指出 86-DOS 与 CP/M 的内部实现差异很大(文献 [3][4])。

这些材料支持把「接口继承」与「代码复制」分开讨论,但不能替代法律判断。本文只分析源码、二进制和当事人材料能够支持的工程结论。

为什么系统调用号仍然相同?Paterson 在 1994 年写给《Microprocessor Report》的信中给出了一种解释。以下引文是译文,英文原文存于 _evidence/vol3/paterson1994.txt:

为了让主要软件厂商把他们的产品从 8080/Z80 移植到 8086,我认定必须把这件事做得尽可能容易。我已经写好了一个 Z80 到 8086 的源码翻译器。我的计划是:开发者把一个 8080 CP/M 程序送进翻译器跑一遍,就是移植到 8086 所需要的全部工作。

这句话里的关键词是翻译器(TRANS)。Paterson 先写了把 Z80 汇编源码翻成 8086 汇编源码的程序,那么要让翻译后的程序尽可能不引入系统相关的修改,它所依赖的操作系统接口就必须与 CP/M 一致:

应用程序请求操作系统服务所用的接口,在套用翻译规则之后,必须与 CP/M 完全一致。(以上为当事人材料。)

本文可以从这段当事人材料作出一个工程上的推断:接口保持一致,TRANS 翻译出的程序才有机会少改动地运行。因此 86-DOS 保留了 CP/M 的功能号、入口地址(CALL 5)和部分 FCB 布局;Paterson 把这些称为「所有应用可见的元素」。

他同时明确说,文件存储机制与 CP/M 不同,并估计这部分约占 86-DOS 代码的 80%。这个比例是 1994 年的事后估计,不是本文重新测量的结果。本文后面的量化分析只讨论接口槽位,不把这个比例当作源码统计。

也要把反面一并摆上:保留功能号、入口地址和部分数据结构,只能说明迁移约束被认真对待,推不出「所有 CP/M 程序都能无修改运行」,1994 年 Wharton 的反驳正是冲着这一点来的(文献 [1])。

兼容目标留下的旁证

如果接口兼容只是宣传口号,源码和工具中不一定会留下对应结构。现有材料恰好提供了几类旁证。

0.11 社区逆向重建版的源码里有一个叫 RDCPM.A86 的工具。单看文件名只能猜它是「读 CP/M」,但它的头部把用途写得很清楚(这是重建版源码实证,不是原始 1980 年源码原件):

FCB:	EQU	5CH
FCB2:	EQU	6CH
OPEN:	EQU	15
CLOSE:	EQU	16
MAKE:	EQU	22
DELETE:	EQU	19
SEQWRT:	EQU	21
SETDMA:	EQU	26
GETDRV:	EQU	25
PRINTBUF:EQU	9
SYSTEM:	EQU	5

SYSTEM EQU 5 是 CP/M 那个著名的 5 号调用入口;FCB EQU 5CH 与 FCB2 EQU 6CH 是 CP/M 约定的程序段前缀里两个默认文件控制块的位置;而 9(打印字符串)、15(开文件)、16(关文件)、19(删文件)、 21(顺序写)、22(建文件)、25(取当前盘)、26(设 DMA 地址)这一串功能号,与 CP/M BDOS 逐一对应。

这份重建版程序把 CP/M 的调用约定具体写进了工具源码。它不能单独证明原始 1980 年源码的全部细节,但与 Paterson 的 1994 年说明、CP/M 对齐的接口和 TRANS 放在一起,可以支持一个较窄的结论:软件和数据迁移是 86-DOS 早期设计的重要目标之一。

证据档位说明:0.11 是社区逆向重建版(法律灰色),本册仅把它用作起点旁证,不作行号基准,也不把它当成原始 1980 年源码。上述 EQU 定义及其与 CP/M BDOS 的对应关系,是重建版源码实证;设计动机另由 Paterson 的当事人材料支持。

接口保留并不等于完整兼容

CP/M 的分发表里,有些槽位在 86-DOS 的世界里根本没有对应的东西。

CP/M 12 号功能是 Return Version Number,返回版本号。CP/M 有版本概念,86-DOS 在 1981 年 4 月定版时也照着放了一个 VERSION。

但它什么都不做。

这正是下一章要量化的现象:接口槽位保留下来,并不代表每个槽位都已经实现同样的功能。

把接口对应关系量化

「86-DOS 兼容 CP/M」仍然过于宽泛。接口兼容至少涉及功能号、参数约定、返回值、副作用、数据结构和错误语义六个层面;仅凭功能号相同,不能推出程序一定能运行。

本册只量化其中一个层面:四份源码的 DISPATCH 表里,某个槽位是否与 CP/M 2 的同编号功能保持相同语义。先把口径限定清楚,再使用这组数字。

度量对象:CP/M 对齐槽

本册使用一个限定指标:CP/M 对齐槽(CP/M-aligned dispatch slots)。判定条件只有两条:功能号相同,且服务语义相同。这里的 semantic id 是本文建立的规范化标签,不是任一源码中的原生字段;名字不同不影响判定,例如 CP/M 的 Return Current Disk 与 86-DOS 的 CURDRV 可以归为同一语义。

判定口径分五类:

类别定义
对齐语义 id 相同
征用语义不同,但 CP/M 在该槽有定义
填空CP/M 2 在该槽未定义
超出86-DOS 有、CP/M 表止于 40,属新增
同位换对象槽位号不变,但服务对象发生变化

例如,CP/M 7 号功能是「读 I/O 字节」,86-DOS 的 7 号槽却是 RAWINP,即无回显的控制台输入(0.80 版加入)。虽然编号相同,服务语义不同,所以归入「征用」而不是「对齐」。

档位声明:CP/M 侧的分发表来自 seasip.info 的 BDOS 功能汇总与 classiccmp 邮件列表留存的 CP/M 2 BDOS 分发表源码片段,属外部资料档(二手转录)。86-DOS 侧的分发表是从四个版本的源码里用脚本提取的,属源码实证。两侧档位不同,不能混为一谈。

四个版本,一条时间序列

可用的内核源码快照有四个(另加一个灰色的 0.11 作起点旁证):

版本文件日期
86-DOS 1.0086-DOS_1.00/86DOS.ASM1981-04-28
快照PC-DOS_1.00_dev/86DOS_1981-06-16.ASM1981-06-16
快照PC-DOS_1.00_dev/86DOS_1981-07-07.ASM1981-07-07
MS-DOS 1.25MS-DOS_1.25/MSDOS.ASM1982-04(版本发行月份;不是本表用来定位源码的文件日期)

从每个文件里提取 DISPATCH 表后的 DW 标号序列,得到的功能槽数是:

1.00  42 槽
6/16  46 槽
7/07  46 槽
1.25  47 槽

四个槽数由 _evidence/vol3/vol3_divergence.py 从源码提取,脚本会重新输出同样的结果。它们构成了本册的时间序列:先看接口有多大,再看每个位置的语义是否发生变化。

边界在哪里动

把四个版本逐槽比对,观察到的变化如下;未列出的槽位在这四份 DISPATCH 表中保持同名:

区间变化
1.00 → 6/16槽 12 VERSION → FLUSHKB;新增槽 42–45
6/16 → 7/07零改动
7/07 → 1.25槽 25 CURDRV→GETDRV;槽 28 WRTPROT→GETFATPTDL;新增槽 46 VERIFY

三个区间共出现 8 个发生变化或新增的槽位:12、25、28、42、43、44、45、46。其中 1.00→6/16 有 1 个改写和 4 个新增,7/07→1.25 有 2 个改写和 1 个新增。其余槽位在这四份分发表中保持同名;这只说明分发表指向未变,不代表内核内部其他代码没有变化。

源码还显示,同一张分发表由两条入口共享,但两条入口的上限不同。 COMMAND:(中断入口)用 CMP AH,MAXCOM 判范围;ENTRY:(CALL 5 入口,注释原句是「This entry point doesn't get as many calls」)用 CMP CL,MAXCALL 判范围。两个常量在 1.00 与 6/16 里分别是 MAXCOM 41 → 45、MAXCALL 36 → 36。

CP/M 兼容入口的上限从 1.00 到 6/16 一步没动,仍是 36;上移的是中断入口。把链的末端也摆上桌:1.25 里 MAXCALL 仍是 36、MAXCOM 46(MSDOS.ASM:63–64),兼容通道的天花板从 1.00 到 1.25 没有动过,46 号 VERIFY 一样进不了 CALL 5 这扇老门。新增的 42–45(日期时间)与 46(VERIFY)全部落在 36 之外,它们从一开始就不在 CP/M 兼容通道的服务范围里。本文因此称它们为「分发表新增的扩展入口」,而不笼统称为「新增系统调用」:存在于 DISPATCH 表,与所有入口都能调到,是两回事。 OS/2 Museum 对 CALL 5 分派器的描述与源码吻合:它调整栈帧、把 CL 移到 AH,再进入与中断入口共享的分发路径(文献 [4])。

从槽位变化看,接口修改主要集中在 1.00 → 6/16 这一段。

这段时间与 IBM PC 适配工作的时间线重合:Paterson 在 1981 年 5 月加入微软;他在 1994 年的信中说,Bob O'Rear 先完成了让 86-DOS 在 IBM 机器上运行的主要工作(文献 [1])。因此,6/16 和 7/07 快照可以放在这段适配背景下阅读,但槽位变化本身不能证明每一处改动都由 IBM 需求直接触发。

本册问题与既有研究的关系

现有公开材料主要讨论三类问题:DOS 是否复制了 CP/M 的代码、86-DOS 与 CP/M 的接口和移植细节,以及 Paterson 清单的发现与恢复。本册采用相同的源码和外部资料,但把问题限定为「四个源码时间点中的分发表语义如何变化」。

这不是对既有研究的替代,而是换一个可复核的切口:把「兼容」拆成槽位、功能号和语义对应关系。四个槽数为 42、46、46、47;其中 6/16 到 7/07 的分发表保持不变。

源码实证四十五年后的四次清点:42 → 46 → 46 → 47依据 _evidence/vol3/vol3_divergence.py 对四份源码 DISPATCH 表的提取结果1.00 42 槽1981-04-28 定版一处改写,四处新增6/16 46 槽槽 12 换了用途,尾部新增 4 槽零改动7/07 46 槽与 6/16 逐槽一致两处改写,一处新增1.25 47 槽槽 28 换了用途,新增 VERIFY图 1:四个版本的功能槽总量。变化集中在 1.00→6/16 区间;6/16→7/07 逐槽一致,但同期 CHKDSK 增长 51 行,不能据此推断开发停滞。

槽 12:从 VERSION 到 FLUSHKB

槽 12 是四份源码中最清楚的一处语义替换:1.00 指向 VERSION,6/16 改为 FLUSHKB。

1.00 的实现

在 86-DOS 1.00(1981-04-28)的第 348 行,源码是这样的:

VERSION:
GETIO:
SETIO:
WRTPROT:
GETRDONLY:

五个标号依次排列,中间没有指令,随后共享 MOV AL,0 / RET。因此,1.00 的 12 号功能虽然沿用了 CP/M 的「返回版本号」编号,实际只返回 0,并没有提供可用的版本号。完整的空壳簇会在第 5 章统计。

6/16 快照中的新实现

从差异清单 File 1 的 5 月 5 日基线算起,42 天后的 6 月 16 日快照中,同一个槽位变成了这样。若从 1.00 定版日 4 月 28 日计算,则相差 49 天;本文沿用差异清单的 5/5 基线口径。

FLUSHKB:
	XOR	BX,BX
	MOV	ES,BX		;Select segment 0
	SEG	ES
	MOV	B,[41AH],1EH	;Reset KB queue head pointer
	SEG	ES
	MOV	B,[41CH],1EH	;Reset tail pointer
	MOV	AH,AL

6/16 文件中不再出现 VERSION 标号。1.00 的空壳簇共有 7 个标号,6/16 的槽 12 则指向 FLUSHKB;后者把键盘缓冲指针写入 IBM PC BIOS 数据区。

那两个地址是什么

[41AH] 和 [41CH] 写在段 0 里,即绝对地址 0040:001A 与 0040:001C。

这是 IBM PC BIOS 数据区的固定位置(外部资料 [9][11],两个来源一致):

地址含义
0040:001A键盘缓冲区头指针(2 字节)
0040:001C键盘缓冲区尾指针(2 字节)
0040:001E键盘缓冲区本体(32 字节,可存 16 个按键)

两条指令各写一个字节 1EH 到 0040:001A 与 0040:001C,B 即 byte,不是字写入。按 IBM PC BIOS 对这块缓冲区的约定,头尾指针相等就表示队列为空;若高位字节为 0,写入低位 1EH 的效果等同于把指针指回缓冲区起点。但源码本身只写了低字节,本文确认它的设计意图是重置键盘队列,不宣称它做了完整的 16 位指针写入。这是一次标准的「清空键盘缓冲」操作,与 BIOS 数据区的定义完全吻合。

因此,这段实现直接写入 IBM PC BIOS 数据区中的键盘缓冲指针。它使用的是该机型的固定内存布局,没有经过可移植的设备接口。

按第 1 章的 CP/M 2 对齐口径,四个时间点的对齐槽数为 34 → 33 → 33 → 32,超出 CP/M 2 表的新槽为 1 → 5 → 5 → 6。这个变化是逐槽发生的,没有在某个版本一次性完成。

一个槽位的语义变化

把三份代码的对应关系列出来:

CP/M 12 号槽   =  Return Version Number(返回版本号)
                        ↓
86-DOS 1.00    =  VERSION,一个返回 0 的空壳
                        ↓
86-DOS 6/16    =  FLUSHKB,直写 IBM PC BIOS 数据区

接口位置没有变,变的是它服务的世界。

同一个 12 号槽,从前承担的是兼容 CP/M 所需的版本接口;后来承担的,是 IBM PC 的键盘服务。

6/16 的 MOV AH,AL 还表明,FLUSHKB 会把调用者传入的输入功能号保留下来,随后继续进入相关输入处理路径;源码列出的相关功能号包括 1、7、8 和 10。

1994 年的 Paterson—Wharton 争论提供了历史旁证。Wharton 引用《Undocumented DOS》指出,DOS 的 0Ch 功能并不返回系统版本,而用于键盘读取(文献 [1])。四份源码把这一变化定位得更早:6/16 快照已经使用 FLUSHKB。至于这一替换会影响多少 CP/M 2.2 程序,Wharton 的估算属于外部论证,本文不把它当作源码实验结果。

为什么是 12 号,而不是新开一个槽

一个自然的问题是:既然同期已经在尾部新增 42–45 号槽,为什么不把 FLUSHKB 也放在尾部?

可以确认的是,12 号在 1.00 中只返回 0;在四个快照里,真正新增的日期时间功能被放在 42–46 号等尾部槽位,而 12 号和 28 号后来分别被重新赋予新语义。由此可以提出一种与证据相容的解释:已有空壳可以复用,新能力通常另开槽位。

但源码没有注释说明为什么选择 12 号,所以这只是样本归纳,不是作者明确写下的设计规则。

这一处变化说明了什么

12 号槽从一个只返回 0 的实现,变成了直接写 IBM PC BIOS 数据区的键盘处理。相邻的 11 号 CONSTAT 和 13 号 DSKRESET 在四份分发表中仍保持原有标号。

因此,四个快照呈现的不是一次整体切换,而是少数槽位逐步改变语义。

混合证据12 号槽的位置从未改变,改变的是它服务的世界依据 86DOS.ASM:348、86DOS_1981-06-16.ASM:341、MSDOS.ASM:414;BIOS 数据区布局见文献 [9]CP/M 12 号功能Return Version Number保留兼容位置;实现为空壳86-DOS 1.00 VERSION返回 0 的空壳复用同一槽位;直写 BDA86-DOS 6/16 FLUSHKB直写 0040:001A / 001C改走 BIOSFLUSH 向量MS-DOS 1.25 FLUSHKB改走 BIOSFLUSH 向量源码摘录(依据转录层,非扫描件):左 1.00 第 348–355 行 右 6/16 第 341–347 行86-DOS 1.00 占位VERSION:GETIO:SETIO:WRTPROT:GETRDONLY:SETATTRIB:USERCODE: MOV AL,06/16 掏空复用FLUSHKB: XOR BX,BX MOV ES,BX SEG ES MOV B,[41AH],1EH SEG ES MOV B,[41CH],1EH图 2:在这条版本序列中,12 号功能槽由 CP/M 兼容占位变为 IBM PC 硬件特化代码,最终又被抽象为 BIOS 向量调用。口径:CP/M 侧定义属外部资料档;条带为转录层渲染。
照片 C
照片 C:IBM Personal Computer 5150——12 号槽的新主人。Wikimedia Commons「IBM PC 5150.jpg」,CC BY-SA 3.0

日期:新增四个系统调用

12 号槽展示了旧位置的语义替换;42–45 号槽展示了新功能如何直接获得新的编号。

四个新增槽位

6/16 的分发表尾部多出来四个名字:

GETDATE  42
SETDATE  43
GETTIME  44
SETTIME  45

在 1.00 里它们一个都不存在。1.00 只有 42 个槽,最后一个是 MAKEFCB。

1980 年偏置如何返回给程序

看 6/16 第 3373 行,GETDATE 的实现:

GETDATE: ;Function call 42
	PUSH	CS
	POP	DS
	CALL	READTIME	;Check for rollover to next day
	MOV	AX,[YEAR]
	MOV	BX,[DAY]
	LDS	SI,[SPSAVE]	;Get pointer to user registers
	MOV	[SI+DXSAVE],BX	;DH=month, DL=day
	ADD	AX,1980		;Put bias back
	MOV	[SI+CXSAVE],AX	;CX=year
	XOR	AL,AL
	RET

注意第 3381 行那句 ADD AX,1980 ;Put bias back。

年份在内部以相对于 1980 的偏移保存,返回给调用者时再把 1980 加回去。这个调用路径说明 6/16 如何把内部年份转换成应用程序看到的完整年份。

ADD AX,1980 不能证明 1980 纪元在这一行才诞生,因为 1.00 的目录项注释已经写着 year-1980。它能证明的是:已有的日期编码现在被纳入了系统调用的返回值。

与 IBM 时钟的关系

7 月 8 日打印的清单页(文件创建于 7 月 7 日)写着,日期/时间功能是「为 IBM CLOCK」加的。源码能直接证明的是:6/16 起,内核提供了读取和设置日期时间的调用路径。

初代 IBM PC 5150 主板没有电池供电的实时时钟,开机时 DOS 时间默认为 01JAN1980,需要人工设定;实时时钟成为 IBM PC 的内置配置要到后来的 PC/AT(文献 [12])。但不能反过来据此断言 SCP 机器「没有时钟所以不需要日期功能」;源码只显示 1.00 已有人工输入日期和文件日期字段,却没有面向应用程序的日期时间调用。

1.00 已经有日期字段:目录项注释写明 bit 9–15 是 year-1980,启动时日期由用户输入,写文件时写入目录项(第 65、1902–1903 行)。它缺少的是面向应用程序的日期时间系统调用,以及自动读取时钟的路径;6/16 新增的正是这条调用路径。

两条批注需要与源码快照分开阅读:

第一条批注属于 1.00 基线清单,第二条批注属于 7/7 快照的打印页;不能把第一条直接当作 6/16 源码的注释。86DOS.DIF 和 6/16 源码共同显示,日期时间入口在 5/5 基线之后、6/16 快照中已经出现;7 月批注进一步说明这项工作被当事人联系到 IBM 时钟。批注的具体书写时间和书写者没有更多记录。

1.25:通过 BIOS 向量调用

MS-DOS 1.25 的源码把日期、时间和控制台缓冲相关入口定义成运行时填充的向量。相关定义位于 :187–190:

BIOSSETDATE     DB      3 DUP (?)       ;Set date
BIOSSETTIME     DB      3 DUP (?)       ;Set time
BIOSGETTIME     DB      3 DUP (?)       ;Get time and date
BIOSFLUSH       DB      3 DUP (?)       ;Clear console input buffer

调用点也可以直接核对:READTIME 调用 BIOSGETTIME(:3435、:3438),SETDATE 调用 BIOSSETDATE(:3571),SETTIME 调用 BIOSSETTIME(:3613),FLUSHKB 调用 BIOSFLUSH(:414)。

与 6/16 直接写固定地址的做法相比,1.25 把硬件相关入口集中到运行时填充的向量中。这个变化支持「硬件依赖被进一步隔离」的判断,但不等于所有移植工作都自动完成。

12 号槽的实现路径因此变成:

1.00  VERSION(空壳)
6/16  FLUSHKB(硬编码 IBM PC 地址)
1.25  FLUSHKB → CALL FAR PTR BIOSFLUSH(走 BIOS 向量)

从 1.00 到 6/16 再到 1.25,12 号槽经历了空壳、固定 IBM PC 地址和 BIOS 向量三种实现形态。

6/16 与 7/7:分发表未变

本章比较 6/16 和 7/7 两份内核快照的 DISPATCH 表。结果是 46 个槽位逐项一致;这里的「一致」只指分发表指向相同,不代表两个文件的全部源码逐行一致。

这一结果不能推出 7 月没有继续开发。同期 CHKDSK 快照从 560 行增至 611 行(6 月 15 日与 7 月 15 日,splitlines 口径),说明同一材料集中仍有工具代码在变化。现有材料能证明的是:内核分发表在这两个时间点之间保持不变,而 CHKDSK 继续变化;二者的因果关系不能仅由时间线推出。

7 月 8 日打印、文件创建于 7 月 7 日的清单页上,还有一条批注:DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK。它支持把日期时间功能与 IBM 时钟联系起来,但不单独确定功能具体在哪一次构建中加入;这一点需要与 1.00 基线和 6/16 源码一起阅读。

1.00 的空壳从哪里来

1.00 的 12 号槽是一个返回 0 的空壳。它对应 CP/M 的「返回版本号」位置,说明接口编号被保留下来,但实现尚未提供实际版本信息。结合 Paterson 对接口迁移的说明,可以把它理解为兼容层中的占位;源码没有留下更具体的设计说明。

其他空壳

脚本扫描 1.00 源码中连续标号共同落入 MOV AL,0 / RET 的位置,共得到 10 个标号。其中 5 个出现在分发表中:

标号1.00 槽位语义出处
VERSION12返回版本号
WRTPROT28写保护磁盘
GETRDONLY29取只读向量
SETATTRIB30设文件属性
USERCODE32用户码

另外 5 个标号没有被分发表引用。GETIO 和 SETIO 的名称来自 CP/M 的 7、8 号 I/O 字节功能,但 1.00 的 7、8 号槽已经指向 RAWINP 和 IN。因此,CP/M 相关名称仍留在源码中,调用表却不再提供对应入口;这是源码中可以直接看到的一处接口与内部标号不完全同步。

这些空壳与迁移工具

0.11 社区逆向重建版中的 RDCPM.A86 把 CP/M 调用约定写进了一份工具源码;它只能作为重建版旁证。再把它与 Paterson 对 TRANS 的说明放在一起,才能讨论早期的迁移目标。

在这些早期材料里,可以同时看到三样东西:

再结合 Paterson 1994 年对 TRANS 设计目标的说明,这组材料支持一个谨慎结论:软件与数据的迁移兼容,是 86-DOS 早期设计的重要目标之一,而不是后来偶然得到的副作用。

已有实现后来被移除

空壳不一定从一开始就是空的。把 1.00 与 6/16 的两个函数并排看:

槽位名字1.006/16
24INUSE有实现(第 2525 行起:MOV AX,CS…)只剩 MOV AL,0
31GETDSKPT有实现(第 2472 行起:取 [CURDRVPT]…)只剩 MOV AL,0

在 1.00 → 6/16 之间,两个原本有实现的函数变成了空壳(6/16 第 336–339 行,vol3_lines.py 可复核)。槽位仍然保留,但处理例程只返回 0;这说明接口位置的生命周期和具体实现可以分开变化。

证据提醒:0.11 是社区逆向重建版(法律灰色),此处只作起点旁证,不作行号基准。

现有材料能说明的是:一部分接口位置先被保留,之后有的被填入新实现,有的继续保持空壳;具体动机仍需依靠当事人材料或更多版本。

槽 28:从 WRTPROT 到 GETFATPTDL

槽 12 的变化并非唯一实例。到 MS-DOS 1.25,28 号槽也从一个空壳变成了文件系统相关功能。

28 号槽

1.25 的分发表里,28 号槽指向 GETFATPTDL;1.00 和 6/16 的同一位置则是 WRTPROT。两份早期源码中,WRTPROT 都落在 MOV AL,0 / RET 空壳簇(1.00 第 348–356 行、6/16 第 330–339 行,vol3_lines.py 可复核)。

它是一个真函数(1.25 第 2623–2631 行,逐字转录;2632 行起是装填返回值的尾部,从略):

GETFATPTDL:     ;System call 28
        PUSH    CS
        POP     DS
        MOV     AL,DL
        CALL    GETTHISDRV
        JC      NOSUCHDRV
        CALL    FATREAD
        MOV     BX,[BP.FAT]
        MOV     AL,[BP.CLUSMSK]

这段代码从 DL 取得驱动器号,定位对应驱动器,读取 FAT,然后返回 FAT 指针和簇掩码。它服务的是 1.25 的文件系统内部结构,与早期 CP/M 语义 Write Protect Disk 不同。

其他空壳的归宿

空壳1.00 槽位1.25 的归宿
VERSION12被取用 → FLUSHKB
WRTPROT28被取用 → GETFATPTDL
GETRDONLY29存活,名字未变
SETATTRIB30存活,名字未变
USERCODE32存活,名字未变

5 个分发表空壳中,2 个在 1.25 被换成新语义,3 个仍保留原名。这个结果不支持「所有 CP/M 遗留一次性清除」的说法;更准确的描述是,槽位在不同版本中按需要被保留或重新使用。四份源码没有直接说明每一次复用的动机。

新能力使用尾部槽位

四份源码中,新增能力都出现在分发表尾部:

6/16 新增 42–45 号日期时间功能,1.25 新增 46 号 VERIFY;而被复用的 12、28 号槽,分别替换了早期空壳或未实现语义。就这四个时间点而言,可以归纳出一个局部模式:新增能力使用尾部槽位,已有空壳有时被重新利用。这个模式是样本归纳,不是普遍规则。

硬件访问的另一处变化

1.25 还有一处不属于槽位、但涉及硬件边界的改动: FLUSHKB 从硬编码 0040:001A 改成了 CALL FAR PTR BIOSFLUSH。

Paterson 在 1994 年的信中引用 1980 年末的 86-DOS 手册,称设备 I/O 处理程序可以针对不同硬件配置独立调整;他还描述过 SCP 8086 系统与 IBM 8088 系统使用同一 DOS 二进制的情况(文献 [1])。1.25 中 BIOSFLUSH 等向量和调用点,确实显示硬件相关入口被集中起来。至于不同发行版是否在所有情况下使用同一内核二进制,仍应以具体版本的 I/O 层和初始化表为准。

源码实证从后续演化看,空壳像储备槽:5 个里取用了 2 个依据 _evidence/vol3/vol3_stubs.py(1.00 空壳簇识别与四版归宿比对)1.00 的 5 个空壳都在分发表里,返回 0需要新位置被取用 2 个12→FLUSHKB;28→GETFATPTDL1.25 仍保留原样保留 3 个29、30、32 号槽未改动图 3:1.00 分发表里的 5 个空壳,到 1.25 时 2 个被换上新语义、3 个原样保留。口径:空壳=若干标号共用 MOV AL,0 + RET;「储备」是就后续效果而言。

结论:兼容边界如何变化

四份源码的分发表显示,86-DOS 并没有在某一天整体停止兼容 CP/M。变化发生在具体槽位上: 12 号从 VERSION 变为 FLUSHKB,28 号后来从 WRTPROT 变为 GETFATPTDL; 42–46 号则作为新增功能出现在表尾。6/16 和 7/7 的分发表保持一致,说明接口变化也会进入暂时稳定的阶段。

四条可复核的观察

  1. 兼容目标与 TRANS 的源码翻译计划有关。Paterson 的当事人材料把功能号、入口地址和部分 FCB 布局列为应用可见元素。
  2. 1.00 中有 5 个分发表空壳,6/16 和 1.25 先后复用了其中的 12、28 号槽;另有 3 个空壳保留到 1.25。
  3. 真正新增的日期时间和校验功能使用了 42–46 号尾部槽位。这个「新功能开新槽、空壳可复用」只适用于本册的四个样本。
  4. 硬件访问从 6/16 的固定 IBM PC 数据区写入,发展到 1.25 的 BIOS 向量调用;这是硬件相关边界的变化,不等于所有移植问题都已解决。

因此,本册的结论不是一个笼统的「兼容率」,而是一条时间序列:大部分旧槽位被保留,少数空壳被重新利用,新能力追加到表尾,硬件接口在后续版本中逐步集中。这个结论只针对四份源码和本册使用的 CP/M 2 槽位基线。

复核方法

本册的主要数字可以由以下脚本复核:

这些脚本读取源码并输出统计,不读取文章中的结论。行号、槽数和空壳归宿都可以回到源文件重新检查。

数据实证对齐的槽在失血,新槽在生长:34→32 与 1→6依据 _evidence/vol1/vol1_cpm.py 的四点输出(CP/M 2 分发表逐槽对账,可复跑)与 CP/M 语义对齐的槽纵轴 30–36(非零起点,为看清变化)343333321.006/167/071.25超出 CP/M 表的新槽纵轴 0–7(CP/M 分发表止于 40)15561.006/167/071.25图 4:四个快照里,与 CP/M 语义对齐的槽由 34 缓降至 32,超出 CP/M 表的新槽由 1 增至 6。口径:对齐=功能号相同且语义 id 相同(本文建立的规范化标签);CP/M 侧分发表为外部资料档。

术语


文献

  1. Tim Paterson 致《Microprocessor Report》的信(1994-10-03) 当事人材料,含 Paterson 信与 John Wharton 回函(同页刊出)。Paterson 说明兼容动机是 TRANS 翻译器与软件迁移,依据为 1976 年《CP/M Interface Guide》;Wharton 回函引 《Undocumented DOS》证实功能 0Ch 用于读键盘。原文 PDF 已存档:_evidence/vol3/paterson1994.pdf。 https://www.ardent-tool.com/CPU/docs/MPR/19941003/0813vp.pdf
  2. Bob Zeidman, Was MS-DOS code copied from CP/M?(2016) 外部资料。源码级与二进制级取证:DOS 未复制 CP/M 代码,但大量系统调用号一致。 https://www.zeidmanconsulting.com/DOS_comparisons/Was%20MS-DOS%20code%20copied%20from%20CPM.pdf
  3. OS/2 Museum, 86-DOS Was an Original 外部资料。从分段架构、磁盘管理、工具命名论证 86-DOS 内部与 CP/M 完全不同。 https://www.os2museum.com/wp?p=1380
  4. OS/2 Museum, Who needs the address wraparound, anyway? 外部资料。深入 CALL 5、FCB、地址回绕、BIOS 假设等移植细节。 https://www.os2museum.com/wp/who-needs-the-address-wraparound-anyway/
  5. Microsoft Open Source Blog, Continuing the story of early DOS development(2026-04-28) 外部资料。微软官方把这些打印清单描述为「printed commit history」。 https://opensource.microsoft.com/blog/2026/04/28/continuing-the-story-of-early-dos-development/
  6. DOS-History / Paterson-Listings 源码实证(主基准)。含 86-DOS 1.00、PC-DOS 1.00 开发快照、打印件转录与可编译源码。 https://github.com/DOS-History/Paterson-Listings
  7. Microsoft / MS-DOS v1.25 源码 源码实证。本册第四点时间序列的终点,MIT 许可。复现脚本实际读取 Paterson-Listings 转录件(3_source_code/MS-DOS_1.25/MSDOS.ASM),已验证与官方仓内容一致、行号相同。 https://github.com/microsoft/MS-DOS/tree/main/v1.25
  8. DOS-History / 86-DOS 0.11 重建版 起点旁证。法律灰色,仅用于 RDCPM.A86 的调用约定取证。 https://github.com/DOS-History/86-DOS-0.11
  9. BIOS Data Area 布局(耶鲁 cs.yale.edu) 外部资料。确认 0040:001A 为键盘缓冲头、0040:001C 为键盘缓冲尾。 https://www.cs.yale.edu/flint/feng/cos/resources/BIOS/Resources/biosdata.htm
  10. Seattle Computer Products 公司史(s100computers 镜像) 外部资料。Paterson 1981 年 5 月加入微软、1982 年 4 月因 IBM 需求压力返回 SCP 的时间线。 http://cpmarchives.classiccmp.org/cpm/mirrors/www.s100computers.com/Hardware%20Folder/Seattle%20Computer%20Products/History/History.htm
  11. ROM-BIOS Variables(Tech Help!) 外部资料。40:001A 为键盘缓冲头、40:001C 为键盘缓冲尾的第二来源。 http://www.techhelpmanual.com/93-rom_bios_variables.html
  12. IBM 5150/5160 的实时时钟问题(minuszerodegrees.net) 外部资料。初代 IBM PC 主板不含电池供电 RTC;每次开机 DOS 时钟默认为 01JAN1980 00:00:00,需人工设定;RTC 成为内置配置要到 PC/AT。 https://www.minuszerodegrees.net/5150_5160/rtc_type_examples/5150_5160_rtc_type_examples.htm
  13. OS/2 Museum, PC-86-DOS 外部资料。说明 SCP 86-DOS 的启动盘布局、BIOS 层与 DOS 核心的组合方式,以及不同版本 I/O/初始化表不兼容会导致内核挂起的边界。 https://www.os2museum.com/wp/pc-86-dos/

复现包(全部数字可复跑)