文章目录
- 第二章 指令:计算机的语言
-
- 2.1 引言
- 2.2 计算机硬件的操作
- 2.3 计算机硬件的操作数
-
- 2.3.1 存储器操作数
- 2.3.2 常数或立即操作数
- 2.4 有符 数与无符 数
- 2.5 计算机中指令的表示
- 2.6 逻辑操作
- 2.7 决策指令
-
- 2.7.1 循环
- 2.7.2 边界检查的简便方法
- 2.7.3 case/switch语句
- 2.8 计算机硬件对过程的支持
-
- 2.8.1 使用更多的寄存器
- 2.8.2 嵌套过程
- 2.8.3 在栈中为新数据分配空间
- 2.8.4 在堆中为新数据分配空间
- 2.9 人机交互
- 2.10 对大立即数的RISC-V编址和寻址
-
- 2.10.1 大立即数
- 2.10.2 分支中的寻址
- 2.10.3 RISC-V寻址模式总结
- 2.10.4 机器语言编码
- 2.11 指令与并行性:同步
- 2.12 翻译并启动程序
-
- 2.12.1 编译器
- 2.12.2 汇编器
- 2.12.3 链接器
- 2.12.4 加载器
- 2.12.5 动态链接库
- 2.12.6 祝你考试高分通过 加油!!
第二章 指令:计算机的语言
2.1 引言
-
指令系统:被一个给定体系结构所理解的命令词汇表
-
存储程序概念:指令与多种类型的数据不加区别地存储在存储器中并因此易于更改,因此产生了存储程序计算机
-
我们采用RISC-V技术的指令集。使用自顶向下,循序渐进的方法并结合各部件及其说明。
-
如下表,为指令集的总体情况。
2.2 计算机硬件的操作
-
RISC-V汇编语言的符
-
add a , b , c
-
指示计算机将两个变量b和c相加并将其总和放入a中
-
这种符 表示是固定的,其中每个RISC-V算术指令只执行一个操作,并且必须总是只有三个变量
2.3 计算机硬件的操作数
-
与高级语言程序不同,算术指令的操作数会受到限制;它们必须取自寄存器,而寄存器数量有限并内建于硬件的特殊位置。寄存器是硬件设计中的基本元素
-
双字:计算机中一种访问基本单位,通常是64位一组;对应于RISC-V体系结构中寄存器的大少。
-
字:计算机中另一种访问基本单位。 通常是32位一组。
-
硬件设计的三条基本原则:
- 简单源于规整
- 更少则更快
- 优秀的设计需要适宜的折中方案
-
尽管我们可以简单地使用寄存器编 0到31来编写指令,但是RISC-V约定在“x”后面跟一个寄存器编 来表示
2.3.1 存储器操作数
-
回顾一下计算机的五个组成部分。处理器只能在寄存器中保存少量数据,但计算机内存可以存储数十亿数据元素。因此,数据结构(数组和结构体)可以保存在内存中。
-
如上所述,RISC-V指令中的算术运算只作用于寄存器,因此,RISC-V必须包含在内存和寄存器之间传输数据的指分。这些指令称为数据传输指令。
-
数据传输指令:在内存和寄存器之间传送数据的命令。
-
地址:用于描述内存数组中特定数据元素位置的值。
-
例如,在图2-2中. 第三个数据元素的地址是2,内存第2 单元存放的数据是10。
-
如图:实际的 RISC-V内存地址和这些内存中双字的内容。为了与图2-2对照,改变了的地址用灰色标出。由于RISC-V按字节寻址,因此双字地址是8的倍数:双字包含8 个字节
-
计算机分为两种,一种使用最左边或“大端”字节的地址作为双字地址,另一种使用最右端或“小端”字节的地址作为双字地址。RISC-V属于后者,称为小端编址。由于仅在以双字形式和八个单独字节访问相同数据时,字节顺序才会有影响,因此大多数情况下不需要关心“大小端”。
-
字节寻址也会影响数组下标。为了在上面的代码中获得正确的字节地址,加到基址寄存器x22的偏移量必须是8X8或64,以便取地址将选择A[8]而不是A[8/8]。(参见2.19节对相关陷阱的介绍。)
-
与载入指令相反的指令通常被称为存储指令(store), 它从寄存器复制数据到内存。存储指令的格式类似于载入指令的格式:操作名称,接着是要写回内存的寄存器,然后是基址寄存器,最后是选择数组元素的偏移量。同样,RISC-V地址是由常数和基址寄存器内容共同决定的。实际上的RISC-V指令名称是sd,表示存储双字。
-
对齐限制:数据在内存中要与自然边界对齐的要求
-
加载双字和存储双字是在RISC-V体系结构中存储器和寄存器之间传输双字的指令。某些品牌的计算机使用其他的载入和存储指令来传输数据。采用这种替代方案的一种体系结构是2.17节中描述的Intel x86
-
内存一定比寄存器慢,因为寄存器数量更少。
-
寄存器中的数据更容易得到利用,RISC-V算术运算指令能够完成读两个寄存器、对它们进行运算以及回写运算结果的操作。而一条RISC-V 数据传送指令只能完成读一个操作数或者写一个操作数的操作,并且不能对他们进行运算。
-
寄存器与内存相比,访问时间短,吞吐率高,这使得寄存器的数据访问速度快并且易于利用,访问寄存器相对于访问存储器功耗更小。因此,为了获得高性能和节约功耗,指令集的体系结构必须拥有足够的寄存器,并且编译器必须高效地利用这些寄存器。
2.3.2 常数或立即操作数
-
只使用目前介绍过的指令,我们需要将常数从内存中取出才能使用。(这些常数会在程序加载的时候存放到内存中。)
-
假设x3+AddConstant4是常数4的内存地址
避免使用加载指令的一种方法是提供另一个版本的算术指令,**它的其中一个操作数是常数。这种带有一个常数操作数的快速加指令称为立即数加或addi。**要将4加到寄存器x22,只需写成: -
常数操作数经常出现;的确,addi是大多数RISC-V程序中最常用的指令。通常把常数作为算术指令操作数,和从存储器取出常数相比,操作速度 更快,能耗更低。、
-
自我检测鉴于寄 存器的重要性,芯片中的寄存器数量随时间变化的增长率是下面哪个/p>
- 非常快:和摩尔定律一样快,摩尔定律预测每18个月芯片上的晶体管数量增长1倍。
- 非常慢:由于程序通常以计算机语言实现,并且指令系统体系结构存在惯性,因此寄存器数量的增长速度与新指令系统在体系结构中的可行性保持一致。
2.4 有符 数与无符 数
-
首先回顾一下,计算机是如何存储数字的。在生活中,我们大多使用十进制,但是数的进制是任意的。在计算机硬件中,数是以一串或高或低的电信 来体现的,因此可以被认为基为 2 的数。
-
二进制数位(binary digit):又称为位,以2为基数表示,或0或1,是信息的基本组成单位。
-
无符 数
- 由于字是在水平或者竖直方向上书写的,用最左边或者最右边表示大小带有不确定性。最低有效位(least significant bit) 表示最右边一位(比如上图中第 0 位,对应数据 1),**最高有效位( most significant bit)**表示最左边一位(上图中的第 63 位,对应数据 0 )。
-
溢出(overflow):
- 正如对无符 数的操作结果可能超出硬件容量而产生溢出一样,对二进制补码的操作也是如此。
- 当二进制位模式下最左边的保留位与左边的无限数位不相同时(即符 位不正确),溢出发生;当数为负数时最左侧为0,或当数为正数时最左侧为1。
-
**RISC-V确实提供了两种字节载入方式:无符 字节载入(lbu)将字节视为无符 数,因此用零扩展填充寄存器的最左位,而字节载入(lb)使用带符 整数。**由于 C 语言程序几乎都是使用字节来表示字符,而不是将字节视为有符 短整数,所以实际中 lbu 专门用于字节加载.
-
处理二进制补码数的两种有用的快捷方法:
-
第一种是对二进制补码求相反数的快速方法。简单地把每个0都转为1以及每个1都转为0,然后对结果加1。这个捷径是基于以下观察:
-
第二种方式是将一个用n位表示的二进制数转换为一个用多于n位表示的数。先去位数更少的数的最高位(符 位),并将其复制来填充位数更多的数的新位。原来的非符 位被复制到新双字的右侧部分。这个方式通常被称为符 扩展(sign extension)
-
以下是RISC-V指令中每个字段名称的含义:
- opcode(操作码):指令的基本操作,这个缩写是它的惯用名称
- rd:目的操作数寄存器,用来存放操作结果
- funct3:一个另外的操作码字段
- rs1:第一个源操作数寄存器
- rs2:第二个源操作数寄存器
- funct7:一个另外的操作码字段
-
问题:当某条指令需要比上述字段更长的字段时,问题就会发生。例如,取字指令必须指定两个寄存器和一个常数,在上述各始终,如果地址使用其中的一个 5 位字段,那么取字指令的最大常数将被限制为31或2^5-1。而这个常数通常用来从数组或者数据结构中选择元素,所以它常常比 31 大的多。因此, 5 位字段因太小而用途不大。
-
因此,所有指令长度相同和统一的指令格式二者之间产生了矛盾。
-
我们回头看硬件设计原则:优秀的设计需要适宜的折中方案。
-
自我检测
-
移位(shift):这个操作将一个字里面的所有位向左或向右移动,并在空出来的位置上添加零。
-
按位与(AND):当两个操作数都为 1 时,经过AND 运算后才为 1.
例如,0000 0000 0000 0000 0000 1101 1100 00000000 0000 0000 0000 0000 1101 1100 0000 和 0000 0000 0000 0000 0011 1100 0000 0000 0000 0000 0000 0000 0011 1100 0000 0000 得到的值是: 0000 0000 0000 0000 0000 1100 0000 00000000 0000 0000 0000 0000 1100 0000 0000
AND 操作提供了一种将源操作数置零的能力。 -
按位或(OR):当两个操作数其中有一个为 1 时,经过 OR 运算变成 1.
-
按位取反(NOT):对于这个操作,它将位上的 0 变成 1,1 变成 0.
-
或非(NOR):按位先或后非操作,仅当两个操作位均为 0 时结果才为 1.
-
异或(XOR):当两个操作数对应位置不同时为1,相同时为 0.
-
RISC-V也提供了 立即数与(andi)、立即数或(ori)和立即数异或(xori)
-
2.7 决策指令
-
条件分支(copnditional branch):该指令先比较两个值,然后根据比较的结果决定是否从程序中的一个新地址开始执行指令序列。
RISC-V中两条类似与if和go to语句功能的指令。
- ,该指令表示,如果rs1 和 rs2 中的数值相等,那么转到标签为 L1 的语句中执行。beq代表的是如果相等则分支(branch if equeal).
- ,该指令表示,如果rs1 和 rs2 中的数值不相等,那么转到标签为 L1 的语句中执行。bnq代表的是如果不相等则分支(branch if not equeal).
2.7.1 循环
-
**基本块:没有分支(可能出现在末尾者除外)并且没有分支目标/分支标签(可能出现在开始者除外)的指令序列。
-
小于则分支指令:比较寄存器rs1和rs2中的值(采用二进制补码表示),如果rs1中的值较小则跳转
-
大于等于分支指令:是相反的情况,也就是说如果rs1中的值至少不少于rs2中的值则跳转
-
如果二者是无符 数,无符 数的小于则分支指令那么rs1中的值小于rs2中的值则跳转。最后,无符 数的大于等于则分支指令在相反的情况下跳转。
2.7.2 边界检查的简便方法
-
将有符 数当作无符 数处理,给我们提供了一种低成本的方式检查是否0≤x<y,常用于检测数组下标是否越界。关键在于二进制补码表示中的负整数看起来像无符 表示中很大的数;因为最高有效位在有符 数中表示符 位,但在无符 数中表示数的很大一部分。因此,无符 比较x<y在检测x是否小于y的同时,也检测了x是否为负数。
2.7.3 case/switch语句
- 分支地址表:也称作分支表,一种包含了不同指令序列地址的表
- 程序只需要索引到表中,然后跳转到合适的指令序列。因此,分支表只是一个双字数组,其中包含与代码中的标签对应的地址。该程序将分支表中的相应条目加载到寄存器中,然后需要使用寄存器中的地址进行跳转。为了支持这种情况,RISC-V这类指令系统包含一个间接跳转指令,该指令对寄存器中指定的地址执行无条件跳转。在RISC-V中,跳转-链接指令(jar)用于此目的。我们将在下一节中看到这种多功能指令更多常见的使用方式。
- 虽然在 C 或者 Java 这样的编程语言中有许多决策和循环语句,但是在指令集这一个层次上实现其功能的基本语句是条件分支。
2.8 计算机硬件对过程的支持
-
过程(procedure):根据提供的参数执行一定任务的存储的子程序。它或者函数是程序员进行结构化编程的工具,二者均有助于提高程序的理解性和代码的可重用性。过程允许程序员每次只需将精力集中在任务的一部分,由于参数能传递数值并返回结果,因此参数承担过程与其他程序、数据之间接口的角色。过程是软件中实现抽象的一种方法。
-
在过程运行中,程序必须遵守以下 6 个步骤:
- 将参数放在过程可以访问的位置。
- 将控制转交给过程。
- 获得过程所需的存储资源。
- 执行需要的任务。
- 将结果的值放在调用程序可以访问的位置。
- 将控制返回初始点,因为一个过程可能由一个程序中的多个点调用。
-
由于寄存器是计算机中保存数据最快的位置,所以我们希望尽可能多的使用寄存器。RISC-V软件在为过程调用分配 寄存器时遵循以下约定:
- x10 ~ x17 : 八个参数寄存器,用于传递参数或返回值
- x1 : 一个返回地址寄存器,用于返回到起点
除了分配这些寄存器之外, RISC-V汇编语言还包括一条过程调用指令:跳转到某一个地址的同时将下一个指令的地址保存在寄存器rd中。这便是跳转和链接指令(jal)。
写作: jal x1 , ProduceAddress
-
返回地址(return address):指向调用点的链接,允许过程返回到合适的地址;在RISC-V中被存储在寄存器x1中
-
调用者(caller):启动过程并提供必要参数值的程序
-
被调用者(callee):根据调用者提供的参数执行一系列存储的指令,然后将控制权返回调用者的过程。
-
程序计数器(program counter,PC):PC 中包含在程序中正在被执行指令地址的寄存器。
2.8.1 使用更多的寄存器
-
栈(stack):被组织成后进先出队列形式并用于寄存器换出的数据结构。
栈需要一个指针,以指向栈中最新分配的地址,以只是下一个过程放置换出寄存器的位置,或者是寄存器旧值的存放位置。 -
栈指针(stack pointer):指示栈中最新分配得地址的值,它指示寄存器被换出的位置,或寄存器旧值得存放位置。在RISC-V中为寄存器sp或x2
栈指针是以双字为单位进行调整。 - 两个最基本的操作:
- 压栈(push):向栈中增加元素。
- 出栈(pop):从栈中移出元素。
- 按照管理,栈“增长”是按照地址由高到低的顺序进行的。这意味着,数据压栈时,栈指针减小;数据出栈时,栈长度缩短,栈指针增大。
2.8.2 嵌套过程
-
叶子过程(leaf procedure):不调用其他过程的过程。
如果所有过程都是叶过程,那么情况就很简单,但是实际并么如此
例如,假设主程序调用过程A,参数为3,将值3存人寄存器x10然后使用jal x1,
A。再假设过程A通过ja1 x1, B调用过程B,参数为7,也存人x10。由于A尚未结束任务,所以寄存器x10的使用存在冲突。同样在寄存器x1中的返回地址也存在冲突,因为它现在具有B的返回地址。除非采取措施阻止这类问题发生,否则该冲突将导致过程A无法返回其调用者。一种解决方法 是将其他所有必须保存的寄存器压栈,就像保存寄存器压栈- -样。 调用者将所有调用后还需要的参数寄存器(x10 ~ x17)或临时寄存器(x5~ x7和x28~ x31)压栈。被调用者将返回地址寄存器x1和被调用者使用的保存寄存器(x8 ~ x9)和x18~ x27)压栈。调整栈指针sp以计算压栈寄存器的数量。返回时,从存储器中恢复寄存器并重新调整栈指针。
-
全局指针(global pointer):指向静态数据区的保留寄存器。
C语言中的一个变量通常对应存储中的一个位置,其解释取决于其类型(type)和存储方式(storage class)。例如整型和字符型。C语言包括两种存储方式:动态的(automatic)和静态的(static)。动态变量位于过程中,退出过程时失效。静态变量在进入和退出过程时始终存在。在所有过程之外声明的 C 变量,以及声明时候使用的关键字 static 的变量都被视作静态的,其余的变量都被视作动态的。为了简化静态数据的访问,RISC-V软件保留了一个寄存器x3,这便是全局指针。
-
帧指针(frame pointer):指向给定过程中保存的寄存器和局部变量的值。
2.8.4 在堆中为新数据分配空间
-
代码段:UNIX目标文件的段,包含源文件中例程的机器语言代码
-
除了动态变量对于过程是局部有效之外,C程序员还需要再内存中为静态变量和动态数据结构提供空间,如下图,给出了RISC-V分配内存的约定:
栈由内存高端开始向下增长。内存低端的第一部分是保留的。之后是RISC-V机器代码的第一部分,通常称为代码段(text segment)。代码段之上的代码为静态数据段(static data segment),是存储常量和其他静态变量的空间。尽管数组通常具有固定长度因而能和静态数据段很好的匹配,但是类似链表这样的数据结构通常会再生命期内增加或者缩短。这类数据结构对应的段习惯上称为堆(heap),一般再存储器中放在静态数据段之后,注意,这种分配允许栈和堆相互增长,从而再两个段此消彼长的过程中达到内存的高效使用。
-
C语言通过显式的函数调用再堆上分配ta和释放空间。malloc() 再堆上分配空间并指向它的指针,free() 释放指针指向的堆空间。内存分配由 C 程序控制,这是很多错误产生的根源。忘记释放空间会导致“内存泄漏”,它会逐渐耗尽大量内存以至于操作系统可能崩溃。过早释放空间会导致“悬摆指针”(dangling pointer),这会造成指针指向程序不想访问的位置。在Java中使用自动的内存分配和无用单元回收机制来放置类似的错误发生。
-
一些递归过程可以使用迭代的方式来实现。通过消除过程调用的相关开销,迭代可以显著提升性能。
-
RISC-V指令系统具有加载和存储这种16位半字的指令。load half unsigned (加载无符 半字)从内存中读取一个半字,将它放在寄存器的最右边16位,用零填充最左边的48位。与加载字节一样,加载半字(lh)将半字视为有符 数.因此进行符 扩展以填充寄存器的最左边48位。存储半字(sh)从寄存器的最右边16位取半字并将其写入内存。
-
RISC-V 还包括将32位值移入和移出存储器的指令。加载无符 字(lwu)将32位字从存储器加载到寄存器的最右边32位,用零填充最左边的32位。加载字(lw) 用第31位的值填充最左边的32位。存储字(sw)从寄存器的最右边32位取一个字并将其存储到存储器中。
2.9 人机交互
- 你的书上没有 哈哈哈哈哈
2.10 对大立即数的RISC-V编址和寻址
2.10.1 大立即
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!