指令系统层


通过前文《微体系结构层》,我们已经引入了指令系统层的概念,本文是对指令系统层更系统的阐述。并且以ARMv7指令系统层为例加以阐释

一、概述

  1. 什么是指令系统层?
    指令系统是指计算机所能执行的全部指令的集合,指令是一组有意义的二进制代码。指令的基本格式为操作码字段+地址码字段。

  2. 提出指令系统层概念的好处?
    不同CPU公司生产的计算机硬件不同,实现同一种操作逻辑的硬件实现过程是不同的。指令系统层要将硬件实现过程封装好,这样以后,各个计算机公司生产的同一系列的计算机尽管其硬件实现方法可以不同,但指令系统、数据格式、I/O 系统等保持相同,方便用户使用,也保持系统向上兼容。

  3. 复杂指令系统(CISC)与精简指令系统的(RISC)的特点
    提出复杂指令系统(CISC)与精简指令系统的(RISC)的初衷都是要提高计算机系统的计算性能。
    CISC通过缩小高级语言与机器指令之间的语义差距,简化目标程序,即认为计算机性能的提高主要依靠增加指令复杂性及其功能来获取。带来的代价是实现机器指令的硬件复杂度上升,微指令系统复杂程度上升,单一指令的执行周期加大,从而造成整个程序执行时间增加。
    针对CISC缺陷,提出了RISC思想,强调简单而统一格式的指令译码,采用高效的流水和编译方法进行流水调度,简化微指令系统,RISC 的主要问题是编译后生成的目标代码较长, 占用了较多的存储器空间,另一个潜在缺点是对编译器要求较高。

  4. 一条指令的完整格式是怎么样的?
    一条指令包含下列信息:(1)操作码,具体说明了操作的性质及功能,每一条指令对应一个操作码。(2)操作数从那里来,CPU通过该地址取得所需操作数。(寻址)(3)操作结果的存储地址,把指令处理的结果保存在该地址中。(4)下一条指令的地址

  5. 指令系统层有哪些指令?
    基本的指令有数据处理指令(包括算术运算、逻辑运算、移位、比较)、数据传送指令(寄存器之间、寄存器与主存储器之间)、程序控制指令(条件转移、无条件转移指令)、I/O指令(读写)、状态管理指令(存储保护、中断处理)。计算机也在根据用户需求不断引入新指令,例如为了防止不同进程同时进入临界区,出现了信号指令(PV操作)

  6. 执行一条指令的步骤?
    可分为以下几个步骤:(1)取指令阶段。将一条指令从主存中取出送到指令寄存器中。(2)指令译码阶段。在组合逻辑控制的计算机和微程序控制的计算机不同(3)指令执行阶段(4)访存取数阶段。根据指令需要,有可能访问主存读取操作数。(5)结果写回阶段

