从四个版本的源码,看 86-DOS 如何把 CP/M 留在身后
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-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.00 | 86-DOS_1.00/86DOS.ASM | 1981-04-28 |
| 快照 | PC-DOS_1.00_dev/86DOS_1981-06-16.ASM | 1981-06-16 |
| 快照 | PC-DOS_1.00_dev/86DOS_1981-07-07.ASM | 1981-07-07 |
| MS-DOS 1.25 | MS-DOS_1.25/MSDOS.ASM | 1982-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 的分发表保持不变。
槽 12 是四份源码中最清楚的一处语义替换:1.00 指向 VERSION,6/16 改为 FLUSHKB。
在 86-DOS 1.00(1981-04-28)的第 348 行,源码是这样的:
VERSION:
GETIO:
SETIO:
WRTPROT:
GETRDONLY:
五个标号依次排列,中间没有指令,随后共享 MOV AL,0 / RET。因此,1.00 的 12 号功能虽然沿用了 CP/M 的「返回版本号」编号,实际只返回 0,并没有提供可用的版本号。完整的空壳簇会在第 5 章统计。
从差异清单 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 的估算属于外部论证,本文不把它当作源码实验结果。
一个自然的问题是:既然同期已经在尾部新增 42–45 号槽,为什么不把 FLUSHKB 也放在尾部?
可以确认的是,12 号在 1.00 中只返回 0;在四个快照里,真正新增的日期时间功能被放在 42–46 号等尾部槽位,而 12 号和 28 号后来分别被重新赋予新语义。由此可以提出一种与证据相容的解释:已有空壳可以复用,新能力通常另开槽位。
但源码没有注释说明为什么选择 12 号,所以这只是样本归纳,不是作者明确写下的设计规则。
12 号槽从一个只返回 0 的实现,变成了直接写 IBM PC BIOS 数据区的键盘处理。相邻的 11 号 CONSTAT 和 13 号 DSKRESET 在四份分发表中仍保持原有标号。
因此,四个快照呈现的不是一次整体切换,而是少数槽位逐步改变语义。
12 号槽展示了旧位置的语义替换;42–45 号槽展示了新功能如何直接获得新的编号。
6/16 的分发表尾部多出来四个名字:
GETDATE 42
SETDATE 43
GETTIME 44
SETTIME 45
在 1.00 里它们一个都不存在。1.00 只有 42 个槽,最后一个是 MAKEFCB。
看 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。它能证明的是:已有的日期编码现在被纳入了系统调用的返回值。
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 新增的正是这条调用路径。
两条批注需要与源码快照分开阅读:
PC-DOS 1.0 NO DATE/TIME FUNCTIONS;DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK。第一条批注属于 1.00 基线清单,第二条批注属于 7/7 快照的打印页;不能把第一条直接当作 6/16 源码的注释。86DOS.DIF 和 6/16 源码共同显示,日期时间入口在 5/5 基线之后、6/16 快照中已经出现;7 月批注进一步说明这项工作被当事人联系到 IBM 时钟。批注的具体书写时间和书写者没有更多记录。
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 两份内核快照的 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 的 12 号槽是一个返回 0 的空壳。它对应 CP/M 的「返回版本号」位置,说明接口编号被保留下来,但实现尚未提供实际版本信息。结合 Paterson 对接口迁移的说明,可以把它理解为兼容层中的占位;源码没有留下更具体的设计说明。
脚本扫描 1.00 源码中连续标号共同落入 MOV AL,0 / RET 的位置,共得到 10 个标号。其中 5 个出现在分发表中:
| 标号 | 1.00 槽位 | 语义出处 |
|---|---|---|
VERSION | 12 | 返回版本号 |
WRTPROT | 28 | 写保护磁盘 |
GETRDONLY | 29 | 取只读向量 |
SETATTRIB | 30 | 设文件属性 |
USERCODE | 32 | 用户码 |
另外 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 的说明放在一起,才能讨论早期的迁移目标。
在这些早期材料里,可以同时看到三样东西:
SYSTEM EQU 5、功能号 9/15/16/19/21/22/25/26)RDCPM,见社区逆向重建版)TRANS)再结合 Paterson 1994 年对 TRANS 设计目标的说明,这组材料支持一个谨慎结论:软件与数据的迁移兼容,是 86-DOS 早期设计的重要目标之一,而不是后来偶然得到的副作用。
空壳不一定从一开始就是空的。把 1.00 与 6/16 的两个函数并排看:
| 槽位 | 名字 | 1.00 | 6/16 |
|---|---|---|---|
| 24 | INUSE | 有实现(第 2525 行起:MOV AX,CS…) | 只剩 MOV AL,0 |
| 31 | GETDSKPT | 有实现(第 2472 行起:取 [CURDRVPT]…) | 只剩 MOV AL,0 |
在 1.00 → 6/16 之间,两个原本有实现的函数变成了空壳(6/16 第 336–339 行,vol3_lines.py 可复核)。槽位仍然保留,但处理例程只返回 0;这说明接口位置的生命周期和具体实现可以分开变化。
证据提醒:0.11 是社区逆向重建版(法律灰色),此处只作起点旁证,不作行号基准。
现有材料能说明的是:一部分接口位置先被保留,之后有的被填入新实现,有的继续保持空壳;具体动机仍需依靠当事人材料或更多版本。
槽 12 的变化并非唯一实例。到 MS-DOS 1.25,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 的归宿 |
|---|---|---|
VERSION | 12 | 被取用 → FLUSHKB |
WRTPROT | 28 | 被取用 → GETFATPTDL |
GETRDONLY | 29 | 存活,名字未变 |
SETATTRIB | 30 | 存活,名字未变 |
USERCODE | 32 | 存活,名字未变 |
5 个分发表空壳中,2 个在 1.25 被换成新语义,3 个仍保留原名。这个结果不支持「所有 CP/M 遗留一次性清除」的说法;更准确的描述是,槽位在不同版本中按需要被保留或重新使用。四份源码没有直接说明每一次复用的动机。
四份源码中,新增能力都出现在分发表尾部:
GETDATE / SETDATE / GETTIME / SETTIMEVERIFY6/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 层和初始化表为准。
四份源码的分发表显示,86-DOS 并没有在某一天整体停止兼容 CP/M。变化发生在具体槽位上: 12 号从 VERSION 变为 FLUSHKB,28 号后来从 WRTPROT 变为 GETFATPTDL; 42–46 号则作为新增功能出现在表尾。6/16 和 7/7 的分发表保持一致,说明接口变化也会进入暂时稳定的阶段。
因此,本册的结论不是一个笼统的「兼容率」,而是一条时间序列:大部分旧槽位被保留,少数空壳被重新利用,新能力追加到表尾,硬件接口在后续版本中逐步集中。这个结论只针对四份源码和本册使用的 CP/M 2 槽位基线。
本册的主要数字可以由以下脚本复核:
vol3_divergence.py:四点分发表逐槽比对(42 → 46 → 46 → 47)vol3_stubs.py:1.00 空壳簇及其后续归宿vol3_lines.py:正文引用行号的原文提取与断言这些脚本读取源码并输出统计,不读取文章中的结论。行号、槽数和空壳归宿都可以回到源文件重新检查。
DW 标号 把功能号映射到处理入口,本册四个版本的全部数字都从这张表提取。MOV AL,0 + RET;四个快照中有两个槽位后来换入了新语义。BIOSFLUSH),使内核不再假定硬件地址。与 6/16 的硬编码写法形成对照。ADD AX,1980 把同一偏置转换成系统调用返回的完整年份。RDCPM.A86 的调用约定取证。 https://github.com/DOS-History/86-DOS-0.11_evidence/vol3/vol3_divergence.py 四点 DISPATCH 表逐槽比对(42→46→46→47) python _evidence/vol3/vol3_divergence.py_evidence/vol3/vol3_stubs.py 1.00 空壳簇识别与归宿(5 分发空壳,2 取用 3 存活) python _evidence/vol3/vol3_stubs.py_evidence/vol3/vol3_lines.py 正文所引行号的原文提取与断言(7 处行号证据 + VERSION 清零) python _evidence/vol3/vol3_lines.py_evidence/vol1/vol1_evolve.py 分发表提取器(被 vol3 两个脚本复用) python _evidence/vol1/vol1_evolve.py_evidence/vol1/vol1_cpm.py CP/M 2 分发表基线(外部资料档,非源码实证) python _evidence/vol1/vol1_cpm.py