跳转至

指令集与存储体系

这是“程序的机器级表示”系列的第一篇文章。本篇初步介绍指令集,汇编等概念,并帮助建立起寄存器 ➡ 主存 ➡ 外存的存储体系印象图。

指令集与汇编

众所周知,高级语言编写的代码,要编译成汇编代码,再转换为机器代码才能被计算机执行。一个程序可以有很多炫酷的功能,一行高级语言的代码也可能有着复杂的含义,但一条机器指令只执行一个非常基本的操作。例如,将存放在寄存器中的两个数字相加,在存储器和寄存器之间传送数据,或是条件分支转移到新的指令地址。

一种处理器能理解的机器指令是有限的,我们称之为这种处理器的 指令集 (IS, Instruction Set) 。跟这种处理器有关的整体架构,包括指令集,寄存器,寻址模式等等被称为 指令集架构 (ISA, Instruction Set Architecture) 。了解了 ISA,我们就能用二进制的机器代码写出能被采用这种 ISA 的处理器正确运行的程序了。

但是二进制机器代码难以阅读,所以人类发明了汇编。汇编代码非常接近于机器代码,可以看作是机器代码二进制格式的文本化表达。所以汇编也是和 ISA 深度挂钩的,在不同处理器之间不能通用。

汇编或 ISA 指令都是一条一条的形式,可以认为处理器会顺序执行这些代码。事实上处理器会并发执行许多指令,并保证整体行为与指定的顺序执行的行为一致。

机器级程序与存储设备

在学习机器级程序之前,先来了解一下寄存器、主存等概念。处理器进行运算,数据从哪里来,存到哪里去,都与存储设备有关。

寄存器是位于处理器内部的,存储有限位数数据的一组存储单元,一般用来存储最近需要用到的数据,比如运行时栈的指针,上个调用函数的返回值,进程的状态等。寄存器的个数、存储位长、各个寄存器的作用,以及在汇编代码中的名称都和 ISA 的设计有密切的联系。

在 Intel 的 x86-64 指令集中,有 16 个存储 64 位值的 通用目的寄存器 ,用来存储整数数据和指针,如 %rsp 保存着栈指针,%rax 保存着返回值。除此之外还有众多其他的寄存器,如保存最近执行的算术或逻辑指令(由 CPU 中的 ALU 执行)状态信息的条件码寄存器等。

主存(等价于内存)是运行程序的主要场所,主要包含:程序的可执行机器代码,操作系统需要的一些信息,管理过程调用和返回的运行时栈,用户分配的内存块等程序运行所需的关键数据。现代计算机中进程所使用的内存都是所谓虚拟内存,即访问的内存地址不是真实的物理地址,操作系统负责虚拟地址和物理地址之间的转换。

而磁盘(或硬盘、U 盘)应算作“外部存储设备”,用来存储和程序运行没有直接关系的数据,如图片,文本等。

上述三种存储部件都是冯诺依曼体系中所谓“存储设备”,之所以要分这么多种,本质上是为了协调处理器和存储设备之间速度的差异。较大的存储设备比较小的存储设备运行得慢,而快速设备的造价远高于同类的低速设备。比如,256G 的硬盘比 16G 的内存条要大 16 倍,但对处理器而言,读取内存中的数据比读取硬盘中的数据要快至少 100 倍,绝大多数情况下要差更多数量级,而只能存储几百字节的寄存器还要比内存快得多。

256G 的内存条价值在 10000 元以上,256G 的硬盘不到 200 元就能买到,而即使是 16G 的内存条也要 300 元左右。

半导体技术的进步使得处理器与主存之间的差距还在持续增大。所以存储体系采用了多层次的缓存结构,其思想就是将读取慢、容量大的设备中近期可能会被使用的数据缓存到读取快,容量小的设备,这样层层缓存,通过复杂的算法设计加速了存取数据的速度。处理器想要访问外存中的数据,也需要先将其读取到主存中。

实际上,处理器中也有多级缓存,作为主存的缓存,比寄存器慢,但要快于访问主存。这部分作为主存的抽象,在汇编中也被看作主存。所以汇编只区分存储器(主存)和寄存器,而对外部设备(包括外存)的访问则是通过 I/O 端口等机制来实现的,其核心思想是把外部设备中的寄存器也在主存中编址,通过读写其寄存器控制外设。

后记——计算机的抽象模型

了解上述内容之后,我们可以总结计算机的一个模型。计算机的 CPU 中有运算单元,若干寄存器,和 PC。PC(程序计数器)也是一个寄存器,它存放着下一条指令在内存中的地址。计算机可以把数据在内存和寄存器、寄存器和寄存器之间搬运,并通过运算单元进行数据运算,通过修改 PC 的值来确认下一条指令是什么。在现代计算机系统中,往往已经不止有一个处理器,这还会涉及到并发等复杂的机制,我们暂且不作介绍。

一个典型的计算机构造