DOS 起源考古 · 第二册

四十天

117 个改动块,看一个内核如何在 42 天里重新长一遍
全册 10 章 · 差异清单:bundle_08/86DOS.DIF · 快照:3_source_code/PC-DOS_1.00_dev · 2026-09-13

证据分档:源码实证=源码/差异清单文本,附行号;数据实证=_evidence/vol2/ 脚本算出、可复跑;外部资料=邮件、官方说明、扫描原件,附出处;推断标为作者推导。比喻单独标注,不算证据。本册沿用第一册的行号纪律:差异清单里的 5 位数是被对比文件自身的行号 × 10;这两个文件与行号主基准树并不逐行对齐,正文行号一律以主基准树(3_source_code)为准、逐处直读核对。
每处改动都问同一组问题:改了什么?为什么改?改在哪一层?牵动了什么?后来活下来了吗?——四种变化:保留、替换、新增、移除。机器不敢替人下的结论,这一册不替它下。
0 四十二天 · 1 SMALLDIR 的关闭 · 2 新机器的指纹 · 3 新东西进来 · 4 接口开始移动 · 5 改动集中在哪里 · 6 一刀会牵动什么 · 7 自动分类的边界 · 8 这四十二天改变了什么 · 9 执行侧证据

0 · 四十二天

三个日期、一份差异清单、117 个改动块

第一册停在 1981 年 4 月 28 日:86-DOS 1.00 定版。基准源码文件 86DOS.ASM 有 3,622 行,分发表有 42 个功能槽,其中 5 个仍是空壳。本册比较 5 月 5 日和 6 月 16 日两个文件之间的差异。按照本册的分块规则,一共得到 117 个改动块;「改动块」是本文的分析单位,不是原作者留下的提交记录。标题中的 42 天,指这两个文件头时间戳之间的日期差。问题也随之明确:这 117 个块分别改变了什么,哪些变化可以解释,哪些只能停在未知?

比较方法 第一册主要描述 1.00 在一个时间点上的结构;本册把 5 月基线与 6 月快照并排放置,先记录差异,再根据源码上下文和其他材料判断它们的作用。

标题中的 42 天来自差异清单文件头,而不是 4 月 28 日到 6 月 16 日的日历差。4 月 28 日是 1.00 的定版日,写在源码台账上;5 月 5 日 10:56 是差异清单 File 1 的时间戳,Paterson 认为它与 4 月 28 日的 v1.00 非常接近,可能对应上传到微软服务器的日期;6 月 16 日 14:25 是 File 2 开发快照的创建时间。因此,86DOS.DIF 比较的是 5 月 5 日的 86DOS.ASM 和 6 月 16 日的 86DOS.A86,两端相差 42 天;书名《四十天》取的是便于阅读的约数。清单中的 5 位数字(如 00230、38750)属于被比较文件自身的打印行号。它们在这份清单中按源码行号乘 10 编排,但被比较的旧文件与 3_source_code 主基准树并不逐行一致:旧侧卷首多一个空行,FAT 注释也有合并和拆分。所以正文的行号一律回到主基准树直读,打印行号只用于引用差异清单本身。

差异清单文件头两行,写明对比的两个文件及其创建时间
差异清单的文件头(逐字转录)
这份清单自己说明了它对比的是哪两个文件、各自什么时候创建。本册的全部时间计算都从这两行出发,不依赖任何转述。

117 个改动块

差异清单用星号行分节:四个星号分隔旧侧和新侧,十个或更多星号才表示一个改动块结束。按这个规则切分,共得到 117 块:两侧行数相同的修改 25 块,新侧更多的新增 57 块,旧侧更多的删除 35 块。改动分散在各个区域,没有出现连续的大段整体替换;这支持「局部演化多于推倒重来」的判断,但它本身不能证明每一块改动的原因。

117 块里,96 块落在功能实现区(占 82%),9 块在系统调用分发表,剩下 12 块分散在头部、数据结构、入口和数据区。按每 400 源码行分格,前 400 行有 22 块,2800–3199 行有 17 块。前者覆盖常量、数据结构和入口,后者主要落在字符输入输出等功能实现区域;第 5 章会分别说明这些区域的改动内容。

本册要回答的问题

1.00 已经是一个可以发布的操作系统。为什么四十二天之后,它还需要被大改?这些改动是在加功能、修问题,还是在为 IBM PC 这个即将到来的硬件环境重新塑形?

117 个改动块是材料,不是结论。本文对每组变化依次检查:改了什么、位于哪一层、是否能从其他材料解释其作用,以及后续版本是否保留了它。为了便于比较,改动先按保留、替换、新增和移除四种结果归类。

证据的边界

这份差异清单是打印件的转录,可能包含誊抄噪声。改动块可以证明源码文本发生了什么变化,但不能单独证明作者为什么这样改;涉及动机时,本文会另找批注、当事人材料或上下文,并把推断单独标出。117 块中有 86 块没有得到可靠的自动初判,初筛结果只覆盖 31 块。这 86 块仍然需要逐块阅读,不能把关键词分类当成最终结论。

