计算的历史可以说可以分为三个时代:大型机、小型机和微型计算机。小型机在第一台大型机和当今无处不在的微型机之间架起了一座重要的桥梁。这就是 PDP-11 的故事,这是有史以来最有影响力和最成功的小型计算机。
当时,小型计算机被用于各种应用。它们用作通信控制器、仪器控制器、大型系统预处理器、台式计算器和实时数据采集处理器。但它们也为硬件架构的重大进步奠定了基础,并为我们今天所知的现代操作系统、编程语言和交互式计算做出了巨大贡献。
在当今的计算世界中,每台计算机都运行 Windows、Mac 或 Linux 的某些变体,很难区分操作系统下的 CPU。但是曾经有一段时间,CPU 架构的差异很重要。PDP-11 有助于解释为什么会这样。
PDP-11 于 1970 年推出,当时大多数计算都是在昂贵的 GE、CDC 和 IBM 大型机上完成的,很少有人可以使用。没有笔记本电脑、台式机或个人电脑。编程仅由少数几家公司完成,主要是在组装、COBOL 和FORTRAN方面。输入是在穿孔卡片上完成的,程序以非交互式批处理运行。
尽管第一台 PDP-11 规模不大,但它为微型计算机的入侵奠定了基础,这将使新一代计算机更容易获得,本质上是一场计算革命。PDP-11 帮助诞生了UNIX 操作系统 和 C 编程语言。它还将极大地影响下一代计算机体系结构。在 PDP-11 的 22 年使用寿命期间——按照今天的标准,这是闻所未闻的任期——售出超过 600,000 台 PDP-11。
早期的 PDP-11 模型并不过分令人印象深刻。第一台 PDP-11 11/20 售价 20,000 美元,但它只配备了大约 4KB 的 RAM。它使用纸带作为存储设备,并有一个 ASR-33 电传打印机控制台,每秒可打印 10 个字符。但它也有一个惊人的正交 16 位架构、8 个寄存器、65KB 的地址空间、1.25 MHz 的周期时间,以及支持未来硬件外围设备的灵活 UNIBUS 硬件总线。对于其创建者 Digital Equipment Corporation 来说,这是一个成功的组合。
PDP-11 的初始应用包括实时硬件控制、工厂自动化和数据处理。由于 PDP-11 在灵活性、可编程性和经济性方面享有盛誉,它在交通灯控制系统、耐克导弹防御系统、空中交通管制、核电站、海军飞行员训练系统和电信中得到了应用。它还开创了我们现在认为理所当然的文字处理和数据处理。
PDP-11 的影响在设备的装配编程中最为明显。
汇编程序编程基础
在 Python、Java 和 Fortran 等高级语言被发明之前,编程是用汇编语言完成的。汇编语言编程可以用很少的 RAM 和存储完成——非常适合早期计算环境。
汇编语言是一种低级的中间格式,可以转换为机器语言,然后可以直接由计算机运行。它是低级的,因为您直接操纵计算机体系结构的各个方面。简而言之,汇编编程通过硬件寄存器和内存逐字节移动数据。对 PDP-11 进行编程的不同之处在于小型机的设计非常优雅。每条指令都有它的位置,每条指令都有意义。
16 位地址空间意味着每个寄存器可以直接寻址高达 64KB 的 RAM,顶部的 4K 保留用于内存映射的输入和输出。PDP-11 可以使用寄存器段寻址总共 128KB 的 RAM(稍后会详细介绍)。因此,尽管 PDP-11 系统只有 4KB 的 RAM,但通过巧妙地使用早期的编程技术,它们仍然高效。
一个汇编语言程序
通过一个简单的 PDP-11 汇编语言程序的例子来理解这个概念是最容易的,我们将在下面进行介绍。以“.”开头的关键字 是对汇编器的指令。.globl
将标签作为符号导出到链接器以供操作系统使用。.text
定义代码段的开始。.data
定义单独数据段的开始。以“:”结尾的关键字是标签。汇编编程使用标签来象征性地寻址内存。(注意:随着 PDP-11 行话和编码的出现,/ 之后的任何文本都是注释。)
关键词 | 翻译 |
.globl _main | 导出标签_main作为操作系统使用的入口点 |
。文本 | 只读代码所在的指令段的开始 |
_main:MOV VAL1,R0 | 将内存位置 VAL1 的字值复制到寄存器 0 |
加 10 美元,R0 | 将寄存器 0 中的值加 10 |
移动 R0, VAL1 | 将寄存器 0 中的值复制到内存位置 VAL1 |
_.data | 读/写数据所在的数据段的开始 |
VAL1:.成为 100 美元 | 预留 2 个字节的存储空间来保存 Val1,初始化为 100 |
虽然数值可以用于内存地址,但使用标签而不是硬编码地址可以更容易地编程并使代码在内存中可重定位。这在运行代码时为操作系统提供了灵活性,确保每个程序快速高效。
.data 汇编器指令将数据放入可读可写的内存段中。代码的内存段是只读的,以防止编程错误破坏程序并导致崩溃。PDP-11 上的这种指令与数据的分离称为“分离指令和数据”。除了增加稳定性之外,此功能还通过启用 64KB 的代码和 64KB 的数据使地址空间翻倍——这在当时被认为是一项相当大的创新。因此,英特尔的 X86 微机后来大量使用了分段。
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) /递减寄存器 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 系统 RAM 的相当大一部分。这是另一个巧妙的 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
这是以下循环的汇编语言等效项:
对于 (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 /如果前一个结果等于 0 则分支,即如果 Z 位设置为 1
BNE/ Branch 如果前一个结果不等于 0,即如果 Z 位清零。
BGT /如果第一个操作数大于第二个,则转移
BGE /如果第一个操作数大于或等于第二个,则转移
BLT /如果第一个操作数小于第二个,则转移
如果第一个操作数小于或等于第二个,则BLE /转移
数据操作
我们来看看数据操作的说明:
MOV $1, R0 /将值 1 移入寄存器 0
MOV $1,VAL1 /将值 1 移动到内存位置 VAL1
VAL1: .byte 0, 0 /下面是如何使用 VAL1 的地址分配和初始化 2 个字节或 1 个字的空间。每当引用符号 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 为基数的符号。其中,八进制的 123 与以 10 为基数的 83 或二进制的 1010011 相同。由于我们在 UNIX 下使用 PDP-11 进行编程,因此我们使用 base 10。
使用子程序
让我们通过处理 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 /初始化度数计数器
再次:
CMP 100 美元,R1
BGTE QUIT /如果寄存器1大于等于100,退出循环
PUSH R1 /将 F 值中的度数推入堆栈,作为 FTOC 子程序的参数
JSR PC, FTOC /调用 FTOC 子程序
POP R3 /检索摄氏温度
MOV R3, VAL1(R1) /将摄氏度值复制到内存中的数组
递增 R1 /递增 R1
BR AGAIN /再次循环
放弃:
… /你的程序的其余部分可以放在这里
。结尾
FTOC: /将华氏度转换为摄氏度的子程序
流行音乐 R0
SUB 32,R0
穆勒 5, R0
9 区,R0
按 R0
即时战略电脑
。数据
VAL1: .WORD 100 /我们的值数组
这是另一个代码示例,展示了自动递增延迟寻址模式的强大功能。在这个例子中,我们试图实现打印缓冲区的操作,以有效地将字符从内存中的一个位置移动到另一个可以打印的位置。
在里面:
MOV #SRC, R0 /设置源地址
MOV #BUFSTART, R1 /设置目标地址
MOV $76, R2 /使用缓冲区大小设置循环计数
开始:
MOVB (R0)+, (R1)+ /移动一个字符并增加源地址和目标地址
DEC R2 /计数减一
BNE BEGIN /循环返回
… /程序的其余部分
使用更大的数字
在容量方面,16 位可以容纳多达 65,536 个数字。但是,如果您想对大于此的数字进行数学运算怎么办?ADC 和 SBC 前来救援。
MOV $65536, R0 / 16 位最高值
ADD 1, R0 /值 65537 太大,无法保存在 16 位二进制补码中
ADC R1
在这种情况下,当 R0 的底部字从加法中溢出时,ADC 将 1 加到 R1,有效地将数字再扩展 16 位,创建一个 32 位数字。R1 现在是这个数字的第 17 位到第 32 位,并且可以将更多操作链接在一起以不断扩展数字的范围。多做一点工作,您就可以支持任意大的整数。
使用 MUL 进行数字相乘时,目标寄存器的编号决定了乘法是 16 位还是 32 位。如果目标寄存器为奇数,则乘法为 16 位。如果目标寄存器是偶数,则结果存储在两个寄存器中。例如:
移动 $32767, R0
移动 10 美元,R2
MUL R2, R0
系统指令和断点陷阱
PDP-11 有各种其他系统指令,包括:
HALT /停止 CPU
WAIT /等待中断
RESET /复位外部总线,包括硬件设备控制和状态寄存器
NOP /什么都不做(“无操作”)
以及支持调试的几条指令:
TRAP /用户调用中断
BPT/断点陷阱
物联网/输入/输出陷阱
RTI /从中断返回
陷阱指令允许计算机将它正在做的任何事情停在一个安全的地方,处理其他事情,然后从它停止的地方恢复原始处理。这对于调试或处理 IO、优先系统调用或用户定义的子例程很有用。BPT 和 RIT 指令支持构建调试器所需的基础知识。ADB 是第一个 UNIX 调试器,是 GDB 的直接前身,GDB 是强大的 GNU 调试器。ADB 可以设置断点、在寄存器和内存中显示值、设置值以及单步执行代码。虽然与 GDB 相比稀疏,但它是 70 年代和 80 年代程序员最好的朋友。
调试如何在机器代码级别工作?当 BPT 指令运行时,处理器状态寄存器中的陷阱位将在执行完每条指令后将处理器定向到某个程序地址。这允许调试器一次单步执行一条指令通过程序存储器。它还允许您在代码中设置断点,以便您可以运行到标签或子例程,然后停下来检查值。
要在任何带有 ADB 或 GDB 的 UNIX 或 Linux 程序上尝试此操作,请在调试器中启动一个程序:Adb a.out
. 然后通过键入单步:s
。CPU 正在使用断点指令。
你好世界
这是一个与操作系统交互并使用系统调用写入输出的程序:
我们用“Hello, World”字符串的地址调用系统写入例程,表示为“msg”。我们使用 .data 汇编器指令告诉汇编器 msg 的地址在数据段中。
“Hello, World”文本被定义为一个字节序列,后跟一个空字符。它的内存位置由标签“msg”定义。因此,我们通过将程序计数器寄存器的值压入堆栈,将文本的地址压入堆栈,然后跳转到写入例程的地址来打印文本。写例程从堆栈中弹出文本的地址并打印文本。写例程通过执行 RTS 指令返回到调用者的地址,该指令将堆栈的返回地址复制到程序计数器中。
这就是调用子程序的方式以及可以嵌套多少子程序。它也是高级语言如何嵌套子例程的底层机制。
启动 PDP-11 模拟器
要真正了解 PDP-11 的能力,让我们在模拟的 PDP-11 上运行其中的一些代码。SimH 是一款令人惊叹的计算机模拟器应用程序,是一款名副其实的时间机器,可以进入计算的过去。它允许您模拟运行数十台经典计算机的代码,包括几种类型的 PDP-11。我们将使用 SimH、脚本和引导数据来配置运行早期版本的 BSD UNIX 的 PDP-11。在此,我们可以编译运行一些汇编程序示例。这个版本的 UNIX 很受大学欢迎,并被用于教授课程,例如 PDP-11 汇编语言。这些说明假定安装在 Linux 上,但可以适用于其他平台。事实上,我在 Windows 中运行的 Ubuntu Linux 中的 SimH 中运行 2.11 BSD UNIX。
我们将为具有 4MB RAM、两个 RD54 磁盘驱动器、一个 TS11 磁带驱动器和一个系统控制台的 PDP-11/93 系统使用Warner Losh 的 SimH 配置。Warner 在最后和最好的 PDP-11 上煞费苦心地为最后和最好的 UNIX 重新创建了引导媒体(他也提供了 UNIX 引导过程的详细描述)。这个版本的 UNIX 实际上支持以太网,因此有野心的人甚至可以将模拟的 PDP-11 放到网络上。
让我们开始我们的配置。首先,启动进入 Ubuntu。然后,调出终端应用程序以访问命令行。
为 PDP-11 构建最新版本的 SimH:
如果你做到了这一点,那么恭喜你——你已经组装并引导到了 1992 年发布的用于 PDP-11 的最后一个最好的 UNIX 版本。
现在我们已经配置并启动到一个运行经典 UNIX 的模拟 PDP-11,让我们编写并运行一个基本的汇编程序。
接下来,通过运行老式版本的 vi 编辑器来提升您的科技街信誉,并创建一个生成文件,将程序编译并链接到可执行文件中:
接下来,输入您的汇编程序:
输入“make”来编译和链接你的程序。现在通过键入来运行您的程序./hw
。
通过在模拟的 PDP-11 上编写和运行代码,您可以重温小型机的黄金时代。五十年前——在手机、个人电脑、位图屏幕和鼠标出现之前——这就是你编写代码的方式。
遗留问题:PDP-11、UNIX 和 C
UNIX 操作系统在 PDP-7 上启动,但在 PDP-11 上得到完善。UNIX 的第一个版本是用 PDP-11 汇编程序编写的;它有 34 个系统调用,用 4,200 行代码编写,运行在 12KB 的主内存上。文件大小限制为 64K。它提供了一个分层文件系统、roff 文本格式化程序、ed 编辑器、用于处理磁盘、磁带和纸带的系统管理工具,它包括 Blackjack、Chess 和 tic-tac-toe。
最重要的是,UNIX 提供了一个交互式的分时系统,可以从廉价的终端访问。带有 UNIX 的 PDP-11 为廉价的交互式计算打开了闸门,从而导致了办公室生产力的爆炸式增长。人们终于有了编辑、存储和打印办公文件的方法。显然,这在企业界很重要,但事情才刚刚开始。
1977 年,一位名叫 John Lions 的计算机科学家撰写了有史以来最著名的计算机书籍之一: A Commentary on the UNIX Operating System。它包含 Unix 内核系统代码的带注释的逐行描述。这本书广受欢迎,直到 ATT 的律师禁止其出版。(今天,可以在这里找到这本书的合法副本。如果您想详细了解历史上的 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,这导致了今天的 Swift。贝尔实验室采用 C 并创建了 C++。Sun Microsystems 采用 C++ 并创建了 Java。微软采用 Java 并创建了 C# 并用它编写了 .NET。从 C 派生的其他语言包括 JavaScript、TypeScript、Go 和 Rust。
但最值得注意的是,ATT Unix 导致了 BSD Unix,后者导致了 MacOS,然后是 iOS。(ATT Unix 也导致了 Linux 和 GNU,这导致了 RedHat、Ubuntu、SUSE、Debian、Gentoo、Slackware 和其他源代码发行版。)
这些遗产值得铭记,但即使在当时,PDP-11 也是有史以来最具影响力的计算机之一。自推出以来的 20 年中售出 600,000 台后,它于 1990 年停产。它非常受大学、研究人员和电信机构的欢迎,并为现代计算技术铺平了道路。
希望通过对 PDP-11、其架构和编程方式的介绍,您可以更好地了解计算如何以及为何超越非交互式批处理计算。简而言之,PDP-11 帮助普及了我们今天认为理所当然的交互式计算范式。如果您正在寻找最能代表小型机系列的单一设备,那么 PDP-11 就是它。
进一步阅读
对于任何希望深入研究的人,Eduard Desautels 教授慷慨地同意开源他的教科书 《PDP-11 和 LSI-11 计算机汇编语言编程》,这本书在大学中被广泛用于向学生介绍 PDP-11 汇编编程。对于我们中的许多人来说,这是我们对“接近金属”编程的新领域的介绍,也是更好地理解未来计算机体系结构的门户。它可以说是这方面最好的书籍之一。您可以在此处查看该书的 PDF 版本:PDP-11 的汇编语言编程。