Unix中的古怪注释和奇怪行为
摘要
丹尼斯·里奇讲述了早期Unix源代码中一些古怪的错误信息和注释,包括著名的“values of β will give rise to dom!”以及那句不可思议的注释“You are not expected to understand this.”
<p><a href="https://lobste.rs/s/csc385/odd_comments_strange_doings_unix">评论</a></p>
查看缓存全文
缓存时间: 2026/08/11 07:08
# Unix中的古怪注释与奇异事件
来源:https://9p.io/who/dmr/odd.html
### Beeta 的值
在第六版 Unix 中,**mv** 命令可能产生这样的诊断信息:
```
values of β will give rise to dom!
```
这条信息曾被一些人注意到,并被记录在一个 WWW 页面(http://www.tmk.com/ftp/humor/computer-error-messages.txt)或类似的地方,那些页面大多只是同一汇编的拷贝,收录了各种系统中难得一见的惊人错误信息。
实际产生这条信息的 **mv.c** 中的源码行是:
```
write(1,"values of \016B\017 will give rise to dom!\n",37);
```
只不过在真实源码中,\016B\017 是用字面上的 ASCII SI 和 SO 控制字符写成的。这些控制字符把 Model 37 电传打字机切换到了它的可选扩展字符集,于是 B 被打印成了希腊字母 beta。关于 beta 在软硬件变迁中如何呈现,见下文。
和这些汇编中记录的大多数信息一样,这条信息是在我们认为不太可能的情况下或由于滥用而产生的;具体细节并不重要。我要记录的是这个短语为什么会被选中。
Unix 在贝尔实验室的“实际业务”中的首次应用,是输入并制作专利申请文件;在 1970 年代初期的一段时间里,我们有三位打字员在六楼那个邋遢的实验室里忙碌地打字。有一天,有人走进来,看到从一台电传打字机里伸出的纸上,赫然孤立地印着这么一句不祥的话:
```
values of β will give rise to dom!
```
当然,很明显是打字员打断了打印输出(从 **ed** 编辑器生成了“\!”),然后向上移动了纸张;上下文必定类似于“变化的 beta 值将引起畴壁移动”或其他某个物理学上貌似合理的专利申请片段。
但这个短语本身就是如此醒目!完全没有意义,但它看起来像什么……警告?“dom”是什么?
与此同时,我们正在试验 Doug McIlroy 和其他人编写的文本转语音软件,当然也试着用它来读这个短语。不知为何,它朗读“give rise to dom!”时,对最后一个词的强调方式突出了“doom”与“dominance”第一个音节之间的语音相似性。它把“beta”读成英式风格的“beeta”。整个事件成了一个小小的、共同拥有的珍宝。
这个短语必须被记录在某个地方,于是它被记录在了 v6 源码中。最有可能是 Bob Morris 干的,但 Ken 也完全可能。
我希望你的浏览器能正确显示 β 为希腊字母 beta。这里写作 `'& beta ;'`,这在 MSIE 和一些 Linux 浏览器上有效,但在 Netscape 4.6(至少在我这里)上不行。以前我曾尝试用 `fontface=symbol b /fontface` 加上相应的尖括号来渲染,这在旧版 Netscape 和 MSIE 上有效,但近期的 Mozilla 不行。唉。如果你用的是旧版 Netscape,配合适当的 fontface,这里是:b。
## /\* 你不应该理解这个 \*/
在 Usenet 或其他地方,我时不时会看到有人提到第六版 Unix 操作系统源代码中的某条注释。
甚至有人送了我两件印着这句话的运动衫。
大多数人可能只是听说过,但亲眼见过的人要么拥有第六版 Unix(约 1975 年),要么读过 John Lions 对这个系统的注解版(1996 年再版:ISBN 1-57298-013-7, Peer-to-Peer Communications)。
它经常被引用来诋毁贝尔实验室研究版 Unix 中注释的数量或质量。恐怕总体而言这不是不公平的观察,但在这个具体例子上并不成立。周围的实际代码和其他注释正是这样:
```
/*
* Switch to stack of the new process and set up
* his segmentation registers.
*/
retu(rp->p_addr);
sureg();
/*
* If the new process paused because it was
* swapped out, set the stack level to the last call
* to savu(u_ssav). This means that the return
* which is executed immediately after the call to aretu
* actually returns from the last routine which did
* the savu.
*
* You are not expected to understand this.
*/
if(rp->p_flag&SSWAP) {
rp->p_flag =& ~SSWAP;
aretu(u.u_ssav);
}
/*
* The value returned here has many subtle implications.
* See the newproc comments.
*/
return(1);
```
所以我们试图解释发生了什么。“你不应该理解这个” 是本着“这不会考”的精神说的一句话,而不是一种无礼的挑战。真正的问题是,我们自己也不太明白到底发生了什么。savu/retu 机制进行进程交换的做法从根本上就是有缺陷的,因为它依赖于切换到之前的栈帧,并在与保存先前状态不同的过程里执行函数返回代码。这在 PDP-11 上有效,因为它的编译器总是使用相同的上下文保存机制;而对于 Interdata 编译器,过程返回代码因保存的寄存器不同而不同。
所以,对于 Steve Johnson 和我来说,在第一次把内核移植到新机器时,这段代码确实在考试范围内。我们痛苦了大约一个星期,才最终说服彼此:这个机制是错误的,再怎么折腾编译器也没用。我们彻底重写了协程控制传递原语,这段代码和这条注释也就成为了历史。
## 我确实感到内疚的注释
在 PDP-11 这样的 16 位机器上做 32 位乘法和除法需要聪明才智。我的 PDP-11 C 编译器使用子程序来做长乘法和长除法。乘法例程是:
```
/ 32-bit multiplication routine for fixed pt hardware.
/ Implements * operator
/ Credit to an unknown author who slipped it under the door.
.globl lmul
.globl csv, cret
lmul:
jsr r5,csv
mov 6(r5),r2
sxt r1
sub 4(r5),r1
mov 10.(r5),r0
sxt r3
sub 8.(r5),r3
mul r0,r1
mul r2,r3
add r1,r3
mul r2,r0
sub r3,r0
jmp cret
```
这个例程很巧妙,而我还不够聪明,没能自己想出来。不过我不感到内疚,因为当时我不知道是谁建议的,而且我确实承认这个事实。
但下面这个,我会在良心上背负一阵子。除法例程包含:
```
1:
mov r4,-(sp)
clr r0
div r3,r0
mov r0,r4 /high quotient
mov r1,r0
mov r2,r1
div r3,r0
bvc 1f
sub r3,r0 / this is the clever part
div r3,r0
tst r1
sxt r1
add r1,r0 / cannot overflow!
1:
```
我几乎(或者说可能完全)弄明白了它为什么有效。灵魂上的污点就在于那句“这就是巧妙的部分”的注释。
#### 附记 1998年10月18日
来自 nSOF 的 Amos Shapir(而且记忆力惊人!)刚刚在邮件里把这个污点弄得更黑(或者说更宽)了一点,如下:
> 我猜想这里的“几乎”是因为这个技巧几乎能行……它有一个严重的 bug,我不得不费了好大劲才发现!
>
> 这个“巧妙的部分”依赖于这样一个事实:如果没有跳转到“bvc 1f”,说明结果无法放入 16 位;在这种情况下,r0、r1 中的长值保持不变。问题是这个行为并没有文档记录;在较新的型号中(我是在 11/34 上发现的),当结果能放入 16 位但放不进 15 位(即对带符号类型溢出,但对无符号类型不溢出)时,溢出位会被设置,但无符号结果会覆盖原来的值——这使得这个例程产生非常奇怪的结果!
## 一个硬件故事
大约在 1970-71 年,Unix 在 PDP-11/20 上运行的硬件不仅不支持虚拟内存,也不支持任何形式的硬件内存映射或保护,例如防止写入内核。这很麻烦,因为我们正把这台机器用于多用户。当有人在调试程序时,按照礼貌,在尝试运行之前要大喊一声“A.OUT?”,以警告其他人保存他们正在编辑的内容。[一个支线故事:有一次,几个人正坐在那儿各自忙碌。Bob Morris 几乎是随口问道:“ld 的参数是什么?”有人告诉了他。接下来一分钟我们继续打字,一个念头开始慢慢渗透,但还没完全浮出水面——换句话说,还不够快。在任何人来得及停下来问“等等 Bob,你到底想干什么?”之前,终端已经停止回显了。]
我们知道 PDP-11/45 即将到来,它确实支持内核和其他进程的内存映射和保护,但不是马上。为了提前做准备,我们与 Digital Special Systems 商定购买一台带 KS-11 附加组件的 PDP-11/20。这是一个额外的系统单元,用螺栓固定在处理器上,使它能够区分内核模式和用户模式,并提供经典 PDP-10 风格的“高段”“低段”内存映射单元。我似乎记得我们订购时这种机器只生产了大约 6 台。
那些记得早期 PDP-11 的人会记得,当时没有乘法、除法或一次移动超过一位的移位指令,只有一个可选的 EAE(“扩展算术扩展”)装置,即 KE-11A,它在物理内存中出现在 I/O 设备空间里:把操作数存到这儿,再从那儿读回结果(实际上是在 777302 及后续地址)。
KS-11 必须处理的一个问题是,普通程序需要做乘法和除法,却又不允许访问 I/O 设备空间。所以它包含了检测 EAE 地址的电路,并将它们重映射到物理地址,而用户模式下所有其他虚拟地址都被映射。
当我们拿到 KS-11 机器时,EAE 完全无法工作。用测试程序测试,我们遇到总线错误,或者 EAE 单元毫无反应。但是,在那个年代,从 Digital Special Systems 买这种东西,你还能得到电路图纸,所以在一番苦思冥想和摆弄之后,我们查阅了图纸。
结果发现,在图纸的一页上,有一个用于 EAE 地址的地址比较器,旁边有一个指向页外的箭头,标着“EAE ADDRESS DETECTED H”。在另一页上,有一个指向页内的箭头,标着“EAE ADDRESS DETECTED L”。我们在这两者之间找不到任何连接。
最后,一位满脸尴尬的 Digital Special Systems 工程师来了一趟,他找到一个闲置的反相器引脚,然后加了几根白线。
如今,这样的问题就很难在现场修复了。
修订于 2002 年 6 月 22 日
相似文章
计算机科学的趣味故事
这篇文章汇集了计算机科学和软件开发中引人入胜的历史故事和轶事,突出了著名的错误、失败和设计见解。
关于UNIX哲学的误解 (2024)
对关于UNIX哲学的常见误解的分析,认为通常将小程序通过管道通信的解释是对原始设计原则的误解。
花括号:Unix和C语言的演变
详细探讨了早期Unix系统在Teletype Model 33上如何输入花括号,涵盖了ASCII 1963、三字符组、双字符组以及终端驱动程序转换。
数学很难(VAX 上的 OpenBSD)
OpenBSD 开发者细数 VAX 浮点异常的恼人怪癖,以及它们如何给内核移植添堵。
无法逃避:ANSI 转义码深度解析
技术探索 ANSI 转义码,追溯其 1970 年代终端起源,并探讨其在现代 CLI 工具和终端 UI 中的持久相关性。