四十二天:这条线上的三个日期,各来自一份不同的材料 4/28 定版 86-DOS 1.00 源码台账:Renumber for general release 5/5 10:56 差异清单的 File 1 86DOS.ASM 的文件时间戳 (≈1.00 上传微软服务器) 6/16 14:25 差异清单的 File 2 86DOS.A86 开发快照 (第一册讲过的 6/16 点) 七天 42 天(差异清单区间) 这四十二天里发生的事,记在一份差异清单里(86DOS.DIF) 它逐条记录两个文件的不同,一共 117 个改动块: 其中新增 57 块、删除 35 块、修改 25 块(vol2_catalog.py 统计) 块的定义、行号换算、时间区间的算法全部写在脚本头部,读者可以重建每一个数字 差异清单的两端是两个文件的时间戳;4/28 是定版日,不在清单里,但在源码台账上
图 0 三个日期各有出处:台账、文件时间戳、快照。42 天由脚本计算。
本章证据
档位事实出处复核方式
外部资料差异清单文件头:File 1) 86DOS.ASM created 05-MAY-1981;File 2) 86DOS.A86 created 16-JUNE-198186DOS.DIF 文件头原文(bundle_08)直读;天数由 vol2_catalog.py 计算
数据实证117 块;增 57 / 删 35 / 改 25;96 块落在功能实现区(82%);最密段为源码前 400 行(22 块)vol2_catalog.py 与 vol2_change_catalog.csv分块规则与换算写在脚本头部,可复跑
源码实证4/28 定版(Renumber for general release)86DOS.ASM:15(第一册第 1 章)直读
外部资料5/5 与 4/28 的关系(上传服务器时间戳口径)Tim_Paterson_24Apr2025_emails.txt邮件原文

1 · SMALLDIR 的关闭

一个兼容开关从 1 变为 0

第一处改动只改了一行常量:SMALLDIR 在 1.00 中为 1,在 6 月 16 日快照中变为 0。这个开关与 0.42 引入的 32 字节目录项有关;关闭它,会让一批只在汇编期保留的旧格式分支不再进入产物。本章先说明这一行改动牵连了哪些代码,再讨论它后来如何退出。

绿色横纹连续纸的差异清单打印页,最上方第一块就是 SMALLDIR 从 1 改成 0
差异清单的正文第一页(打印原件扫描)
清单正文的第一页。第一块就是本章分析的差异:两侧行号同为 00230,SMALLDIR: EQU 1 对 SMALLDIR: EQU 0,中间用四个星号隔开旧侧与新侧,整块以十四个星号结束。同一页往下还有第 2、3 块(DSKTEST 与 MAXCOM/ESCCH),MAXCOM/ESCCH 将在第 4 章讨论,DSKTEST 的变化见第 6 章。纸是连续纸,左侧是进纸链孔,右下角是页码 090247。

当时发生了什么

差异清单的第一块,新旧两侧各只有两行,改的就是那个常量:

1)1 00230 SMALLDIR: EQU 1 ;1 to enable, 0 to disable
1) 00260 ; Turn on switch below to allow testing disk code with DEBUG. It sets
****
2)1 00230 SMALLDIR: EQU 0 ;1 to enable, 0 to disable
2) 00260 ; Turn on switch below to allow testing disk code with DEBUG. It sets
**************
86DOS.DIF 第一个改动块(两侧行号同为 00230;该行在主基准树是第 22 行),逐字转录

从 1 到 0。清单上没有写原因,也没有写这一天还发生了什么,清单只记录「两份文件在这里不一样」。要知道这行常量改变了什么,得回到源码里逐项统计。

开关影响哪些代码

SMALLDIR 是一个汇编期开关:汇编器在读到 IF SMALLDIR 时,按它的值决定底下那段代码要不要编进系统。它不是运行时才判断的变量,关掉它,等于让一整片代码在汇编阶段就消失。

vol2_smalldir.py 逐条统计了两个版本中的出现位置。1.00 共 15 处:定义 1、条件汇编分支 13、引用 1;6/16 剩 13 处:定义 1、分支 12,引用消失。6/16 中的 12 个条件分支分布在 111、537、578、617、814、880、961、1092、2434、2518、3723、3733 行,从目录项处理一直延伸到数据区;3723 行还使用了 IF SMALLDIR-1,表示开关为 0 时编入另一支代码。因此,改变一个 EQU 值会改变多处汇编期选择;这里的「牵连」只统计源码文本,不等同于运行时影响。

差异清单第 48 号改动块还显示了一个具体后果:旧侧 1028 行附近的 XOR AX,AX 被删去,新侧保留了 IF SMALLDIR 的分支开头。开关值变为 0 后,与旧目录格式有关的那段清零动作不会进入汇编产物。

它影响的是哪一层

SMALLDIR 保护的是目录格式相关的汇编分支。0.42 把目录项从 16 字节扩到 32 字节后,数据区起点和可用簇数都会变化;源码因此准备了两套参数:FIRREC1/MAXCLUS1 对应 16 字节目录,FIRREC2/MAXCLUS2 对应 32 字节目录。13 个条件分支分布在目录项处理、搜索、FAT 读取和数据区等位置。当开关为 0 时,条件汇编会把其中属于旧格式的代码排除在产物之外。

为什么现在关

清单没有写原因,动机就不能当作源码事实。能摆上桌面的只有三点:第一,关闭发生在 6 月 16 日的开发快照里,同一时期的代码正在为 IBM PC 适配新的硬件假设(见下一章);第二,如果目标发行形态已经不再需要 SCP 旧目录格式,继续编入这套兼容分支的收益自然会下降,但这只是与时间线相容的解释,不是材料明说的决定;第三,SMALLDIR 在 1.20 的修订台账中被写成 Kill SMALLDIR,后来确实从系统里消失。把这三点放在一起,可以看出它的退场方向;至于 Paterson 当时的具体考虑,纸上没有留下答案。

后来活下来了吗

从现有版本可以确认,SMALLDIR 的值经历了 1 → 0,随后在 1.25 的修订台账中被删除。这说明旧目录格式的兼容路径先被关闭,之后连相关定义也退出了源码。至于关闭它时是否明确针对 IBM PC,差异清单本身没有留下直接说明。

