
计算机发展史大致可以划分为三个时期:大型机时期、小型机时期和微型机时期。小型机在笨重的早期大型机与如今无处不在的微型机之间架起了一座重要的桥梁。本文的主角——PDP-11,正是有史以来最具影响力和最成功的小型机。
小型计算机的身影曾遍布各个领域:通信控制器、仪器控制器、大型系统预处理器、桌面计算器,乃至实时数据采集处理器。但更重要的是,它们为硬件架构的飞跃奠定了基础,并极大地推动了现代操作系统、编程语言以及我们如今熟悉的交互式计算模式的发展。对于任何一名对计算机基础感兴趣的人来说,了解这段历史都像是找到了开启现代技术大厦的钥匙。
如今,几乎每台电脑都运行着某一版本的 Windows、macOS 或 Linux,这使得我们很难分辨底层 CPU 的差异。但在过去,不同的 CPU 架构意味着截然不同的世界观,而 PDP-11 的处理器或许能最好地解释个中缘由。
PDP-11 诞生于 1970 年。那时,大部分计算任务都在昂贵的 GE、CDC 和 IBM 大型机上完成,只有极少数人有机会接触。没有笔记本电脑、台式机或个人电脑这回事。编程仅由少数几家公司完成,主要使用汇编语言、COBOL 和 FORTRAN。数据通过穿孔卡输入,程序以非交互式的批处理模式运行。
尽管第一台 PDP-11 性能平平,但它为小型机的普及铺平了道路,让新一代计算机更易获取,由此引爆了计算机领域的革命。PDP-11 催生了 UNIX 操作系统和 C 编程语言,并对后世的计算机架构产生了深远影响。在其长达 22 年的生命周期中——以今天的标准来看简直不可思议——PDP-11 的销量突破了 60 万台。
早期的 PDP-11 型号并非十分出色。第一台 PDP-11/20 售价高达 2 万美元,却只配备了大约 4KB 的 RAM。它以纸带作为存储介质,控制台是一台 ASR-33 电传打字机,每秒只能打印 10 个字符。但它也拥有令人赞叹的特性:正交的 16 位架构、8 个寄存器、65KB 的地址空间、1.25MHz 的时钟周期,以及能灵活支持未来硬件外设的 UNIBUS 总线。对其制造商——数字设备公司(Digital Equipment Corporation,简称 DEC)而言,这无疑是一个成功的组合。
PDP-11 最早的应用领域包括实时硬件控制、工厂自动化和数据处理。随着其灵活性、可编程性和经济性日渐声名远播,它被用于交通信号灯控制、耐克导弹防御系统、空中交通管制、核电站、海军飞行员训练系统以及电信系统。它还开创了我们今天习以为常的文字处理和数据处理技术的先河。
PDP-11 的影响力最能从其汇编编程中窥见一斑。
汇编语言编程基础
在 Python、Java 和 FORTRAN 等高级语言出现之前,编程是用汇编语言完成的。汇编语言编程只需极少的内存和存储空间,这使其非常适合早期计算机的发展环境。
汇编语言是一种低级中间格式,会被转换成机器语言,再由计算机直接执行。说它“低级”,是因为它直接操作计算机的体系结构。简单说,汇编编程就是逐字节地在硬件寄存器和内存之间搬运数据。PDP-11 编程的独特之处就在于其精巧的设计:每条指令都有存在的理由,且清晰明了。
16 位地址空间意味着每个寄存器可以直接寻址最多 64KB 的 RAM,其中最高的 4KB 保留用于内存映射的输入输出。早期的 PDP-11 可以通过寄存器段寻址,访问总计 128KB 的 RAM(稍后会详述)。所以,尽管 PDP-11 系统可能只有 4KB 的 RAM,但通过巧妙运用早期编程技术,程序仍能高效运行。
汇编语言程序
理解汇编最好的方式是通过一个简单的 PDP-11 示例程序。以“.”开头的关键字是汇编器指令。.globl 将一个标签作为符号导出给链接器,供操作系统使用。.text 定义了代码段的起始。.data 则定义了一个独立数据段的起始。以“:”结尾的关键字是标签。汇编编程使用标签来符号化地寻址内存。(注意:由于接下来会用到 PDP-11 的术语和代码,斜杠“/”之后的任何文本都是注释。)

