计算机科学中,有一个非常核心的工程方法论,就是“接口”。
系统通过“接口”隔离外部与内部,外部只关注接口功能,不关注内部实现,这样就降低了复杂度和耦合性。接口,通常分为“输入”和“输出”两种。所谓功能就体现在:给到什么样的输入,以及得到什么样的输出。
起初,系统可以是非常简单的小逻辑单元,完成一个非常简单的任务,比如:一个加法器电路。它有两个输入项:加数1,加数2,还有一个输出项:和。那么,这两个输入和一个输出,就形成了这个小单元对外的“接口”。
有了接口,外部使用者不需要关心这个小逻辑单元内部的实现原理,而只关注它的功能就可以了。使用者的目的,就是想办法利用现有的资源的接口,实现更加复杂的逻辑和功能。
比如,给定若干个上面提到的加法器,要实现一个能处理三个加数连续相加的“连加器”,要如何实现?一个可能的方案是:选取两个加法器,把其中一个加法器的输出,连接到另一个加法器的某个输入端,如下图所示:
通过一定的逻辑组合,两个子系统(加法器)组成了一个功能更加强大的新系统(连加器)。新的系统有四个接口,三个输入,以及一个输出。
可以想见,通过继续扩展加法器的数量,可以不断增强连加器的功能,使得它可以接受越来越多的加数并输出结果。
加法没问题了,如果想要实现减法呢?我们就需要引入另外一种器件(子系统),取反器。我们知道,二进制数的求负,是通过取反并加一来实现的。于是,我们可以设计出如下的减法器。
现在,我们有了加法器,和减法器,可以把它们组合起来进行更复杂的加减混合运算了。
随着引入子系统的种类和数量越来越多,组合逻辑越来越复杂,我们也能得到功能越来越强大的系统。关键是,在构建系统的过程中我们所用到的方法论:把每个子系统看做是独立的组件,使用者只关心接口的功能(接受什么输入,得到什么输出),不关心内部实现,使用者通过接口之间的连接,组成更大更复杂的系统。
大到CPU与操作系统,小到APP的内部架构,可以说99%的计算机工程都是构建在上述方法论基础之上的。而这一篇的主题,主要就是想研究 “CPU” 这个硬件系统,对操作系统(使用者)而言所暴露出来的“接口”。
现代计算机的功能当然很强大,但从CPU的角度,其实只需要支持几种最基本的运算类型就可以了,因为复杂运算最终也是可以通过算法分解为基本运算的。CPU中负责运算的主要部件是ALU,它通常可以完成加减乘除、与或非等算数和逻辑运算,其设计原理跟我们上面提到的加法器大同小异,只不过要复杂许多。
下图是一个简化了的ALU的逻辑图,输入接口接收操作数,控制接口接收计算类型,输出接口产生结果。
在CPU中,所有的输入、输出数据都会保存在寄存器当中,所以下图也可以表示成:
以“加法”为例:我们将加数和被加数分别写入输入寄存器,在控制寄存器中写入“加法命令”,然后,ALU就开始运行,它先分析控制寄存器中的命令,发现是要执行“加法运算”,因此会去输入寄存器中取出两个操作数,进行加法运算,然后将得到的结果保存在输出寄存器中。
ALU是CPU中的运算单元,除了运算,CPU的另外一个重要功能,是能够“自动执行”。事实上,上面的“加法”过程就可以看做是“一个语句序列”(或者说,程序)。
1、将被加数送至“输入寄存器1”
2、将加数送至“输入寄存器2”
3、将“加法命令”送至“控制寄存器”
4、执行加法运算(ALU的工作)
5、从“输出寄存器”读取结果
可以看到,在上面的一系列工作中,ALU只完成了一部分的工作(只有第四步),其他部分的工作显然需要有其他部件来完成,CPU中的这个部件叫做“控制器”(CU)。
控制器的输入,通常是一段程序的首地址(第一行语句的位置),接下来,控制器从这里开始,逐条地读取语句,分析语句,执行语句,然后再继续读取下一条语句。过程如下:
通常,程序(语句序列)和数据(操作数)都保存内存中的某个地方,我们需要告诉控制器第一行语句的位置(输入),控制器就开始了“读取语句——分析语句——执行语句——读取下一条语句”的无限循环。以上面的加法程序为例,仔细看控制器的每个动作:
1、取回一条语句
2、分析语句 —— “将,操作数1(语句中包含了操作数在内存中的位置),送至,输入寄存器1”
3、执行语句 —— 去内存中指定位置读取操作数,将其写入输入寄存器1
4、取回下一条语句
5、分析语句 —— “将,操作数2(语句中包含了操作数在内存中的位置),送至,输入寄存器2”
6、执行语句 —— 去内存中指定位置读取操作数,将其写入输入寄存器2
7、取回下一条语句
8、分析语句 —— “将,“加法指令”,送至,控制寄存器”
9、执行语句 —— 将指令代码写入控制寄存器
10、取回下一条语句
11、分析语句 —— “让ALU执行加法运算”
12、执行语句 —— 发送控制信号让ALU开始工作
13、取回下一条语句
14、分析语句 —— “将,输出寄存器中的数据,送至,内存(语句中包含了要保存结果的内存位置)”
15、执行语句 —— 将结果读出并写入内存
16、取回下一条语句
虽然我们只是拿加法程序举了个例子,但不管多么复杂的程序,CPU控制器的动作基本上都没有太大变化,永远是上面的循环。而且,如果仔细分析,会发现其实它的工作其实很“枯燥”,无非就那么几类:
1、找到内存中的某个位置。不管是要取回指令,还是要读出/写入数据,都需要先在内存中找到目标位置,这个过程我们称之为寻址。
2、解析指令。指令,是CU能够理解的最小命令单元。一个最常见的指令,就是传送数据,例如:从某地址读取某值,或者将某值写入某地址。另一类常见的指令是执行控制动作,例如让ALU执行加法操作。
3、从内存/寄存器中读取,或向内存/寄存器中写入数据。CU的几乎所有执行动作,最后都落地为读写操作。例如,让ALU进行加法运算,就是将操作数和加法指令写入对应的寄存器,然后再从寄存器中读取结果。事实上,从逻辑角度来看,CPU的绝大部分功能都是通过对数据的读写来完成的。这个思想将贯穿整个计算机系统,例如,对某个设备的控制和访问,也是通过对相应端口的读写操作来完成的。
在上面图中,CU、ALU、若干寄存器一起,组成了一个最基本的CPU系统。它们各有分工,CU可以看做是总指挥,它逐条地解析程序命令,并根据具体指令,完成数据的搬运(读写),或指挥其他部件(如ALU)完成工作,在这个过程中,各个寄存器就是根据需要用来临时存放数据的地方。这个过程有点像“推箱子”游戏,CU不断地在各个寄存器之间“腾挪”数据,最终是为了让数据得到适当的处理并送至最终的目的地。
把这个图在抽象和简化一下,能够得到一个极度简化的CPU”接口“逻辑图。输入接口,是初始指令的地址,在程序执行过程中,根据具体指令的不同,还涉及到与内存之间进行指令或数据上的输入/输出。
看起来,CPU中各部件的工作流程简单、机械,但却快速高效,周而复始。但只要我们能够编写出适当的指令序列,就能够让CPU通过这个机械的流程最终实现相当复杂的计算和逻辑处理,这就是传说中的”编程“。
正常情况下,CU从第一条语句开始,不停地获取下一条语句,并进行解析和执行。因为每条语句在内存中所占用的空间是一定的,所以CU永远能够准确地知道”下一条“语句的位置。这个按照顺序执行语句的过程,就叫做顺序执行。但很多时候,只有顺序执行时不够的,我们会希望CU在执行过程中跳过若干条语句,直接跳转到某条语句开始执行,或者干脆跳转到一个新的代码段的首地址开始执行,因此CPU也必须要有支持指令跳转的能力。
跳转,通常是通过一条特定的指令来完成的。当CU在顺序执行过程中,遇到一条跳转指令,它就会根据指令的具体内容,重新进行寻址,定位到要跳转的目的地,然后放弃本来准备读取的下一条语句(终止顺序执行),而是直接从新的目的地址获取新的语句。
现在,我们的CPU可以支持顺序执行,和指令跳转,还能进行数据传送以及基本运算。虽然看起来挺简单,但实际上已经能够应付绝大多数类型的程序了。不过,从上图中我们看到,对CPU的使用者来说,当把第一条语句的地址输入到CPU接口以后,就失去了对CPU的控制,接下来CPU就像是”脱缰的野马“,完全机械地严格执行程序,直到运行至最后一条语句。对”管理者“来说,这显然是有风险的。当管理者把一项工作交给员工,他还希望能够随时干预员工的工作过程,比如,给他插入另一个优先级更高的工作任务,或者,强行终止他的工作。
因此,对CPU的使用者来说,需要有另外一个”接口“,通过它能够对一个正在执行程序的CPU进行”干预“。现实世界中,我们把这个接口,称作”中断“。下图,是引入了中断接口以后的CPU逻辑图。
CPU接收到中断信号以后,无论当前在做什么事情,都会暂停下来,然后CU会按事先的约定,跳转到一个固定地址,那里通常放着一段中断处理代码,CU会取回中断处理代码的第一条语句,然后继续它的机械循环。
到目前为止,我们的CPU拥有以下几种接口:
1、寻址输入。程序首地址就是一个地址输入,这里是CPU工作的起点,事实上,这通常是一个公共的约定地址,在CPU加电启动以后会自动跳转到改地址获取第一条指令。此外,取指、跳转、读写操作,都需要给CPU一个目标地址的输入。寻址,是CPU必须拥有的能力之一。
2、指令输入。程序中的每条指令,可以看做是对CPU的输入,CPU会根据不同的指令产生不同的输出(或动作)。我们前面已经接触到了几种不同类型的”指令“:传送数据指令、运算指令、跳转指令。当然,对一个真实的CPU来说,能支持的指令种类还有非常多。一个CPU能够支持的所有指令类型,组成它的指令集,指令集是衡量CPU特性的重要特性。
3、数据输入/输出。CPU既有数据的输入接口,用来读取数据,也有数据的输出接口,用来保存数据。通常,绝大部分的数据传输会发生在CPU与内存之间。此外,与计算机连接的各种设备也通过数据端口与CPU通信,CPU通过对这些端口的读写来实现与设备的交互。
4、中断输入。这是CPU的一个重要特性,有了它,才可能实现多任务等技术特性。(否则,CPU只能埋头做一件事,哪里有多任务可言呢?)
现实中,CPU的直接使用者通常是操作系统。操作系统的任务,就是利用CPU暴露出来的有限接口,通过程序控制,再封装出更加强大和丰富的接口,给上层使用者(通常是应用程序)使用。例如,进程、线程、文件管理,等等。这些内容,将在后续文章中陆续介绍。