一个开关的一生:出生、工作、关闭、删除 0.42 出生 1981-02-25 目录项从 16 字节 扩到 32 字节(台账) 为了照顾还拿着 老格式盘的老客户, 开关默认打开, 两套磁盘参数同时保留 它的任务: 让老盘还能读 1.00 工作 1981-04-28 开关 = 1 牵连 15 处: 定义 1 · 条件分支 13 · 引用 1(vol2_smalldir.py) 13 个分支散布在 目录、参数、搜索各处, 汇编时按开关二选一 第一册读到的它 6/16 关闭 1981-06-16 快照 开关 = 0 差异清单的第一块 牵连 13 处: 定义 1 · 条件分支 12 关掉后这些分支 连同里面的代码 不再被汇编进系统 本册首先分析的版本差异 1.20 删除 1981-12-31 台账原话: Kill SMALLDIR 定义与全部分支 从源码里消失 老 16 字节目录 格式正式退场 1.25 台账可查 一个为「让老客户平滑过来」而生的开关,在系统准备迎接新机器时被关掉,年底被删除。 现有材料能确认它先被关闭,随后在 1.20 台账中被删除;关闭的直接动机仍没有留下说明。
图 1 SMALLDIR 的一生。四个阶段的每个数字都出自可复跑的脚本。
证据摘录
SMALLDIR: EQU 1 ;1 to enable, 0 to disable | SMALLDIR: EQU 0
1981-05-05 与 06-16 两版之间 · 来源:86DOS.DIF 第一改动块
本章证据
档位事实出处复核方式
源码实证SMALLDIR 在 1.00 牵连 15 处(定义 1 / 分支 13 / 引用 1),在 6/16 牵连 13 处(定义 1 / 分支 12)vol2_smalldir.py 输出;1.00 :22,118,507,539,578,737,810,893,1006,1015,1041,2330,2415,3381,3391;6/16 :22,111,537,578,617,814,880,961,1092,2434,2518,3723,3733全部出现位置逐条列出,注释行不计入;口径毛边留档:1.00 :1015 的 IF 1-SMALLDIR 同为取反,因不是行首 IF SMALLDIR 被计入「引用」而非「分支」
源码实证差异清单第一块:SMALLDIR EQU 1 → 0(两侧行号同为 00230)86DOS.DIF 第一改动块直读
源码实证开关关掉后相关代码被删(旧侧含 XOR AX,AX 与 IF SMALLDIR)86DOS.DIF 第 48 号改动块(旧侧 1028 行附近删 1 行)catalog + 直读
源码实证1.20 台账写 Kill SMALLDIRMS-DOS_1.25/MSDOS.ASM 修订台账直读(第一册第 6 章已引)
作者推导关闭动机与 IBM PC 即将到来相关(三条事实并列,方向清楚;具体想法纸上没写)由时间线与发行配置推得推断已标注;不冒充源码证据

2 · 新机器的指纹

BIOS 地址、调用方式和内存检测都发生了变化

117 个改动块中,有一部分改的是硬件假设,而不是面向用户的新功能。1.00 把 BIOS 段地址和内存检测方式写在内核里;6 月 16 日快照则修改了这些位置,同时把部分硬件访问收进内核自己的例程。把这几组改动放在一起,可以看出代码如何适应新的运行平台。

第一处:BIOS 在哪一段

BIOS 是机器提供的底层输入输出程序,早期系统通常通过固定入口调用它。8086 使用段地址访问内存;可以把段号暂时理解为一段内存区域的编号。在 1.00 中,BIOS 所在段被写成一个符号:

BIOSSEG: EQU 40H
1.00 的 86DOS.ASM:132;6/16 快照同位置改为 EQU 60H(差异清单第 7 块)

6 月 16 日快照把 BIOSSEG 改为 60H。1.00 中的 BIOS 远调用都通过 BIOSSEG 这个符号取得段地址,所以同一个常量的变化会影响所有采用这种写法的调用点。这是符号寻址带来的整体替换效果;它不等于所有硬件访问都已经完成抽象。

第二处:部分直接调用被收进例程

按 CALL BIOSxxx,BIOSSEG 形式统计,1.00 有 18 个直接调用点,6/16 剩下 15 个。6/16 中可以找到 AUXIN(:363)、AUXOUT(:370)、LISTOUT(:3093)和 STATCHK(:2968)等内核例程,部分设备访问因此先经过内核自己的接口,再由接口调用 BIOS。控制台和磁盘路径仍保留直接调用,所以这里描述的是局部收拢,而不是完整的硬件抽象层。

第三处:不再自己探内存

另一处变化是内存检测。1.00 中有 MEMSCAN 例程,内核通过读写高位地址来探测内存边界;6/16 快照删除了这段循环,改用 INT 12H 请求 BIOS 报告内存大小(以 1K blocks 计)。

这组改动把「内核自行探测」换成了「向平台询问」。同一份差异清单还显示,BIOS 返回的内存大小被放进数据区,供后续初始化使用。因此可以说,6/16 快照把一部分硬件知识交给 BIOS;但这只是对这几处改动的概括,并不表示整个内核已经与硬件完全解耦。

同一件事的两种做法:内存大小从哪儿来 1.00:自己探 内核 MEMSCAN 循环 内存本身 . 读写试验 6/16:问 BIOS 内核 INT 12H BIOS 知道的内存大小 左边:内核自己读写内存高位,试探哪里到头,机器换了,这套假设可能全错 右边:内核改问 BIOS 要答案,硬件知识从操作系统里挪了出去,挪到了固件那一层
图 2 MEMSCAN(自己探)与 INT 12H(问 BIOS)的对照。左图例程名与右图服务名均可在两个快照中定位。
证据摘录
INT 12H ;Get memory size--1K blocks in AX
6/16 快照(差异清单第 114 块的新侧)。它替换掉的旧代码叫 MEMSCAN,自己扫描内存的循环。
本章证据
档位事实出处复核方式
源码实证BIOSSEG 从 40H 改为 60H,全部 BIOS 调用经同一符号寻址1.00 :132;差异清单第 7 块直读 + 差异清单
数据实证直接 BIOS 调用点 18 → 15;6/16 新增内部例程 AUXIN :363 / AUXOUT :370 / LISTOUT :3093 / STATCHK :2968vol2_data.py A 节输出两版本按行型正则统计
源码实证MEMSCAN 循环被删除,替换为 INT 12H 取内存大小差异清单第 114 块(旧侧 MEMSCAN:/HAVMEM,新侧 INT 12H)直读
作者推导「硬件知识从操作系统挪到 BIOS 层」是对这三组改动的概括由上述三处合成推断已标注;改动本身是源码事实