尽管你可以直接用数值作为内存地址,但使用标签而不是硬编码地址,能简化编程,并使代码可以在内存中重定位。这赋予了操作系统运行代码时更大的灵活性,从而确保每个程序都能快速高效地运行。
汇编器指令 .data 将数据放在可读写的内存段中。代码段则是只读的,这能防止编程错误导致程序崩溃。PDP-11 上这种“指令与数据分离”的特性,不仅提升了稳定性,还让地址空间翻倍,代码和数据各享 64KB——这在当时是一项相当前卫的创新。正因如此,英特尔的 x86 微型计算机后来广泛采用了分段结构。

PDP-11/20 的原装前面板。图片来源:Rama & Musée Bolo
PDP-11架构
PDP-11 凭什么更易于编程?答案在于它简洁而强大的架构。
八个 16 位寄存器是它指令集架构(ISA)的核心。其中六个是通用寄存器,一个是堆栈指针,一个是程序计数器。寄存器可以使用八种不同的寻址模式,访问任何其他寄存器、内存或立即数。每个寄存器都能对 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 移动到由寄存器 0 的内容加上 START 地址所得到的地址
堆栈指针和程序计数器
如前所述,PDP-11 将指令和数据分别存储在不同的内存段中。堆栈指针(SP)用于管理数据内存,程序计数器(PC)则管理代码的执行顺序。
在计算机发展的早期,CPU 的寄存器数量有限,大多数操作都在内存中完成。程序员通过栈(Stack)和堆(Heap)来管理内存数据。堆通常用于存储全局变量、数据和常量。栈则用于存储动态变量和子程序中用到的数据。拥有一个专用的栈寄存器对程序员来说是一种真正的奢侈,也有助于加快程序执行速度。如今,编程语言大多采用垃圾回收机制自动管理内存。但在汇编编程中,内存的方方面面都必须手动管理,而栈指针指令正是你的得力工具。
以下是栈指针寻址方式:
- 延迟:
MOV (SP), R0 将栈指针指向内存中的值移动到寄存器 0
- 自动递增:
MOV $1, (SP)+ 将值 1 移动到栈指针指向的内存位置,并将栈指针的值加一
- 索引:
MOV ARRAYSTART(SP), R0 将 ARRAYSTART 的值加到栈指针的值上,创建一个有效地址,并将该地址的内容移动到寄存器 0
- 索引延迟:
MOV @VAL1(SP), R1
使用程序计数器
程序计数器可以像其他寄存器一样被访问,但这并不明智。在 PDP-11 处理器上,程序计数器一直忙于跟踪内存中的下一条指令。程序计数器旨在支持跳转、分支和其他控制流指令。指令 MOV START, PC 的效果与 JMP START 相同,都是从地址 START 开始执行代码。
区别在于,使用 JMP 或分支指令能更清晰地表明程序流程控制的改变,让程序更易读、更易追踪。以下是无条件跳转和分支的例子:
JMP START 跳转到 START 的地址,该地址可以是 16 位内存地址空间中的任何位置
这也被称为“长跳转”,因为 START 的有效地址使用了全部 16 位。长跳转也是直接寻址的一个实例。
BR CALC 跳转到 CALC 的偏移地址,向前最多 127 个字或向后最多 128 个字
这与 ADD CALC, PC 的效果相同,其中 CALC 是一个 8 位的值,可以为负数。
分支指令也称为“短跳转”,因为它跳转的距离有限。在这里,CALC 是一个间接地址,是相对于当前地址的偏移量。例如,如果 CALC 位于内存中 32 个字之后的位置,等效指令就是将程序计数器加上 32 个字。
为什么同时需要长跳转和短跳转?答案很简单:内存。长跳转中的 16 位地址会额外占用一个字节。在分支较多的程序或操作系统中,这可能会占用 8KB 系统内存中相当可观的一部分。这又是 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,或将寄存器 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 位为 0,则执行分支
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。
难点在于,执行完后如何返回到当初调用子程序的位置。传统的做法是在调用子程序前手动保存返回地址:
MOV PC, RET_ADDR 将程序计数器的地址保存到内存地址 RET_ADDR
然后在子程序末尾通过 MOV RET_ADDR, PC 复制回去。
但如果存在嵌套子程序调用——一个子程序调用另一个子程序——该怎么办?管理返回地址的复杂度和工作量会迅速飙升,导致代码不可靠且充满缺陷。PDP-11 通过 JSR 和 RTS 指令对,为我们解决了这个问题,消除了时间和内存的浪费。
JSR PC, START 跳转到子程序 START,并将程序计数器的值压入堆栈
这条指令执行了几个操作:首先,它将程序计数器的值(即当前正在执行的代码的内存地址)压入堆栈,堆栈指针加一。然后,它跳转到 START 地址,即子程序的起始处。在子程序结束时,执行:
RTS PC 从子程序返回,使用 PC
这会将返回地址从堆栈中弹出,并复制到程序计数器中,从而跳转回去。这类栈操作非常适合嵌套子程序。不过,你必须仔细协调堆栈的压入和弹出操作,任何一个步骤出错都可能导致严重的编程错误。
考虑下面这个子程序示例。假设我们要使用整数运算,将 100 个华氏度值转换为摄氏度值:
CLR R1 / 初始化度计数器
AGAIN:
CMP $100, R1
BGTE QUIT / 如果寄存器 1 的值大于或等于 100,则退出循环
PUSH R1 / 将华氏度值压入栈,作为 FTOC 子程序的参数
JSR PC, FTOC / 调用 FTOC 子程序
POP R3 / 获取摄氏度温度
MOV R3, VAL1(R1) / 将摄氏度值复制到内存中的数组
INC R1 / 递增 R1
BR AGAIN / 再次循环
QUIT:
... / 你程序其余的部分可以放在这里
.end
FTOC: / 将华氏度转换为摄氏度的子程序
POP R0
SUB 32,R0
MUL 5,R0
DIV 9,R0
Push R0
RTS PC
.data
VAL1: .WORD 100 / 我们的值数组
这里还有一个展示自动递增延迟寻址模式强大功能的代码示例。我们的目标是实现一个打印缓冲区,高效地将字符从内存中的一个位置移动到另一个可供打印的位置。
INIT:
MOV #SRC, R0 / 设置源地址
MOV #BUFSTART, R1 / 设置目标地址
MOV $76, R2 / 使用缓冲区大小设置循环计数
BEGIN:
MOVB (R0)+, (R1)+ / 移动一个字符,源地址和目标地址都加一
DEC R2 / 计数减一
BNE BEGIN / 循环返回
... / 程序的其余部分
使用更大的数字
就容量而言,16 位能存储的最大数值是 65,535。但如果要对更大的数字进行运算呢?ADC 和 SBC 就能派上用场了。
MOV $65535, R0 / 16 位中的最大值
ADD 1, R0 / 数值 65536 太大,无法用 16 位表示
ADC R1
在这种情况下,当加法运算导致 R0 的最高位溢出时,ADC 会将 R1 加 1,从而有效地将数字扩展到 32 位。此时,R1 就是这个数字的第 17 到 32 位,并且可以链接更多操作来继续扩展。稍加改进,就能支持任意大的整数。
使用 MUL 函数进行乘法运算时,目标寄存器的编号决定了运算结果是 16 位还是 32 位。如果目标寄存器为奇数,结果为 16 位;如果为偶数,结果将同时存储在两个寄存器中。例如:
MOV $32767, R0
MOV $10, R2
MUL R2, R0
系统指令和断点陷阱
PDP-11 还包含其他各种系统指令,其中包括:
HALT 停止 CPU
WAIT 等待中断
RESET 复位外部总线,包括硬件设备控制和状态寄存器
NOP 什么都不做(“空操作”)
以及几条用于支持调试的指令:
TRAP 用户触发中断
BPT 断点陷阱
IOT 输入/输出陷阱
RTI 从中断返回
陷阱指令允许计算机把当前正在执行的操作暂时“冻结”在一个安全的地方,去处理其他任务,之后再从中断的地方恢复执行。这对于调试或处理 I/O、优先级系统调用或用户自定义子程序非常有用。BPT 和 RIT 指令提供了构建调试器所需的基本功能。ADB 是第一个流行的 UNIX 调试器,也是功能强大的 GNU 调试器 GDB 的直接前身。ADB 可以设置断点、显示寄存器和内存中的值、设置值以及单步执行代码。虽然与 GDB 相比功能简陋,但在 20 世纪 70 和 80 年代,它是程序员的得力助手。
机器码级别的调试是如何工作的?当执行 BPT 指令时,处理器状态寄存器中的陷阱位会在每条指令执行完毕后,将处理器引导至特定的程序地址。这使得调试器能够一次单步执行一条指令。它也允许你在代码中设置断点,以便程序运行到某个标签或子程序处时自动停下,供你检查其内部状态。
要在任何使用 ADB 或 GDB 的 UNIX 或 Linux 程序上尝试此操作,请在调试器中启动程序:adb a.out。然后输入 :s 命令进行单步执行。此时,CPU 正在幕后使用断点指令。
Hello, World
这是一个与操作系统交互并使用系统调用来输出结果的程序:
/ Hello, World in PDP-11 Assembler
.text
sys write; msg; 15 / call UNIX system call, write to print msg text
sys exit
.data
msg:
我们调用系统写入例程,传入“Hello, World”字符串的地址,记为“msg”。我们使用 .data 汇编器指令告诉汇编器,msg 的地址位于数据段中。
“Hello, World”文本被定义为一个字节序列,后跟一个空字符。它的内存位置由标签“msg”定义。因此,我们通过以下步骤打印文本:将程序计数器的值压入栈,将文本的地址压入栈,然后跳转到写入例程的地址。写入例程从栈中弹出文本的地址并打印。写入例程通过执行 RTS 指令,将栈中的返回地址复制到程序计数器中,从而返回调用者。
这就是子程序的调用方式,也是子程序可以嵌套的基础。它同样是高级语言中嵌套子程序的底层机制。
启动PDP-11模拟器
为了真正体验 PDP-11 的性能,我们不妨在模拟器上跑一些代码。SimH 是一款强大的计算机模拟器,像一台时光机,带我们回到计算机的过去。它让你能在模拟环境中运行数十种经典计算机的代码,其中就包括好几种 PDP-11 型号。我们将使用 SimH、脚本和启动数据,配置一台运行早期 BSD UNIX 版本的 PDP-11。在这个系统中,我们可以编译并运行一些汇编程序示例。这个版本的 UNIX 在大学中很流行,常被用来教授 PDP-11 汇编语言等课程。以下说明假设你安装在 Linux 系统上,但也能适用于其他平台。实际上,我是在 Windows 系统下的 Ubuntu Linux 虚拟机中,运行 SimH 里的 2.11 版 BSD UNIX。我使用 Oracle 的 VirtualBox 在 Windows 下运行 Ubuntu,再在 Ubuntu 里运行 SimH。
我们将使用 Warner Losh 的 SimH 配置,该配置适用于拥有 4MB 内存、两台 RD54 磁盘驱动器、一个 TS11 磁带驱动器和一个系统控制台的 PDP-11/93 系统。Warner 精心为这最后一款、也是最好的 PDP-11 系统重建了 UNIX 启动介质(他还提供了 UNIX 启动过程的详尽描述)。这个版本的 UNIX 实际上支持以太网,所以有雄心的人甚至可以把这个模拟的 PDP-11 系统接入互联网。
让我们开始配置。首先,启动进入你的 Ubuntu 系统,打开终端应用以访问命令行。
为 PDP-11 构建最新版本的 SimH:
% mkdir simh
% cd simh
% sudo apt install make
% sudo apt install gcc
% wget http://simh.trailing-edge.com/sources/simhv311-0.zip
% unzip simhv311-0.zip
% make pdp11
% sudo cp BIN/pdp11 /usr/local/bin
Now retrieve and assemble the boot image for 2.11 BSD UNIX patch 195:
% sudo apt install git
% sudo apt install expect
% mkdir mk211bsd
% git clone https://github.com/bsdimp/mk211bsd
% cd mk211bsd/195
% wget https://www.tuhs.org/Archive/Distributions/UCB/2.11BSD-pl195.tar
% tar xvf 2.11BSD-pl195.tar
% gunzip *.gz
% perl mk211p195tape.pl
% expect 211bsd-195.expect
# Edit the last two lines of 211bsd-195.ini to remove the
# Start the simulator:
% pdp11 211bsd-195.ini
: ra(0,0) unix
: ^D
Login as root with no password:
login: root
[1] root--> df
如果你进行到了这一步,恭喜你——你已经成功组装并启动了 1992 年发布的、适用于 PDP-11 的最后一版也是最好的 UNIX。