二、ARMv7指令系统层概述

  1. 我们说ARMv7是32位的,这里的32位指的是地址总线根数或者数据总线根数概念么

    1. 由浅到深分析这个问题,首先32位指的是寄存器的位数(即微体系结构层那些寄存器都是32位的)。
    2. 寄存器既可以暂存地址信息又可以暂存数据信息,显然32这个数字是人们设计出来的,那人们基于什么目标设计出的这个数字呢?是地址还是数据?
    3. 当然是数据总线,你32位数据总线,ALU一次就能处理32bit的数据,8位数据总线的ALU一次只能处理8bit的数据,在相同频率的情况下,8位数据总线的ALU就得连续处理4次数据,数据量才能和32位数据总线一次处理的数据量相同,性能差4倍呢。
    4. 但是要注意,定下32这个数字后,地址总线的大小也是32位了,限制了地址空间最大只能寻址到232(实际中你可能只用了几块内存芯片,远远没达到这个地址上限,但32这个数字限制了你最终能把地址扩到多大。)
    5. 所以ARM体系结构中,CPU一次处理一个字,一个字的大小是32位。这个概念不影响字节的概念(字节就是8位),字节的字长是人们基于使用习惯统一成8位的。
    6. 还有一个疑惑要解答:数据总线32位是不是意味着从内存某个地址取出来的数据是32位的呢?不是! 内存存储数据的基本单位是字节(即内存的物理地址按照字节计算的)。一个内存物理地址只能取一个字节的数据,32位的寄存器要取4个地址的内存数据才可以形成一个字。
    7. IJVM的MAR是面向字读取内存的,而内存的物理地址按照字节计算,因此,MAR中的数字传递到地址总线上时,需要一层映射(最简单的映射方法就是左移两位)
    8. 4字节构成一个字,一个字在内存里面的存储方式,有地址对齐式的存储,也有非对齐式存储,前者更好(因为更好读写),后者假如某个数据是从地址6开始的,CPU只能先读地址4-7的数据,再读8-11的数据,再组合,显然很麻烦。
    9. 一个字在内存里的存储,高位字是存储在地址低位还是高位?高位字存储在地址地位(大端存储)、高位子存储在地址高位(小端存储
  2. ARMv7指令系统层的寄存器有哪些

    1. 16个通用寄存器和32个浮点寄存器 指令系统层
  3. ARM体系结构特点

    1. 加载/存储体系结构。即能直接访问内存的唯一操作只有加载与存储。所有ALU操作的操作数只能来自于寄存器(而不是内存),同时执行结果必须保存在寄存器中。(而不是内存)

二、数据类型

指令系统层定义了各种指令,而指令是要对数据进行处理的(对操作数进行计算),因此,我们要了解清楚数据类型的概念。数据可分为数值型和非数值型。

数值数据类型

  • 最主要的数值数据类型为整形
    • 典型长度有8、16、32、64位(定义时占据的内存空间不同)
    • 无符号整数和有符号整数
  • 浮点型数据,许多计算机对整数操作数与浮点型操作数使用了不同的寄存器

非数值数据类型

  • 字符类型。还是要用01编码代表字符,常用的字符编码是ASCII(7位)和UNICODE(16位)
  • 字符串。字符串就是连续的字符流,要么通过特殊的结束符,要么通过长度域来表示字符串的长度。指令系统层对字符串提供拷贝、查找、编辑以及其他操作。
  • 布尔值。0为假,1为真,布尔值还是按照字节存储(字节有地址便于访问)
    • 这种方法优点浪费。用布尔值构成一个32位的数组,那么一个字(4个字节的地址)存储满了布尔信息。这种数据结构称之为位图

指针

  • 寄存器存储的01信息,这个信息代表一个机器地址,称之为指针。微体系结构层的SP、PC、LV都是指针。在寄存器间接寻址一章对指针更深入理解。

三、指令格式

指令格式准则

上一节知道了指令操作的对象有哪些类型,这一节讲解对这些对象进行操作的格式怎么定义的。

常见的指令格式如下。
指令系统层

一条指令占据一个字的大小是合理的,那么操作码该占据多大空间?地址该占据多大空间呢?

  • 我们思考:指令的长度是由最大指令长度决定的,那么一条最长指令占据的总大小=最大操作码位数+最长的三地址位数总和。 如果按照这样定义了长度,如果有一条0地址指令,他的操作码位数很少,而且他还没有地址,那么它势必存在的很大的空间浪费。
  • 那么,如何保证指令大小统一的前提下,尽量缩短指令长度呢?(扩展操作码的思想)
  • 具体实现如***意,最高4位为1111时标志了要开始使用8位来解释操作码了。当次高4位为1110和1111时,开始使用12位解释操作码了。当次次高位为1111时,开始使用16位解释操作码了。
    指令系统层

32位ARM指令格式

ARM采用加载/存储体系结构,所以没有寻址的概念,操作数的来源要么是寄存器、要么是立即数,操作数的去向也是寄存器,指令格式如下:
指令系统层

四、寻址方式

一条指令中,操作数是从那里来的呢,也就是寻址的概念

立即寻址

定义操作数的最简单方式是指令直接包括操作数本身,而不是地址。这样的操作数称之为立即数,立即数的缺点是只有常数才有这种方式,且立即数的大小受到地址字段的影响。

直接寻址

指定位于内存中的操作数的一种方法是给出它的完整地址,称之为直接寻址。使用直接寻址指令访问的永远是同一个内存地址。这个地址中的值会变化,但地址永远不会变化。因此,直接寻址只能用于访问全局变量。

寄存器寻址

与直接寻址概念类似,只不过定义的是寄存器的地址而不是内存的地址。在ARM这种加载/存储体系结构中,几乎都是这种寻址方式。

寄存器间接寻址

这种方式里,定义的操作数来自内存,但是对应的内存地址不像直接寻址那样直接写在指令中,指令给出的是寄存器的地址,这个寄存器存储了对应的内存地址信息,这个寄存器中存储的信息称之为指针。这样的好处是不需要在指令中定义完整的内存地址,而且在指令执行的时候,根据寄存器内容的不同,访问不同的内存字节。

使用寄存器间接寻址,就能很简单的实现一个数组1024个元素的累加了。
指令系统层

变址寻址

如果能利用一个寄存器和一个已知的偏移量来访问内存,称之为变址寻址。

  • 例如IJVM中局部变量的访问,是通过寄存器中的内存指针(LV)加上偏移量来进行。
  • 再例如以下功能:两个有1024个元素的数组,计算是否存在一个非零对。使用了变址寻址(寄存器R2与A地址这一常量相加的结果来用于访问内存)
    指令系统层

基址变址寻址

用两个寄存器和一个偏移量(可选)相加的结果来进行寻址。很好理解,一个寄存器存储的是偏移量,另一个寄存器中的信息是指针

栈寻址

设计人员希望把指令中的地址设计的足够短,极端情况干脆没有地址,那就是进栈/出栈操作。

这一节提到如何将中缀表达式转换成后缀(逆波兰)表达式,转换成后缀(逆波兰)表达式的优点是很容易把一条复杂数学表达式转换成代码实现。

例如计算(8+2 * 5)/(1+3 * 2-4)
指令系统层

转移指令的寻址方式

前面讨论的都是操作数据的指令。对于转移指令和过程调用指令,无需指出操作数的寻址方式,但是也需要寻址方式来指定目标地址。

前面讨论的大多数寻址方式都可以用于转移指令。例如直接寻址,直接把目标地址放在指令里面。

寄存器间接寻址允许程序运行时计算目标地址,然后放入寄存器,再跳转到目标地址处。这种方法足够灵活,但增大程序出错的可能性。

变址寻址利用一个寄存器和一个已知的偏移量来计算目标地址

还有一种是程序计数器寻址(就是变址寻址),把指令中带符号的偏移量加到程序计数器上来得到目标地址

五、指令类型

六、控制流