3 · 新东西进来

日期/时间:一个需求穿过操作系统的四层

6/16 快照新增了 42–45 四个日期时间入口。手写批注把这项工作联系到 IBM 时钟;源码则可以把它拆成四个层次:接口、实现、可写状态和文件中的日期字段。本章按这四层检查一项日期时间需求如何进入内核。

第一层:接口多出四格

6/16 快照的系统调用分发表(源码 :325)末尾新增四个入口:42 取日期、43 置日期、44 取时间、45 置时间。1.00 的分发表中没有这四项;日期时间功能从这些编号进入内核。

第二层:四个例程和一个公共处理

四个功能各有实现体(:3373、:3386、:3436、:3485)。读取路径中的 GETDATE 和 GETTIME 会调用 READTIME(:3324),源码注释说明它负责处理跨午夜的日期变化;SETDATE 和 SETTIME 则在参数检查后直接调用时钟服务。以 GETDATE 为例(6/16 快照 :3373 起),它从内部变量取出年份和日序,把年份加回 1980,再把月日写入用户寄存器。

第三层:状态住进了 DATA AREA

READTIME 使用的 YEAR(:3552)、DAY 等变量位于源码末尾的 DATA AREA(:3537 起)。日期时间功能除了新增四个例程,也新增了保存运行状态所需的变量;这说明接口变化会继续传到内核的数据区。

第四层:落盘

日期字段在 1.00 中已经存在两个位置:FCB 的 FDATE(:65),以及目录项偏移 24 起的位场日期(:83)。6/16 快照又调整了这两处的注释和相关结构(:70、:89)。因此,新增的不是「第一次保存日期」,而是面向应用程序的日期时间调用,以及与之配套的读取、设置和状态管理路径。

两条变化放在一起看

SMALLDIR 的变化减少了一条旧格式路径,DATE/TIME 的变化增加了一组新接口。同一份差异清单里,兼容路径的关闭和平台功能的增加同时发生;这比单独统计 117 个改动块,更能说明这轮修改同时包含删减和扩展。

一个需求进入操作系统,要穿过几层(日期/时间,6/16 快照) 需求层 纸边手写批注 DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK,为 IBM 的时钟加日期时间功能 接口层 DISPATCH 多出四格(:325) 42 取日期 · 43 置日期 · 44 取时间 · 45 置时间,1.00 里一格都没有 实现层 四个例程(:3373 / :3386 / :3436 / :3485)与 READTIME(:3324) GETDATE、GETTIME 通过 READTIME 处理跨天;设置路径直接调用时钟服务 状态层 DATA AREA 里多了日期变量(YEAR :3552 等) 这是内核唯一会被改写的地方,日期/时间从此在这里住下 盘面层 FCB 的日期字段(:70)与目录项的位场日期(:89) 日 5 位、月 4 位、年 7 位(年减 1980),写进每一个目录项,随文件落到盘上 四层、六个行号锚点,「加一个功能」在操作系统里从来不是加一个函数 时间字段的时间部分(时/分/秒)1.25 才进 FCB,本册材料止于日期
图 3 四层、六个行号锚点。每层都可以回 6/16 快照直接核对。
证据摘录
DATE/TIME FUNCTIONS ADDED FOR IBM CLOCK
Paterson 写在清单纸边的批注(另一页上)。日期:7 月 8 日打印(文件创建于 7 月 7 日)的那页。它的前一半故事,另一页上「PC-DOS 1.0 没有日期时间功能」那条,已在第一册引过。
本章证据
档位事实出处复核方式
源码实证DISPATCH 末尾四格(42–45 日期时间)只在 6/16 出现6/16 快照 :325 附近;1.00 :302–346 无此四格vol2_data.py C 节 + 直读
源码实证四个例程与 READTIME、YEAR 变量的行号6/16 快照 :3373 / :3386 / :3436 / :3485 / :3324 / :3552脚本定位 + 直读;读取路径与设置路径分别检查
源码实证GETDATE 处理跨天、年偏置 +1980、结果写回 CX/DX6/16 快照 :3373–3384直读(第一册第 6 章已引)
外部资料手写批注 DATE/TIME FUNCTIONS ADDED FOR IBM CLOCKarchive.org paterson_listings 之 images/listings.jpg裁切放大逐字确认(第一册已核)

4 · 接口开始移动

一个位置被顶掉,一个上限被抬高

第 3 章新增了日期时间功能;本章只看接口编号本身如何变化。6/16 快照有两处需要分开理解的事实:12 号槽从 VERSION 改为 FLUSHKB,同时 MAXCOM 从 41 变为 45。前者是一个槽位的语义替换,后者是中断入口允许的上限变化。

先分清两个数字

第一册讲过 1.00 的分发表有 42 格,6/16 快照有 46 格;而 MAXCOM 是 41 抬到 45。这两组数字不是一回事:

MAXCOM = 41 ;允许调用的最大功能号(INT 路径的上限)
DISPATCH 表长度 = 42 ;表里实际的格数(功能号从 0 编到 41)
── 6/16 ──
MAXCOM = 45 ;上限抬到 45
DISPATCH 表长度 = 46 ;格数 46(功能号 0 到 45)
概念对照(非源码引用;MAXCOM 定义见 1.00 :45 与 6/16 快照同位置)

上限表示允许调用的最大功能号,表长表示分发表中实际有多少个表项。1.00 的功能号从 0 到 41,所以 MAXCOM=41 对应 42 个表项;6/16 从 0 到 45,所以 MAXCOM=45 对应 46 个表项。两者相差 1,只是因为编号从 0 开始。

12 号槽:一次替换

1.00 的 12 号槽指向 VERSION,其函数体只把 AL 清零后返回。6/16 快照中,VERSION 标号消失,12 号槽改指 FLUSHKB,后者直接重置 IBM PC BIOS 数据区中的键盘缓冲指针。这是槽位语义的替换,不只是函数改名。1.00 空壳簇中的其他标号大多仍被保留,其中 GETIO、SETIO 原本就没有被分发表引用;后续版本对这些空壳的处理并不完全相同。

上限抬高的意义

MAXCOM 从 41 变为 45,数值上正好覆盖新增的四个日期时间槽;MAXCALL 仍为 36,因此这些新增槽不属于 CALL 5 的兼容入口。按同一套 CP/M 2 对齐口径,1.00 的对齐槽为 34 个,6/16 为 33 个,1.25 为 32 个。分发表扩大与对齐槽减少,是同一条接口演化中的两个不同指标。

本章证据
档位事实出处复核方式
源码实证VERSION 标号自 6/16 起消失,12 号槽改为 FLUSHKB;其余六个空壳标号保留vol1_evolve.py 空壳去向表(第一册管线);6/16 快照无 VERSION 标号脚本比对 + 直读
源码实证MAXCOM 41 → 45;MAXCALL 不变(36)差异清单第 3 块;1.00 :44–45 与 6/16 快照同位置直读
数据实证上限与表长恒差 1(功能号自 0 编起):41/42 → 45/46vol2_catalog.py 的 DISPATCH 提取脚本可复跑
作者推导「容量变大、重叠变小发生在同一张表上」是对两组数字的概括由第 4 章数据合成综合判断

5 · 改动集中在哪里

改动在源码中的分布,以及三个集中区域

把 117 个改动块按旧侧起点放回源码,可以看到它们并不均匀分布。前 400 行有 22 块,2800–3199 行有 17 块。这只是文本变化的密度,不是开发时间或工作量的测量;本章只讨论改动集中在哪里,以及这些区域各自改了什么。

一张分布图

按旧侧起点每 400 行分组,块数依次为:0–399 行 22 块,400–799 行 18 块,800–1199 行 16 块,1200–1599 行 9 块,1600–1999 行 7 块,2000–2399 行 6 块,2400–2799 行 11 块,2800–3199 行 17 块,3200–3599 行 11 块。前 400 行覆盖常量、数据结构、入口和分发表;2800–3199 行则落在字符输入输出等功能实现区域。

最前面的 22 块:骨架先动

前 400 行的 22 块主要涉及常量、开关和表,包括第 1 章的 SMALLDIR、第 2 章的 BIOSSEG 和第 4 章的 MAXCOM。这些位置的共同特点是:一行定义可能被多个调用点引用,所以文本改动很小,影响范围却可能较大。

2800–3199 行的 17 块:三类改动

这 17 块可以按内容分成三类。第一类是行编辑器:ENTERINS 和 EXITINS 两个命令合并为 TOGGLINS(第 90、91 块),按键表随之调整。第二类是字符设备输出:LISTOUT、STATCHK 等例程(第 93–95、100 块)把状态检查和 Ctrl-S 暂停等逻辑集中到更明确的调用路径中。第三类是 FCB 构造和输入解析:MAKEFCB 一带(第 101–105 块)增加了引号、控制字符和大小写处理。同一区域还出现了 DATE16 例程,和第 3 章的日期功能有关。

低密度区域的含义

1600–1999 行只有 7 块,主要对应顺序读写和随机读写实现。这只能说明本次差异清单在这段的文本变化较少,不能单独证明相关代码已经完全稳定或没有问题。把它和前 400 行、2800–3199 行的高密度区域对照,可以确认本轮修改更集中在接口骨架、硬件适配、字符输入输出和 FCB 解析等位置。

本章证据
档位事实出处复核方式
数据实证每 400 行一格的块数分布(22/18/16/9/7/6/11/17/11,按差异清单旧侧起点行 ÷10 计);96 块(82%)落在功能实现区vol2_catalog.py 输出分格统计可复跑
源码实证高密段 2800–3199 的 17 块内容:行编辑命令合并(TOGGLINS)、LISTOUT/STATCHK 例程、FCB 构造的字符处理、DATE16 例程catalog 第 90–106 号块 + 6/16 快照对应行逐块直读
作者推导三组故事的归纳(行编辑/设备收拢/FCB 强化)与「密度低=已稳定」的解读由块内容与分区对照得出归纳已标注

6 · 一刀会牵动什么

源码牵连度:一个符号出现在哪里

第一章统计了 SMALLDIR 在两个版本中的出现次数。本章把这个统计拆开,说明定义、条件分支、引用和数据定义分别有多少处。这里使用「源码牵连度」这个名称,是因为统计对象是源码文本中的位置,不直接判断运行时哪些路径会被执行。

牵连度的三个来源

SMALLDIR 在 1.00 中出现 15 处:定义 1 处、条件汇编分支 13 处、引用 1 处。其中第 1015 行的 IF 1-SMALLDIR 是取反分支,处理开关为 0 时的路径。FIRREC1/FIRREC2 和 MAXCLUS1/MAXCLUS2 等参数也与这套分支对应,但它们属于结构上的关联,不计入上面的 15 处文本统计。因此,本章把文本计数和结构推断分开呈现。