现在,我们已经在模拟的 PDP-11 上配置并启动了经典的 UNIX,是时候编写并运行一个基本的汇编程序了。

接下来,运行你的老伙计 vi 编辑器,创建一个 makefile 文件,将程序编译并链接成可执行文件,以此提升你的技术达人形象:

然后,输入你的汇编程序:

输入 make 编译并链接你的程序。现在,输入命令 ./hw 运行你的程序。
通过在模拟的 PDP-11 上编写和运行代码,你重温了小型计算机的黄金时代。五十年前——在手机、个人电脑、位图屏幕和鼠标出现之前——人们就是这样写代码的。
遗留系统:PDP-11、UNIX 和 C

UNIX 和 C 语言的创造者丹尼斯·里奇和肯·汤普森与一台 PDP-11/20 电脑合影。图片来源:PanelSwitchman
UNIX 操作系统最初诞生于 PDP-7 处理器,但在 PDP-11 处理器上得以完善。UNIX 的第一个版本是用 PDP-11 汇编语言编写的,包含 34 个系统调用、4200 行代码,运行在 12KB 的主内存上。文件大小限制为 64KB。它提供了分层文件系统、roff 文本格式化程序、ed 编辑器、用于处理磁盘、磁带和纸带的系统管理工具,甚至内置了二十一点、国际象棋和井字棋等游戏。
最重要的是,UNIX 提供了一个可经由廉价终端访问的交互式、分时系统。搭载 UNIX 的 PDP-11 打开了廉价交互式计算的大门,进而引爆了办公效率的爆炸式增长。人们终于有了编辑、存储和打印办公文档的手段。这在商业领域的意义显而易见,但变革才刚刚开始。
1977 年,一位名叫约翰·莱昂斯的计算机科学家撰写了有史以来最著名的计算机书籍之一:《UNIX操作系统注释》。书中对 UNIX 内核系统代码进行了逐行注释和描述。这本书风靡一时,直到 AT&T 的律师叫停了它的出版。(如今,你可以在这里 http://www.lemis.com/grog/Documentation/Lions/ 找到这本书的合法版本。如果你想了解 UNIX 内核的历史功能,这本书是你的不二之选。)
C 编程语言由编写 Unix 的同一组人开发,它源自 BCPL,而 BCPL 又是 Algol 的后代。C 语言的演进充分利用了 PDP-11 的指令集。以下 C 语言特性可以直接编译到 PDP-11 架构:
? 运算符与 TST 指令直接对应
- 算术和逻辑运算符与 PDP-11 指令直接对应
- 位操作编译为字节数据操作
- 用于内存寻址的指针是直接对应的
虽然 C 语言中的 ++ 和 -- 运算符对应着 DEC 和 INC 指令,但它们的灵感其实源于 PDP-7 的寻址模式。
到 1973 年夏天,C 语言已足够成熟,可以编译 Unix 系统,从而形成了一个良性循环:用 C 编写 Unix 程序加速了 Unix 的开发,带来了更多可用的 Unix 功能,进而促进了 PDP-11 系统在科研、工业、制造业和学术界的销售。这反过来又促使 PDP-11 系统变得更大、更快,从而能够承载更强大的 Unix 功能。
C 语言是一项重大进步;它是一种可跨 CPU 移植的语言,并能生成高效的操作系统代码。C 语言的成功催生了 Mac 上的 Objective-C,而 Objective-C 最终发展为今天的 Swift。贝尔实验室以 C 为基础创建了 C++。 Sun Microsystems 又以 C++ 为基础创建了 Java。微软则借鉴 Java 创建了 C#,并用 C# 编写了 .NET 框架。其他源自 C 语言的还包括 JavaScript、TypeScript、Go 和 Rust。
但最值得一提的是,AT&T Unix 催生了 BSD Unix,BSD Unix 又催生了 macOS,并最终演变出了 iOS。(AT&T Unix 还催生了 Linux 和 GNU,进而衍生出 Red Hat、Ubuntu、SUSE、Debian、Gentoo、Slackware 以及其他众多发行版。)

Unix 系统族谱。图片来源:维基百科
这些成就值得铭记,但即便在当时,PDP-11 也是最具影响力的计算机之一。它在问世后的 20 年里售出了 60 万台,直到 1990 年才停产。PDP-11 深受大学、研究机构和电信公司的青睐,为现代计算技术的发展铺平了道路。
希望这篇关于 PDP-11 架构及其编程方式的介绍,能让你更好地理解计算机技术如何以及为何会从非交互式批处理计算演变至今。简而言之,PDP-11 帮助普及了我们今天习以为常的交互式计算范式。如果你正在寻找一款最能代表小型机发展历程的设备,PDP-11 就是不二之选。
延伸阅读
对于想深入了解的朋友,Eduard Desautels 教授慷慨地开源了他的教材《PDP-11 和 LSI-11 计算机汇编语言程序设计》。这本书曾广泛作为大学 PDP-11 汇编语言编程的入门教材。对我们许多人来说,它开启了“接近底层”编程的新世界,也为我们理解未来的计算机架构奠定了基础。它堪称该领域最优秀的著作之一。你可以在这里查看本书的 PDF 版本:PDP-11 汇编语言程序设计。