计算机程序执行任务是由一些小的执行步骤序列构成的,如两个数相加,判断条件等。计算机需要有执行四类操作指令的能力

  • 在存储器和处理器寄存器之间的数据传送
  • 数据的算数和逻辑运算
  • 程序序列和控制执行
  • 输入/输出传送

讨论这些指令之前,需要了解一下便于后续讨论的标记符号

寄存器传送标记

要对信息计算机从A单元传送到B单元进行描述。
可能涉及到的单元有存储器单元,处理器寄存器,I/O系统的寄存器。
大多数情况下,用一个表示它的硬件二进制地址的符号名来识别其位置。如存储单元的地址名可能是LOC,PLACE,A,VAR2;处理器寄存器的地址名可能是R0,R5;而I/O寄存器名可能是DATAIN,OUTSTATUS
单元中的内容用地址名+方括号的形式表示
[LOC]
R3←[R0]+[R2]表示,将R0与R2的内容相加之后,传送到R3中去
这种标记方式称为寄存器传送标记(RTN) :::warning 注意:RTN右边总是表示值,左边是存放该值的存储单元,结果是用右边的值覆盖左边原有的值 :::

汇编语言符号

寄存器传送标记中的R1←[LOC]
用汇编语言表示为Move LOC,R1
而寄存器传送标记中的R3←[R0]+[R2]
用汇编语言表示为Add R0,R2,R3

基本指令类型

对于一个高级程序语言的指令
C=A+B
其含义是对变量A,B当前值做加法,并将结果放到变量C中,当该语句被编译时,A,B,C三个变量分配到内存的不同单元中。我们利用这些变量名去引用相应的内存单元地址。
C←[A]+[B],为执行这个动作,A,B中的内容会从存储器中取出并传入处理器中,在CPU中进行求和,结果传送回存储器并放入C中覆盖其原有内容。
首先我们假设这个动作由单个机器指令完成,这条指令包含着三个操作数A,B,C的存储地址。
Add A,B,C表示将A,B的值相加,结果存入C
假设一个操作数的地址用k位进行表示,操作用m位表示,则表示这样一条指令至少需要3k+m位。对于一个字长为32位的处理器,想用一个字存储这条指令是有困难的。
因此需要有一种允许在多个字共同存储一个指令的格式来表示这种指令。
一个可选择的方法是,使用将源指令拆分为多个指令,分步执行。
即不用三地址指令,转而使用二地址指令
Add A,B 表示将A的数累加到B上并覆盖原有的值。
虽然能够得到两数相加的结果,但是覆盖了B原有的值。这个问题可以使用另外一个指令来解决。
Move B,C,表示将B处的值复制到C处。(也许使用Copy更符合这个符号的意义,不过这是因为历史原因达成共识,保留Move了下来)。
因此源指令可以用下面两个指令完成。
Move B,C
Add A,C :::warning 上面的指令有一个共性就是,源操作数在前,目的操作数在后,But,实际上,不同的处理器实现中,制造商对源操作数与目的操作数的相对位置并未达成共识。不同的处理器,其顺序可能不同。甚至是同一个处理器,其顺序也可能不同。 ::: 在定义三地址和二地址指令后,也必须考虑二地址指令可能也无法正确的放如一个字中。因此还有一地址指令。
Add ALoad AStore A
这类地址只指明了一个操作数,如Add A,显然是不符合指令的语义的,当需要第二个操作数的时候,会认为第二个操作数默认在处理器寄存器的一个单元中,即累加器,因此
Add A表示将存储单元A中的内容加到累加寄存器中,其和覆盖累加寄存器原值。
Load A表示将存储单元A中的值,加载(复制)到累计寄存器中。
Store A表示将累加寄存器的值,存储(复制)到存储单元A中,覆盖A原值。 :::warning 早期计算机是围绕一个单独的累加寄存器设计的。但是现代处理器通常会有多个通用处理器寄存器。对这些单元的访问要比内存中的单元访问快许多。因为处理器寄存器的数量少,需要用来表示位数的地址也相对少,如对于32个寄存器,只需要5位表示地址。比内存单元的编址少许多。这些寄存器通常用于存放处理器处理过程中的临时数据。 ::: 寄存器通常用R_i表示
因此引入寄存器之后,有
Add A,R_i
Load A,R_j
Store R_k,B
指令操作数可以同时操作两个处理器寄存器。这种不涉及内存访问的指令,执行速度要远快于进行内存访问的指令。
编译器在进行高级语言与低级机器语言的转换时候,需要十分重视,即编译器优化。 :::info 上面讨论了三地址,二地址,一地址指令。实际上,还有零地址指令,所有地址都是隐含的指令,在下推栈结构的机器中可以找到。 :::