六个符号的对照

同样的统计也应用到其他五个符号:SMALLDIR 15 → 13(分支 13→12,引用 1→0);DSKTEST 6 → 4;ESCCH 5 → 3;BIOSSEG 20 → 17;INTBASE 8 → 8;MAXCOM 2 → 2,但值从 41 变为 45。这组数字说明,部分符号在新版本中的文本引用减少,部分符号保持不变;它描述的是源码结构变化,不直接说明运行时行为。

为什么一个开关会牵动多处代码

SMALLDIR 是编译期开关,不是运行时变量。它的值会决定 12 个条件分支是否进入 6/16 的汇编产物,也会决定使用哪套目录参数。因此,改动虽然只有一行 EQU,结果却会出现在目录处理、搜索、FAT 读取和数据区等多处;「牵连度」能量化这些文本位置,但不能把它直接换算成开发成本或运行时影响。

本章证据
档位事实出处复核方式
数据实证SMALLDIR 的 15 处 / 13 处逐条明细(含行号与行型)vol2_smalldir.py 输出全量列出,注释行不计入
数据实证DSKTEST/ESCCH/BIOSSEG/INTBASE/MAXCOM 的牵连度对照vol2_ripple.py 输出定义/引用/分支/数据四分量分别统计
作者推导间接影响(磁盘参数)计入牵连;「十二个决策点」是对 12 个条件分支的解释由 1.00 :118–3391 与 :118–123 的关系读出解释已标注;分支计数是源码事实

7 · 自动分类的边界

86 个改动块,四种初筛失败

117 个改动块中,关键词初筛只为 31 块给出唯一类别,其余 86 块因为未命中或命中多个类别而需要人工复核。本章用四个例子说明:关键词能帮助整理材料,但不能替代对代码上下文的阅读。

一:关键词没有覆盖到的常量

第 3 号块改动了 MAXCOM: EQU 41 → 45、ESCCH: EQU 1BH → 0,并保留 INTBASE。这些变化涉及接口上限和输入控制,但关键词表没有把 MAXCOM 或 ESCCH 列入任何类别,因此该块只能进入待复核清单。仅靠关键词,无法从常量名推断改动作用。

二:命中多个类别时的初判偏差

第 114 号块删除 MEMSCAN 循环,换成 INT 12H 请求 BIOS 报告内存大小。块文本同时命中兼容性和硬件适配关键词,脚本按规则给出兼容性初判并标记待复核。结合前后源码后,这一块更适合归入硬件适配:它改变的是内存检测方式,而不是 CP/M 接口。

三:多个候选都不完全贴合

第 20 号块调整了功能表附近的标号:旧侧有 READER,新侧出现 INUSE 和 GETDSKPT。脚本同时命中硬件适配、新功能和稳定性类别,并把它放入人工复核。结合分发表位置后,更准确的描述是接口布局调整;预设的三个类别都不完全合适。

四:代码结构不在关键词里

第 101、105 号块修改了 FCB 构造中的字符终止、引号处理和大小写屏蔽。这些变化没有命中关键词,但读源码上下文可以看出它们属于输入解析。关键词没有命中,并不等于改动没有语义。

人工复核的必要性

四种情况说明,自动分类只能用于初筛。31 块得到唯一类别,86 块则逐块对照旧侧、新侧和周边代码,再给出类别和置信度,并把结果写入 vol2_change_catalog.csv。脚本减少了机械查找,最终的语义判断仍然依赖源码上下文。

证据摘录
MAXCOM: EQU 41 → MAXCOM: EQU 45 ESCCH: EQU 1BH → ESCCH: EQU 0
差异清单第 3 号块。该块未命中关键词初筛,本文结合上下文在第 4 章讨论。
本章证据
档位事实出处复核方式
数据实证自动初筛 31 块 / 待人工 86 块;四种失败模式各有一个真实案例(块 3 盲区、块 114 判错、块 20 多判、块 101/105 沉默)vol2_taxonomy.py 的 classify 对 catalog 逐块重跑本页可复现
作者推导四个失败模式的命名与「初筛/复核」的分工本文的方法论概括作者推导;块的原文与初判是脚本输出

8 · 这四十二天改变了什么

从差异分布和结构连续性判断演化方式

前面的章节分别看了兼容开关、硬件适配、新功能、接口槽位和改动分布。本章把这些证据合在一起,回答一个限定的问题:5 月 5 日到 6 月 16 日的变化,更接近局部演化,还是一次重新实现?

先做一个反事实

如果这四十二天是推倒重写,通常会期待看到大段代码整体替换、旧接口大量消失、数据结构断裂或版本记录中断。现有材料呈现的是另一种形态:117 个改动块分散在旧侧打印行号 230 到 38,750 之间,没有出现连续的大段替换;FCB、磁盘参数块和入口代码仍保持相同的总体结构;功能槽位的变化集中在少数位置,6/16 仍保留大部分原有槽位。这些证据更支持「在既有内核上持续修改」,但不能据此排除局部代码曾经重写。

五问的回答

把前面几条主线分别归纳:SMALLDIR 的值从 1 变为 0,12 个条件汇编分支不再按旧格式编入;其后 1.20 台账记录了 Kill SMALLDIR。BIOSSEG 从 40H 变为 60H,采用该符号的 BIOS 远调用随之改变目标段;这能证明 6/16 的硬件布局假设发生了变化,不能仅凭这两个快照断言该寻址方式此后一直保留。DATE/TIME 新增 42–45 四个槽、四个例程和状态变量;7 月批注把它联系到 IBM 时钟,1.25 仍保留这些日期时间入口。FLUSHKB 取代了 12 号槽的 VERSION,实现上负责重置键盘缓冲指针;1.25 的 12 号槽仍由 FLUSHKB 占用。其中「为什么改」只有日期时间的硬件联系有批注支持,其余动机都应按推断或未知处理。

