深入理解计算机系统(4.1)------Y86指令集体系结构

941 阅读8分钟

  本章我们将进入处理器体系结构介绍的神秘海洋中,我们熟悉的手机,电脑等设备的核心硬件都离不开处理器。处理器可以称的上是人类创造的最复杂的系统之一,一块手指大小的硅片,可以容纳一个完整的高性能处理器、大的高速缓存,以及用来连接外部的逻辑电路。而且由于摩尔定律,从性能上讲,今天一块芯片上的处理器,已经使得三十年前比房间那么大的超级计算机都相形见绌了。

  那么可能有人会问,我们软件开发者,永远都不会自己去设计处理器,那我们为什么要学习处理器的实现?

  ①、首先处理器的设计是非常有趣而且重要的,处理器设计包括很多好的工程实践原理,它需要完成复杂的任务,而结构又要尽可能的简单和规则,我们去了解事物是怎样工作的有其内在的价值。

  ②、处理器是整个计算机能正常工作的重要组成部分,理解处理器如何工作能帮助我们理解整个计算机如何工作。

  ③、虽然我们不用去设计处理器,但是我们工作的产出很多都是在包含处理器的硬件系统上运行的,了解它能让我们工作更有效率。

  我们知道计算机系统底层硬件只识别机器语言,而处理器就是用来执行一系列指令,每条指令执行某个简单的操作。比如两个数相加,汇编指令 ADD 会被编码为一个或多个字节组成的二进制格式。

  这里一个处理器支持的指令和指令的字节级编码称为它的指令集体系结构(Instruction-Set Architecture,ISA)。

  而不同的处理器家族,比如Intel IA32、IBM/Freescale PowerPC和AMD处理器家族,都有不同的ISA。这和我们上一章讲的汇编语言是直接面向处理器(Processor)的程序设计语言,不同类型的CPU 有不同的机器指令系统,也就有不同的汇编语言是一样的。不同的处理器,其指令集体系结构也不一样,也就是说一个程序编译成在一种机器上运行,就不能在另外一种机器上运行,如何处理这种兼容性问题呢?ISA 在编译器编写者和处理器设计人之间提供了一个抽象概念层,编译器编写者只需要知道允许哪些指令,以及它们是如何编码的;而处理器设计者必须建造出这些指令的处理器。

1、Y86指令

  本篇博客我们主要讲解的是Y86指令体系结构。首先我们要知道的是Y86指令是不存在的,这是本书的作者受到 IA32指令,也就是“x86”的启发,所假想出来的一种处理器体系结构,与 "x86" 相比,Y86指令集的数据类型、指令和寻址方式都要少一些,字节级编码也比较简单。但是它仍然足够完整,能够写一些简单的处理证书的程序,而设计一个Y86处理器要求我们面对许多处理器设计者同样面临的问题。所以学习Y86处理器的设计是很有必要的。

  定义一个指令集体系结构,会包括定义各种状态元素,指令集和它们的编码、一组编程规范和异常处理事件。

  Y86程序中的每条指令都会读取或者修改处理器状态的某些部分,这便称为程序员可见状态,这里的程序员既可以是用汇编代码写程序的人,也可以是产生机器级代码的编译器。在处理器实现中,只要我们保证机器级程序能够访问程序员可见部分,就不需要完全按照ISA 隐含的方式来表示和组织这个处理器状态。

  和IA32一样,Y86程序员可见部分包括:寄存器、存储器、条件码、PC(程序计数器)、程序状态。

  在Y86当中,寄存器也是有8个,每一个寄存器可以存储一个字,也就是一个32位二进制。条件码是一个一位二进制的寄存器,保存着最近的算术或逻辑运算所造成的影响的信息。PC则是程序计数器,记录当前正在执行的指令的地址。存储器则是一个很大的字节数组,保存着程序和数据,Y86的程序可以使用虚拟地址(类似于数组的下标)来访问存储器,硬件和操作系统会将虚拟地址翻译为实际的地址。最后一个程序状态(stat),它则代表着程序的运行情况。它会指示程序是否正常运行,或者发生了某个特殊事件。

  下图是 Y86 ISA 各个指令的描述,左边是指令的汇编码表示,右边是字节编码。它只包括四字节整数操作。

image.png

  halt :这个指令会停止指令的执行。在IA32中有个与之相当的指令 hlt,不过IA32的应用程序不允许使用这条指令,因为它会导致整个系统暂停运行。而对于Y86来讲,执行 halt 指令会导致处理器停止,并将状态码设置为 HLT。

  nop:这是一个占位指令,它不做任何事情,后续为了实现流水线,它有一定的作用。

  xxmovl:这是一系列的数据传送指令,其中r代表寄存器,m代表存储器,i代表立即数。比如rrmovl指令,则代表将一个寄存器的值,赋给另外一个寄存器。

  OPl:这包括4个整数操作指令,addl、subl、andl和xorl。他们只对寄存器数据进行操作。

  jXX:包括7个跳转指令,jmp,jle,jl,je,jne,jge,jg。根据分支指令的类型和条件码的设置来选择分支。

  cmovXX:包括6个条件传送指令,cmovle,cmovl,cmove,cmovne,cmovge和cmovg,只发生在两个寄存器之间,不会将数据传送到存储器。

  call:指令将返回地址入栈,然后跳到目的地址。

  ret:call是过程调用,ret是返回。将返回地址入PC,并跳到返回地址。

  pushl和popl:指令实现了地址的入栈和出栈

2、指令编码

  指令集的一个重要性质就是字节编码必须要有唯一的解释。任何一个字节序列要么是一个唯一的指令序列的编码,要么就不是一个合法的字节序列。

  Y86就具有这个性质,因为每条指令的第一个字节有唯一的代码和功能组合,给定这个字节,我们就可以决定所有其他附加字节的长度和含义。这个性质保证了处理器可以无二义性的执行目标程序代码。即使代码嵌入在程序的其它字节中,只要从序列的第一个字节开始处理,我们仍然可以很容易的确定指令序列。反过来,如果不知道一段代码序列的起始位置,我们就不能准确的确定怎样将序列划分为单独的指令。对于试图直接从目标代码字节序列中抽取出机器级程序的反汇编程序和其它一些工具来说,就带来了困难。

  对于如下两个图:

  下图是整数操作、条件传送和分支指令的具体编码:

image.png
下图是8个程序寄存器对应的标识符ID

image.png
我们应该怎么确定指令 rmmovl %esp,0x12345(%edx)的字节编码?

  首先最最上面的一幅图,我们可以看到rmmovl 的第一个字节是40,。源寄存器%esp应该编码放在rA字段中,而基址寄存器%edx 应该编码放在 rB 字段中,本博客的第三幅图我们知道这两个寄存器的标识符ID为42。最后偏移量编码放在4字节的常数中,我们在0x12345的前面填上0变为4个字节,也就是字节序列 00 01 23 45,写成按字节反序就是 45 23 01 00。所以整个连接起来就是:404245230100

3、Y86异常

  对于Y86来说,程序员可见的状态中包括stat状态码,它标识了程序执行的状态。这个状态码的可能值如下:

image.png
对于Y86,当程序遇到异常时,我们就简单的让处理器停止执行指令。但是在更完整的设计中,处理器通常会调用一个异常处理程序,这个过程被指定用来处理遇到的某种类型的异常。

4、总结

  本篇博客我们简单介绍了Y86的指令集结构,相对而言不难理解。后面将会介绍具体的逻辑设计和硬件控制语言HCL。