指令执行与线性序列

假设,一个字内可以存储一条指令,内存按字节寻址。
处理器中几个单元

  • PC(程序计数器)中存储下一步执行指令的地址
  • IR(指令寄存器)中存储当前将要执行指令的内容

Move A,R0
Add B,R0
Move R0,C
则对于上述将A,B值的和存储到C的第一条指令执行步骤如下
第一阶段:取指令
PC指向内存地址i,对应指令MoveA,R0,IR获取并存储指令Move A,R0
第二阶段:执行
去操作对IR中的指令检查并确定将要执行的操作,随后执行,这个阶段可继续细分

  • 取操作数,从内存单元A中取操作数(也可是处理器寄存器)
  • 运算,执行算数或逻辑运算(此处无运算)
  • 结果存储,将结果存入目标单元R0中

在上述两个过程的某个时间点,PC内容会递增,指向下一个指令Add B,R0,两个阶段执行完毕,而PC也包含下一指令地址,开始执行下一条指令。

转移

上面讨论的是按顺序执行的,总是按顺序执行肯定是行不通的,还有一些指令用来跳转。
考虑对一个有n个数的列表相加的任务,其每个数的地址使用NUM1,NUM2,…,NUMn表示
对于该任务,在内存中要完成这个任务,其指令序列可以是

  1. Move NUM1,R0
  2. Add NUM2,R0
  3. Add NUM3,R0
  4. ...
  5. Add NUMn,R0
  6. Move R0,SUM

这种暴力的方案,n位长的数组就需要n+2条指令执行,太占内存了
显然需要一种更经济的方案,循环,将临时的SUM和放入一个额外的寄存器。

指令Decrement R1用于对寄存器R1-1
指令Branch>0 LOOP,是一个条件转移指令,表面当前一条指令执行结果>0时,会产生一个转去执行LOOP单元指令的转移指令
则对于上述求和任务,可使用如下指令来完成。

  1. Move N,R1
  2. Clear R0
  3. ------LOOP单元起始处,循环开始------
  4. 确定下个数地址
  5. 将下个数加到R0
  6. Decrement R1
  7. Branch>0 LOOP
  8. ------循环结束------
  9. Move R0,SUM

条件码

上面提到的条件转移指令的使用,实现中很重要的一点就是,条件判断,条件判断需要信息,因此处理器在执行过程中,要保存有关各种操作执行结果的过程信息。保存就需要存储单元,这些单元称为条件码标志位,这些标志通常会被聚集在一起形成一个特殊处理器单元称为条件码寄存器
每个单独的条件码标志可以根据操作执行结果置为1或0
常用标志如下:
N(负数):如果是负数,则置为1,否则为0
Z(零):如果是零,则置为1,否则为0
V(溢出):如果运算溢出,则置为1,否则为0
C(进位):如果运算结果有一个进位输出,则置为1,否则为0

生成地址

讨论回上面的LOOP单元,当满足Branch>0时,产生一个转移指令,回到LOOP头之后,就需要下一个数的地址,But,如何确定下一个的地址。
一个可能的处理,循环开始前,增加一个寄存器R,R存放第一个数的地址,在之后的循环中每次递增4,则得到下一个数的地址 :::info 在这种及其类似情况下,更多的是根据需要用灵活的方式去指明一个操作数的地址。CPU指令集通常会提供多种解决方案,称为寻址方式。 :::