结论

综合这些证据,5 月 5 日到 6 月 16 日的变化更接近连续演化,而不是重新实现。理由是结构仍然连续,绝大多数接口槽位保留,改动分散在多个区域,且变化集中在硬件适配、日期时间和旧目录开关等少数主题。但这个结论的范围只到现有两份源码和差异清单;它不排除局部例程被重写,也不能还原每一次改动的具体动机。源码行数从 3,622 增至 3,888,净增 266 行;这个数字表示文本规模变化,不等于新增代码量。

本章证据
档位事实出处复核方式
作者推导四条主线的五问回答与「连续演化」结论由第 1–7 章证据合成反事实与结论均为作者推导;所依据的事实(117 块分布、33/34 对齐、台账连续、266 行)见前七章
数据实证净增 266 行(3,622 → 3,888);对齐槽数 34 → 33vol1_evolve.py / vol1_cpm.py(第一册管线)可复跑

9 · 执行侧证据

汇编、引导和功能 12 实验的当前状态

前八章主要依据纸面材料。最后这一章再加上执行侧证据:先把源码汇编成机器码,再在仿真环境里观察它的行为。汇编和 1.00 侧运行实验已经完成;6/16 侧因缺少匹配的 DOSIO/初始化契约,仍停在静态和部分入口验证。

9a:汇编实证(已完成)

素材核对完成后,证据包在一台 Linux 环境中用 NASM 编译了社区对 Paterson 汇编器(SCP ASM 2.44)的重实现,再对 86DOS.ASM 执行汇编。这里使用的是现代兼容实现,不是 1981 年的原始汇编器;每一环都有日志和产物留存:

86DOS.ASM(60,124 字节,= 基准树同一文件)
↓ SCP ASM 2.44 重实现(asm244l,Linux i386)
Error Count = 0
↓
86DOS.bin = 5,446 字节
↓ 与现代 IBM PC 适配盘映像(内核写在扇区 5 起;扇区=磁盘上一次读写的最小单位)逐字节对比
差异 = 49 字节级差异(24+19+2+1+3;第四册详拆)
构建与对比的完整记录见 BUILD_RECORD.md(_evidence/vol2/remote_build/),命令与日志可复跑

49 个字节表示源码编译内核与现代 IBM PC 适配复刻盘内核之间的差异,不是汇编器产生的错误数量。独立复核把它们分成五类:24 字节键位表、19 字节 BIOS 段基址、2 字节搜索检查旁路、1 字节 FCB 寻址修正和 3 字节行编辑引子。公开复现项目把这组变化概括为键盘映射补丁,但字节级证据显示,不能把 49 个字节全部归入键盘;每一类改动的历史动机也不能仅凭最终字节恢复。

9b:运行实证(1.00 已完成,6/16 未完成)

汇编通过只能证明源码副本能被这份现代兼容汇编器接受并产出机器码,不能单独证明历史机器上的运行行为。49 个字节是 1.00 编译产物与现代 IBM PC 适配复刻盘之间的差异,不能作为 6/16 快照的通用补丁。1.00 侧的功能 12 实验已经完成;6/16 侧因为缺少匹配的 DOSIO 和初始化契约,完整引导尚未完成。

已完成:1.00 内核
输入:先把键盘缓冲头尾写成 30/40,再以 INT 21h 调功能 12
结果:红屏;头尾仍为 30/40,VERSION 空壳未清缓冲

未完成:6/16 快照
静态结果:槽 12 指向 FLUSHKB,机器码写入 0040:001A/001C = 1Eh
运行状态:无法用 1.00 适配盘的 DOSIO 完整引导;未取得完整行为记录
不能用:把 1.00 的 49 字节适配差异直接套到 6/16 快照

判定:1.00 侧行为已证;6/16 侧保留为静态已证、运行未证
实验记录见第四册第 5 章与 _evidence/vol4/E4_ATTEMPT.md

功能 12 是一个清晰的对照点:1.00 的槽位指向空壳,6/16 的槽位指向 FLUSHKB。但两个版本只有在匹配的 I/O 层和初始化表下才具有可比的完整运行环境。当前证据因此分成两层:1.00 有运行时结果;6/16 有汇编、机器码和部分入口探针结果,完整的清空行为仍未验证。

证据摘录
Seattle Computer Products 8086 Assembler Version 2.44pts1 Error Count = 0 86DOS.bin = 5,446 bytes
2026-09-13,构建记录中的 Linux 环境。1981 年的源码,在现代兼容汇编器下仍然得到 Error Count = 0。
本章证据
档位事实出处复核方式
机器实证源码 60,124 字节经 SCP ASM 2.44 重实现汇编,Error Count = 0,产出 5,446 字节内核;与现代 IBM PC 适配盘内核差异 49 字节BUILD_RECORD.md(`_evidence/vol2/remote_build/`)证明该源码副本能被兼容汇编器接受并产出机器码;49 字节分类见第四册
机器实证1.00 侧功能 12 行为已完成;6/16 侧完整运行未完成第四册第 5 章;`_evidence/vol4/E4_SUCCESS.md` / `E4_ATTEMPT.md`1.00 红屏已记录;6/16 因缺少匹配 DOSIO/初始化契约,按未验证处理
外部资料asm244l 来自 pts-86-dos-reconstruction;IBM PC 适配映像来自 TheBrokenPipe/86-DOS_PCAdaptation两个公开仓库出处已在构建记录中注明

