0%

计算机组成原理复习:第三章 指令系统

这篇文章的内容主要是上课的时候写的,复习的时候我又修改了一些地方。

计算机组成原理复习:第三章 指令系统

指令对应机器语言,是计算机中最基本的命令。

本章先整体介绍了指令的结构,包括了操作码、地址码两个部分,并简要介绍两者所占长度的问题。之后,本章对地址码和操作分别进行了进一步介绍。先是介绍了地址码的具体知识,因为地址码可能不表示一个具体的地址,而是通过一些间接的方式来获取地址。特别地,有一部分存储区构成了栈结构,有一些操作专门是针对堆栈的。然后是一些具体的指令操作。最后,本着从普适性到具体性的思想,本章介绍了指令集中应该具有的操作,以及一些具体的指令集的发展过程。

指令格式

指令就是一段二进制代码。指令可以分成两部分,前一部分是操作码,后一部分是地址码。我觉得可以把操作码看成函数名,地址码看成函数参数。准确来讲,地址码是表示数据的代码,用地址码获取数据的方式再寻址技术部分会提到。

地址码结构

函数的参数可以有多有少。我们默认讨论双操作数系统。在指令这个“函数”中,最多支持4个参数。可惜的是,这个函数只支持默认参数来实现重载。也就是说,所有的指令实际上都会需要4个参数,但有些参数可能存在其它位置(可以看成一个全局变量),或者某些操作不需要所有4个参数。所以指令实际传入的操作数数量可能是0~4个。

指令传入的4个参数是:第一操作数地址,第二操作数地址,运算结果地址,下一条指令的地址。和C++的默认参数一样,每少输入一个参数,就代表最后的那个参数被设为了默认值。

3参数指令默认将下一条指令的地址设为当前指令地址+1。2参数指令把运算结果到第一操作数的地址。1参数指令把另一操作数存在某一个寄存器里。0参数指令一般是堆栈操作,把栈顶元素当成参数来进行操作。

操作码结构

操作码的长度可以固定,也可以不固定。后者可以节约空间,因为多地址的操作中地址码的长度较长,可以分配比较短的操作码。

显然,和哈夫曼编码一样,不能有一个操作码是另一个操作码的前缀。

寻址技术

寻址指获取操作数或指令的地址。实际上,这一节不仅讲了如何寻址,还先讲了如何编址。

编址

编址就是给寄存器、主存或IO设备中的存储单元编号,这样通过一个编号就能唯一地找到存在某处的一个数了。

编址最重要的是编址的最小单位。如果编址最小单位是字,那么在一个字长16位的系统里,你只能通过地址区分每16位数,而不能唯一确定数据某个字中的哪个地方;如果编址最小单位是字节,那么你就可以区分每8位的数字;如果编址最小单位是位,那么你可以精确地访问每一位了。

显然,编址最小单位是位的话,地址就会变得非常长了。还按字节编址比较好一些。在C语言中,我们可以得到每一个char的地址,这大概是字节编址的一个体现。

编址长度和主存的字长不是一个概念。

寻址

主要讲怎么寻找数据的地址。

最简单的是把数据直接放在指令里,这样不需要取通过地址来访问数据了。也就是往函数直接传了个值。但这样传进去的值的位数可能不够多。

还可以传数据的地址,也就是传指针。有时为了方便,还可以寻两次地址,也就是传指针的指针。数据可以在寄存器里找,也可以在主存里找,共$2\times 2 =4$种方法。这里还要多提一句,寄存器间接寻址指的是第一次在寄存器里找地址,第二次还是在主存里找。

此外,为了方便地获取一段连续的数据,还可以用基础地址加偏移量的方式获取数据。用C语言来写的话,就是

*(p + i)p表示基础地址,i是偏移量。如果i在寄存器中,就叫做变址寻址;如果p在寄存器中,就叫做基址寻址。理论上这两种寻址方式表示的意思都是一样的,硬件实现方式也可能是相同的,但二者使用上有一些细微的差别。由于用户希望改变对于某个基址的偏移量i,因此变址寻址是一个面向用户的操作。

此外,在基址寻址的基础上,基址p可以来自程序计数器。这种寻址方式叫相对寻址。

指令类型

数据传输

数据是在寄存器和主存之间传输的,理论上存在4种不同起点和重点的传输指令。把寄存器看成0,主存看成1,有序对<0,0>,<0,1>,<1,0>,<1,1>就可以表示4种数据传输的指令了。

此外,还有对堆栈的PUSH和POP指令。但是,这两种指令本质上也是对于主存的操作,并不能称为最基本的操作。

除了单向传输,还可以交换两个位置的数据。

运算

包含C语言中的一些基本运算。比如加减乘除模、按位逻辑运算、移位运算。但书中没有具体讲这种指令的结构(比如有几个操作数)。

流程控制

 转移

转移有两种:有条件转移和无条件转移。有条件转移用于实现条件语句,而无条件转移让程序结构更加灵活,不必受到顺序存储的束缚。

转移都要定义转移到的地址这一参数。对于有条件转移,还要把比较条件作为参数。

子程序调用

一个程序通过一些指令,可以调用另一个程序。只是根据调用关系,把被调用的程序叫做子程序。

程序本身就是被调用的,这种调用其他程序的关系中体现了递归的思想。

子程序调用也可以让程序不按顺序执行,而是跑到另一个地方去执行。在这一点上,它和转移十分类似。为了搞清这两个概念,要知道这两个概念间的区别。

子程序调用与转移的区别
  1. 子程序有“返回”这一操作。在调用了子程序后,一定要进行返回,返回到调用子程序的下一条指令。
  2. 机器中应该有“程序”这个概念,也就是说不同程序之间是有明确的界限的。转移只能在当前程序中转移,而调用子程序会跳转到子程序的开头。

既然调用子程序的时候要返回,那么机器必须存储返回的地址。事实上,每调用一次子程序就会产生一条返回地址信息。有多种存储返回地址的方法。

返回地址的存储
  1. 存到子程序的开头。我觉得这种方法很蠢,递归调用完全不可行了。但如果没有递归的话,这样设计节约了存储空间。
  2. 先存到寄存器中,再让子程序把寄存器的内容存到内存中。这样不错。
  3. 存到堆栈中。这种方法和2的本质是一样的。函数调用本身就类似栈的行为,用栈模拟的话更加自然,更加易于操作。但是,我觉得这种分类还是不够清楚。方法2里,你把东西存在内存里,在内存里也可以构成一个堆栈。方法3理论上是方法2的一个改进,而不是完全没有交集的两个方法。

返回指令

返回指令明明是和子程序调用配套出现的,不懂书上为什么要单独列出来。

输入输出指令

输入输出往往要用到除主存之外的存储区域。因此,IO指令可以分成两类。一种是IO地址单独编址,从0开始命名;另一种是把IO的地址接到主存后面。前者较独立编址,后者较统一编址。

我感觉这一小节涉及的内容还是比较少的,考试应该不会考。

指令集的发展

讲了一些很虚的东西,没什么价值。