快速了解 PDP-11:有史以来最具影响力的小型计算机(2022)

Hacker News Top 新闻

摘要

PDP-11 小型计算机于1970年推出,在计算机历史中影响深远,催生了 UNIX 和 C 编程语言。本文概述其架构和影响。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/16 23:36

# PDP-11 简史:史上最具影响力的小型计算机 来源:https://arstechnica.com/gadgets/2022/03/a-brief-tour-of-the-pdp-11-the-most-influential-minicomputer-of-all-time/ 计算机历史大致可分为三个时代:大型机、小型机和微型机。小型机在早期大型机和如今无处不在的微型机之间架起了一座重要的桥梁。这就是 PDP-11 的故事,它是有史以来最具影响力、最成功的小型计算机。 在其巅峰时期,小型机被用于各种应用场景:通信控制器、仪器控制器、大型系统预处理器、桌面计算器以及实时数据采集处理器。但更重要的是,它们为重要的硬件架构进步奠定了基础,并对现代操作系统、编程语言和交互式计算做出了巨大贡献,正如我们今天所熟知的那样。 在当今的计算机世界中,每台计算机都运行着某种 Windows、Mac 或 Linux 变体,很难区分操作系统底层的 CPU 有何不同。但曾几何时,CPU 架构的差异可是件大事。PDP-11 正好能帮助我们理解为什么当时情况如此。 PDP-11 于 1970 年推出,那时大多数计算任务都在昂贵的 GE、CDC 和 IBM 大型机上完成,很少有人能用得上。没有笔记本电脑、台式机或个人电脑。编程只由少数公司进行,主要使用汇编语言、COBOL 和 FORTRAN(https://arstechnica.com/science/2014/05/scientific-computings-future-can-any-coding-language-top-a-1950s-behemoth/)。输入靠穿孔卡片,程序以非交互的批处理方式运行。 尽管最初的 PDP-11 配置简陋,但它为小型计算机的普及铺平了道路,使新一代计算机更容易获取,实质上引发了一场计算革命。PDP-11 帮助孕育了 UNIX 操作系统(https://arstechnica.com/gadgets/2019/08/unix-at-50-it-starts-with-a-mainframe-a-gator-and-three-dedicated-researchers/)和 C 编程语言(https://arstechnica.com/features/2020/12/a-damn-stupid-thing-to-do-the-origins-of-c/)。它还极大地影响了下一代计算机架构。在 PDP-11 长达 22 年的生命周期中——以今天的标准来看,这种寿命闻所未闻——共售出了超过 60 万台。 早期的 PDP-11 型号并不令人印象深刻。第一台 PDP-11 11/20 售价 20,000 美元,但只配备了大约 4KB 的内存。它使用纸带作为存储介质,并配有一个 ASR-33 电传打字机控制台,每秒仅打印 10 个字符。但它拥有令人惊叹的正交 16 位架构、八个寄存器、65KB 的地址空间、1.25 MHz 的周期时间,以及一个灵活的 UNIBUS 硬件总线,可支持未来的硬件外设。对于其创造者数字设备公司(DEC)来说,这是一个成功的组合。 PDP-11 最初的应用包括实时硬件控制、工厂自动化和数据处理。随着 PDP-11 在灵活性、可编程性和经济性方面声名鹊起,它被用于交通灯控制系统、奈基导弹防御系统、空中交通管制、核电站、海军飞行员训练系统和电信领域。它还开创了我们今天习以为常的文字处理和数据处理的先河。 而 PDP-11 的影响力在其汇编编程中体现得最为显著。 ## 汇编器编程基础 在 Python、Java 和 Fortran 等高级语言被发明之前,编程都是使用汇编语言完成的。汇编语言编程只需要很少的内存和存储空间——非常适合早期计算环境。 汇编语言是一种低级中间格式,会被转换为机器语言,然后由计算机直接运行。说它低级,是因为你直接操作计算机架构的各个方面。简而言之,汇编编程是通过硬件寄存器和内存逐字节地移动数据。PDP-11 编程之所以与众不同,在于其设计优雅。每条指令都有其恰当的位置,每条指令都合情合理。 16 位地址空间意味着每个寄存器可以直接寻址最多 64KB 的内存,其中最高的 4KB 保留给内存映射输入输出。早期的 PDP-11 可以使用寄存器段来寻址总共 128KB 的内存(稍后会详细介绍)。因此,尽管 PDP-11 系统只配备了 4KB 内存,但通过巧妙地使用早期编程技术,它们仍然能够高效运转。 ## 一个汇编语言程序 通过下面这样一个简单的 PDP-11 汇编语言程序示例,最容易理解这个概念,我们将逐步解释它。以“.”开头的关键字是汇编器的指令。`.globl` 将一个标签导出为符号,供链接器和操作系统使用。`.text` 定义代码段的起始位置。`.data` 定义独立数据段的起始位置。以“:”结尾的关键字是标签。汇编语言使用标签来符号化地寻址内存。(注意:根据稍后出现的 PDP-11 术语和代码,`/` 之后的所有文本都是注释。) **关键字****含义** `.globl _main` 将标签 `_main` 导出为操作系统使用的入口点 `.text` 指令段起始位置,只读代码存放于此 `_main:` MOV VAL1, R0 将内存位置 VAL1 处的字值复制到寄存器 0 `ADD $10, R0` 将 10 加到寄存器 0 中的值上 `MOV R0, VAL1` 将寄存器 0 中的值复制到内存位置 VAL1 `.data` 数据段起始位置,可读可写数据存放于此 `VAL1: .word $100` 预留 2 字节存储空间存放 Val1,初始化为 100 虽然可以用数值表示内存地址,但使用标签而非硬编码地址可以使编程更容易,并使代码可在内存中重定位。这为操作系统运行代码提供了灵活性,确保每个程序快速高效。 `.data` 汇编器指令将数据放在既可读又可写的内存段中。代码的内存段是只读的,以防止编程错误破坏程序并导致崩溃。在 PDP-11 上,这种指令与数据的分离被称为“指令与数据分离”。除了增加稳定性之外,这个特性还通过允许 64KB 用于代码和 64KB 用于数据,使地址空间翻倍——这在当时被认为是一项重大创新。相应地,英特尔的 X86 微计算机后来广泛使用了段。 `另一件值得上画廊的老式技术:PDP-11/20 的原始前面板 PDP-11/20 的原始前面板。图片来源:Rama &amp; Musée Bolo (https://commons.wikimedia.org/w/index.php?curid=24518885) ## PDP-11 架构 是什么让 PDP-11 更适合编程?是其简单而强大的架构。 八个 16 位寄存器是指令集架构(ISA)的核心。其中六个是通用寄存器,一个是堆栈指针,一个是程序计数器。这些寄存器可以使用八种不同的寻址模式之一来访问任何其他寄存器、内存或直接数据。每个寄存器可以对 16 位字数据、8 位字节数据执行逻辑、数学或测试操作,也可以访问内存。寄存器还可以读/写 16 位字或 8 位字节的内存。后来的 PDP-11 版本增加了用于直接处理浮点数的寄存器。 以下是一些寻址模式的示例: ***/*****直接寄存器地址** MOV $1, R0 /*将数值 1 移动到寄存器 0 中*/ /**寄存器间接地址** MOV $1, (r0) /*将数值 1 移动到寄存器 0 所指向的内存中*/ /**自动递增** MOV $1, (r0)+ /*将数值 1 移动到寄存器 0 所指向的内存中,并将寄存器 0 中的地址增加 1 个字*/ /**自动递减** MOV $1, -(r0) /*将寄存器 0 中的地址减 1,然后将数值 1 移动到其新指向的内存中*/ /**索引寄存器** MOV $1, START(r0) /*将数值 1 移动到由寄存器 1 的内容加上 START 的地址计算得到的地址中*/ ## 堆栈指针和程序计数器 如前所述,PDP-11 将指令和代码保存在独立的内存段中。堆栈指针(SP)帮助你管理数据内存,程序计数器(PC)帮助你管理代码的执行顺序。 在早期的计算时代,CPU 的寄存器数量有限,因此大多数操作都在内存中完成。堆栈和堆是程序员在内存中管理数据的方式。堆通常用于全局变量、数据和常量。堆栈用于子程序中使用的动态变量和数据。为堆栈设置一个专用寄存器对程序员来说是一种真正的奢侈,并有助于加快程序执行速度。如今的编程使用垃圾回收这种现代技术来自动管理内存。但在汇编语言编程中,如果需要使用内存,你必须手动管理其方方面面。堆栈指针指令可以帮助你做到这一点。 以下是堆栈指针的寻址模式。 **/ 延迟** MOV (SP), R0 /*将堆栈指针所指向的内存的值移动到寄存器 0*/ **/ 自动递增** MOV $1, (SP)+ /*将数值 1 移动到堆栈指针所指向的内存,并增加堆栈指针的值*/ **/ 索引** MOV ARRAYSTART(SP), R0 /*通过将 ARRAYSTART 的值与堆栈指针的值相加来创建一个有效地址,将该地址的内容移动到寄存器 0*/ **/ 索引延迟** MOV @VAL1(SP), R1 ## 程序计数器的使用 程序计数器可以像其他任何寄存器一样被访问,但这样做并非明智之举。在 PDP-11 上,程序计数器忙于跟踪内存中的下一条指令。程序计数器旨在支持跳转、分支和其他控制流指令。指令 `JMP START` 的效果等同于 `MOV START, PC`:即开始在位置 START 执行代码。 区别在于,使用 JMP 或分支指令清楚地表明了控制流的变化,使程序更易阅读和跟踪执行路径。以下是无条件跳转和分支的示例: `JMP START` /*跳转到 START 的地址,该地址可以位于 16 位内存地址空间中的任何位置。*/ 这也被称为“长跳转”,因为“START”的有效地址使用了所有 16 位。长跳转也是直接地址的一个例子。 `BR CALC` /*分支到 CALC 的偏移地址,向前最多 127 个字,或向后最多 128 个字。*/ 这等同于 `ADD CALC, PC`,其中 `CALC` 是一个 8 位值,可以是负数。 分支指令也称为“短跳转”,因为你跳不了那么远。在这种情况下,CALC 是一个间接地址,它是相对于当前地址的一个偏移量。例如,如果 CALC 在内存中比当前位置靠后 32 个字,那么等效的指令就是将 32 个字加到程序计数器上。 为什么需要长跳转和短跳转?一个词:*内存*。长跳转中的 16 位地址会多使用一个字节。在分支很多的程序或操作系统中,这可能会消耗掉 8K 系统内存的相当一部分。这是 PDP-11 处理有限内存的另一个巧妙技巧示例。 ## 测试与分支 在 PDP-11 上,测试和分支指令是条件判断和循环的基础。 比较、数学和逻辑指令对数据执行操作,导致条件码(CC)更新。CC 是一个只读寄存器,其位根据这些操作的结果被设置。CC 包含 Z(零)位、N(负)位、V(溢出)位和 C(进位)位。分支和跳转的条件可以基于设置位值的操作之后 CC 的状态。 示例如下: CMP A,B /*通过从 A 中减去 B 来比较 A 和 B,并相应地设置 Z、N、V、C 位*/ BEQ OUT /*如果它们相等,则短分支到位置 OUT*/ 以下是以下循环的汇编语言等效代码: For (i=j=0; i< 100; i++) j = j + i; CLR R0 /*清除,即将寄存器 0 初始化为零*/ CLR R1 /*将寄存器 1 初始化为零*/ AGAIN: /*AGAIN 是该内存地址的一个内存标签*/ CMP R0, $100 /*将寄存器 0 与数值 100 比较*/ BGT QUIT /*如果寄存器 0 大于 100,则分支到 QUIT*/ ADD R0,R1 /*将寄存器 0 加到寄存器 1 上*/ INC R0 /*将寄存器 0 加 1*/ BR AGAIN /*分支到 AGAIN*/ QUIT: /*QUIT 是该内存地址的一个内存标签*/ TST 可用于检查单个值: TST A /*这将根据数据设置(置为1)或清除(置为0)Z 或 N 位*/ PDP-11 拥有一套完整的有条件分支指令,包括但不限于: BEQ /*如果前一个结果等于零,即 Z 位为 1,则分支*/ BNE /*如果前一个结果不等于零,即 Z 位被清除,则分支*/ BGT /*如果第一个操作数大于第二个,则分支*/ BGE /*如果第一个操作数大于或等于第二个,则分支*/ BLT /*如果第一个操作数小于第二个,则分支*/ BLE /*如果第一个操作数小于或等于第二个,则分支*/ ## 数据操作 让我们来看看数据操作的指令: MOV $1, R0 /*将数值 1 移动到寄存器 0 中*/ MOV $1,VAL1 /*将数值 1 移动到内存位置 VAL1 中*/ VAL1: .byte 0, 0 /*这里是分配和初始化 2 个字节(或 1 个字)空间的方法,地址为 VAL1。每当引用符号 VAL1 时,汇编器都会将其替换为内存中的地址。当程序被链接成可执行文件时,链接器可以重定位该符号地址。*/ ADD $1, R0 /*将数值 1 加到寄存器 0 的内容中*/ ADD $1, VAL1 /*将数值 1 加到内存位置 VAL1 的值中*/ SUB $10, VAL1 /*从内存位置 VAL1 的值中减去 10*/ 在几乎所有比较指令中,寄存器和内存位置可以互换使用。这给了汇编程序员更大的自由度,也减少了一项需要手动跟踪的事情。只要可能,将数据保存在寄存器中是有益的,因为每一条替代内存地址的寄存器指令都会减少内存占用并加快执行速度。这些是汇编编程相对于高级语言的关键优势——汇编代码几乎总是运行得更快,占用更少的内存。 ## 二进制补码表示法与负数 PDP-11 通过 16 位和 8 位整数原生支持正数和负数。后来的型号增加了浮点寄存器。 这样,算术逻辑单元(ALU)可以加减乘除正负整数。汇编器使使用数字变得简单。只需使用 $ 表示立即数,并使用尾随的“.”表示十进制——这两个 `$64.` 和 `$-1055.` 都是有效的数字。 传统上,PDP-11 使用八进制,即基数为 8 的表示法。在这种表示法中,0123 代表八进制数,等同于十进制的 83 或二进制的 1010011。由于我们是在 UNIX 下用 PDP-11 编程,所以我们使用十进制。 ## 使用子程序 让我们通过在 PDP-11 上处理一些情况来深入探讨。假设你正在编写一个汇编程序,并且有一段代码会在程序的不同部分重复使用。为什么不写一个子程序,然后从程序的不同地方调用它呢?简单的部分是跳转到子程序的起始位置:`JMP START`。 棘手的部分是如何返回到调用子程序的地方。旧式的方

相似文章

探索 PDP-1 Lisp (1960年)

Hacker News Top

关于运行1960年历史性的PDP-1 Lisp实现的详细介绍,包括启动过程及其作为首个交互式编程环境的重要意义。

计算机课程

Hacker News Top

关于早期计算机辅助教学系统的详细历史记录,重点介绍PLATO和DTSS,以及利用计算机彻底改变教育的前景。

Acorn Archimedes 上的 PipeDream

Hacker News Top

本文探讨了 Acorn Archimedes 及其 RISC OS 的历史,以 PipeDream 生产力套件为例,聚焦于这种偏离标准 WIMP 界面的早期计算创新。