术语速览

差异清单(DIF) 记录两个文件逐处不同的清单。86DOS.DIF 记录 1.00(5/5)与 6/16 快照之间的全部差异。
改动块(change block) 差异清单里一组相邻的增/删/改。以 10 个星号及以上为边界,块内不拆分。
修订台账 源码文件开头用注释写的版本记录。86-DOS 的台账从 0.34 排到 1.00,每一行写明日期和这次改了什么;第一册第一章全文引过。
打印行号 打印清单给每行源码编的号(每行加 10)。除以 10 得到被打印文件自身的行号;它与行号主基准树可能差几行,本册正文行号一律以主基准树为准。
汇编期开关 用 IF/ENDIF 写在源码里的常量开关。汇编时决定一段代码编不编进系统,而不是运行时才判断。
源码牵连度 一个符号在源码里被引用的总量,分定义、条件汇编分支、直接引用、数据四类。只统计文本,不判断运行时哪些路径真的会走到,所以叫「牵连度」,不叫「影响」。
SMALLDIR 0.42 引入的汇编期开关:打开则同时支持 16 字节老目录。6/16 关闭,1.20 删除。
BIOS 机器里固化的一段程序,操作系统读写磁盘、屏幕都要通过它办事。它住在内存的固定位置。
内存段 8086 把内存分成一段一段来用,段号就像区号。40H/60H 是十六进制的段号。
FCB 文件控制块。程序与内核之间关于一个文件的「合同」,第一册第五章讲过,DOS 沿用 CP/M 的约定。
汇编 把文字形式的程序翻译成 CPU 能执行的机器码。做这件事的程序叫汇编器。
16/32 字节目录项 目录里描述一个文件的数据块大小。32 字节是 0.42 之后的新格式,16 字节是更早的老格式。
BIOSSEG BIOS 所在内存段的常量。1.00 是 40H,6/16 快照改为 60H,新机器的指纹。
INT 12H BIOS 的标准服务之一:报告内存大小。6/16 起内核用它替代自己的内存探测循环。
连续演化 第 8 章的结论:在现有差异清单和两份源码中,改动呈分散小块,接口和数据结构保持连续;这更支持局部演化,不能排除局部重写。
反事实 先设想另一种情况应当留下什么痕迹,再与实际材料比较的推理方法。第 8 章用它比较整体重写与局部演化。
实验规格 先规定输入、预期和判定方式,再搭建环境。当前状态是 1.00 侧已测,6/16 侧完整行为仍未验证。

参考文献

[1](SRC)Paterson-Listings 仓库:2_printed_files/bundle_08/86DOS.DIF(差异清单)
Tim Paterson 提供原始清单;DOS-History 团队转录;微软 2026-04-28 MIT 发布。文件头注明对比对象:86DOS.ASM created 05-MAY-1981 与 86DOS.A86 created 16-JUNE-1981。本册的 117 个改动块、类型分布、行号全部来自这份清单。
本册用途:全册素材。
[2](SRC)同一仓库 3_source_code:1.00 与 6/16 两个快照、MS-DOS 1.25 台账
同上。86-DOS_1.00/86DOS.ASM(改动前)、PC-DOS_1.00_dev/86DOS_1981-06-16.ASM(改动后)、MS-DOS_1.25/MSDOS.ASM(台账含 1.20 Kill SMALLDIR)。
本册用途:每块改动的双侧核对与「后来留下了吗」的追踪。
[3](DOC)Tim Paterson 往来邮件(2025-04-24)
随仓库发布。5/5/81 时间戳口径;清单堆内容清点。
本册用途:第 0 章 4/28 与 5/5 关系的当事人口径。
[4](WEB)微软开源博客:Continuing the story of early DOS development(2026-04-28)
Stacey Haffner / Scott Hanselman。发布说明与「打印版提交历史」的比喻。
本册用途:材料性质的官方描述。
[5](SRC)pts-86-dos-reconstruction:SCP ASM 2.44 的 NASM 重实现
pts(项目作者)。github.com/pts/pts-86-dos-reconstruction。asm244l.nasm 编出的 Linux i386 静态程序接受 SCP 汇编语法、目标 8086,产出与原汇编器逐位一致。
本册用途:第 9 章 9a 的汇编器。
[6](DOC)TheBrokenPipe/86-DOS_PCAdaptation:86-DOS 各版本的 IBM PC 适配软盘映像
TheBrokenPipe(Yufeng Gao)。github.com/TheBrokenPipe/86-DOS_PCAdaptation 的 release 提供 86-dos_ibm_pc_port_all_versions.zip,内含 1.00 的 160KB 启动映像;该映像属于现代 IBM PC 适配复刻工程,源码编译内核与映像内核的 49 字节差异由第五类证据逐字节分类,不能整体称为键盘映射。
本册用途:第 9 章 49 字节对比的基准;现代适配盘的来源与性质。
[7](SRC)本册分析管线(vol2_*.py 六个脚本)
本系列自建。vol2_dif.py(差异清单切分摸底)、vol2_catalog.py(117 块归档)、vol2_taxonomy.py(五类初筛)、vol2_ripple.py(牵连度)、vol2_smalldir.py(SMALLDIR 全量明细)、vol2_data.py(BIOS 收拢/高密段/DATE 全链)。规则与口径写在各脚本头部。
本册用途:全册数据实证的出处。
证据边界,如实标出。全册 10 章。全部数字由 _evidence/vol2/ 下的脚本算出:catalog(117 块归档)、taxonomy(五类初筛)、ripple 与 smalldir(牵连明细)。差异清单是打印件转录,可能含誊抄噪声;关键块均已回两个快照源码核对。动机类表述均为推断并已标注。本页自包含:无外部请求、无脚本、样式全内联。