本系列文章为408考研计算机组成原理知识点整理仅涉及到一些重要的考研知识点并不是完全系统全面的知识,参考书目和视频资料:唐朔飞 计算机组成原理(第二版),袁春风 计算机组成与系统结构(第二版)王道考研计算机组成原理,B站王道考研计算机组成原理视频课。
4.1 指令格式
4.1.1 指令、指令格式
指令、指令格式
指令(机器指令):指示计算机执行某种操作的命令,是计算机运行的最小功能单位
指令集(指令系统):一台计算机的所有指令的集合
注意:一台计算机只能执行自己指令系统中的指令,不能执行其他系统的指令。
指令通常包括操作码字段和地址码字段两个部分
- 操作码:指明了该指令是一个什么类型的指令,也即它具有怎样的功能。它是识别指令,了解指令功能及操作数地址的关键信息。比如:操作码可以指出该操作是“算数加”还是“算数减”运算,是“程序转移”还是“返回操作”。
- 地址码:给出了被操作的信息(指令或数据)的地址。比如:“参与运算的一个和多个操作数所在的地址、运算结果的保存地址、程序的转移地址、被调用的子程序的入口地址"。
指令字长、机器字长和存储字长
- 指令字长:一条指令的总长度(可能会变),也即一条指令中所包含的二进制代码的位数,它取决于操作码的长度、操作数地址码的长度和操作数地址的个数
- 机器字长:CPU进行一次运算所能处理的二进制数据的位数(通常和ALU直接相关)
- 存储字长:一个存储单元中的二进制代码位数(通常和MDR位数相同)
注意:指令字长与机器字长没有固定的关系,它可以等于机器字长,也可以大于或小于。通常,把指令字长等于机器字长的指令称为单字长指令,相应地还有半字长指令、双字长指令。指令字长会影响取指令所需时间,例如,当机器字长=存储字长=16it,则取一条双字长指令就需要两次访存操作。
定长指令字和变长指令字
- 定长指令字结构:在一个指令系统中,如果所有指令的长度都是相等的,则称为定长指令字结构。具有定长指令字结构的指令其执行速度快,控制简单。
- 变长指令字结构:各种指令的长度随指令功能而不同。由于主存一般是按字节编址的,所以指令字长多为字节的整数倍。
4.1.2 指令分类
零地址指令

零地址指令:它只给出了操作码OP,没有给出显式地址,有以下两种情况:
- 该指令本身就不需要操作数:比如空操作、停机、关中断等等
- 零地址的运算类指令它仅仅会用于堆栈计算机中:通常参与运算的两个操作隐含地从栈顶和次栈顶弹出,送到运算器进行运算,运算结果再隐含地压入堆栈。
一地址指令

一地址指令:一地址指令也有两种常见的形态,需要根据操作的含义确定究竟属于哪一种
- 只有目的操作数:根据地址A1读取操作数,进行OP操作后,结果存回原地址,也即OP(A1)->A1。比如常见的“加1、减1、求反、求补”等操作。
- 需要两个操作数,但其中一个操作数隐含在某个寄存器中,比如ACC:也即(ACC)OP(A1)一>ACC,其中A1指某个主存地址,(A1)表示地址中的内容。
二地址指令

二地址指令:二地址指令往往就是常见的算数和逻辑运算,它们需要使用两个操作数,也即目的操作数和。地址中会给出目的操作数和源操作数的地址,其中目的操作数地址还可用于保存本次运算的结果,也即(A1)OP(A2)->A1
三地址指令

三地址指令:相比二地址指令,三地址指令需要新的地址来存储运算结果,也即(A1)OP(A2)->A3
四地址指令

四地址指令:相比三地址指令,多了一个用于指明下一条指令地址的功能。这样就实现了指令的跳转功能(如果没有跳转,指令将会正常+1),也即(A1)OP(A2)->A3,A4=下一条指令的地址
定长操作码:指令系统中所有指令的操作码长度都相同,有n位就有2n条指令。定长操作码的控制器译码电路设计简单,但是灵活较差。
可变操作码:指令系统中各指令的操作码长度可变。可变长操作码控制器译码电路设计复杂,但灵活性很高。
4.1.3 扩展操作码
采用定长指令字变长操作码:即不同地址数的指令会具有不同长度的操作码
下面以一条16位的三地址指令为例进行扩展:OP为4位,A1 A2 A3均为4位。
扩展二地址指令

三地址指令操作码范围为0000~1111
将1111留作扩展码,也即1111开头的指令不再代表三地址指令,比时三地址指令变更为15条,操作码范围为0000~1110,于是,二地址指令操作码将会以1111开头。
实际上,CPU在取得一条指令时,一定是直接读入16位,所以只需要根据所读入的是否为1111即可判断它是三地址指令还是二地址指令。
扩展一地址指令

此时二地址指令操作码范围为11110000~11111111
将11111111留作扩展码,也即11111111开头的指令不再代表二地址指令,此时二地址指令变更为15条,操作码范围11110000~11111110,于是,一地址指令操作码将会以11111111开头。
扩展零地址指令

此时一地址指令操作码范围为111111110000~111111111111
将111111111111留作扩展码,也即111111111111开头的指令不再代表一地址指令,此时一地址指令变更为15条,操作码范围为111111110000~111111111110,于是零地址指令操作码将会以111111111111开头
由于零地址指令不需要再向后扩展,所以是16条,范围为1111111111110000~1111111111111111

总之,在整个扩展过程中,操作码的位数会随着地址码位数的减少而增加。相比于之前的16条三地址指令。经过扩展,仅损失了一个三地址指令,却增加了15条二地址指令、15条一地址指令和16条零地址指令。
上面展示的是扩展操作码比较常用的一种方法,还有很多种设计方案。不论使用哪种方法,在设计时一定要注意以下几点:
- 不允许短码是长码的前缀,也即短操作码不能与长操作码的前面部分相同:这一点,类似于哈夫曼树的前缀编码,比如0011和00110000,如果这样设计就会产生歧义
- 各指令操作码不能重复
通常情况下,对于使用频率较高的指令,分配较短的操作码;对使用频率较低的指令则分配较长的操作码,从而尽可能减少指令译码和分析的时间。
4.2 指令寻址方式
4.2.1 指令寻址和数据寻址
指令寻址
指令寻址:我们编写的程序最终会被翻译等价的机器指令,指令和数据无差别地存放在主存当中。CPU中有一个很重要的寄存器—程序计数器PC(Program-Counter),它指明了下一条指令的存放地址,PC在执行完一条指令后会让程序计数器自动“+1”。
指令寻址分为顺序寻址和跳跃寻址
顺序寻址:顺序寻址可以简单的理解为:(PC)+“1”->PC,但是这里的“1”要理解为一个指令字长,要视具体的指令长度、编址方式的不同而定。
只有在系统采用定长指令字结构,并且指令字长=存储字长=16bt=2B,且主存按字编址时,PC才能简单的+1。其余条件不变,如果主存按字节编址,即每一条指令会占两个地址,此时PC要+2
如果采用变长指令字结构同时按字节编址,此时不同指令的字长是不一样的。由于CPU无法确定当前指向的指令占多少存储字,此时CPU可以先读入一个字,操作码一定会包含在其中,因此可以通过操作码来判断这是一个几地址的指令,就可以确定这条指令具体占的字节数n,接着PC+n即可
跳跃寻址:所谓跳跃,是指下一条指令的地址码不再由程序计数器给出,而由本条指令给出下一条指令地址的计算方式。跳跃寻址通过转移类指令实现:
- 无条件转移:JMP
- 条件转移:(JZ:结果为0;JP:结果溢出;JC:结果有进位)
- 调用和返回:CALL及RETURN
- 陷阱(Trap)与陷阱指令
数据寻址
数据寻址:执行一条指令时,将地址码当作形式地址码,需要对其按照一定规则进行解释翻译,才能得到真实的地址。当按不同方式进行解释时,就会形成不同的数据寻址方式,我们将介绍十种数据寻址方式。
在下面的介绍中,假设指令字长=机器字长=存储字长,且操作数为3
隐含寻址(取指令一次访存)

隐含寻址:不是明显地给出操作数的地址,而是在指令中隐含着操作数的地址
优点:有利于缩短指令字长
缺点:需要增加存储操作数和隐含地址的硬件
立即寻址(取指令一次访存)

立即寻址:形式地址A就是操作数本身,又称为立即数,一般采用补码形式。它的寻址特征为#,通常用于给寄存器赋初值。
优点:指令执行阶段不需要访问主存,指令执行时间最短
缺点:A的位数限制了立即数的范围
直接寻址(取指令一次访存+执行指令一次访存)

直接寻址:指令字中的形式地址A就是操作数的真实地址,即EA=A
优点:简单,指令执行阶段仅访问一次主存,不需要专门计算操作数的地址
缺点:A的位数决定了该指令操作数的寻址范围,且操作数的地址不易修改
间接寻址(取指令一次访存+执行指令两次访存)

间接寻址:指令的地址字段给出的形式地址不是操作数的真正地址,而是操作数的有效地址所在存储单元的地址,也就是操作数地址的地址,即EA=(A)
优点:可以扩大寻址范围(有效地址EA的位数大于形式地址A的位数);便于编写程序(间接寻址方式可以很方便地完成子程序返回)
缺点::指令在执行阶段要多次访存(一次间接寻址需要两次访存,多次寻址需要根据存储字的最高位确定几次访存)
寄存器寻址(取指令一次访存)

寄存器寻址:在指令字中直接给出操作数所在的寄存器编号,即EA=Rs,所其操作数就在由Rs所指的寄存器内存放
优点:指令在执行阶段不问主存,只访问寄存器;指令字段且执行速度快,支持向量/矩阵运算
缺点:寄存器价格昂贵,个数有限
寄存器间接寻址(取指令一次访存+执行指令一次访存)

寄存器间接寻址:寄存器R中给出的不是一个操作数,而是操作数所在主存单元的地址
特点:扩大了寻址范围,与一般间接寻址方式相比速度更快,但指令的执行阶段需要访问主存。
相对寻址(取指令一次访存+执行指令一次访存)

相对寻址:把程序计数器PC的内容加上指令格式中的形式地址A而形成操作数的有效地址,也即EA=(PC)+A,其中A是相对于PC所指地址的偏移量,可正可负,使用补码表示
相对寻址的作用:相对寻址中,操作数地址不是固定不变的,可以随PC值的变化而变化,并且与指令地址之间总是相差一个固定值,因此便于程序的浮动(注意区分基址寻址的浮动,这里的浮动是指一段代码在程序内部的浮动),相对寻址广泛应用在转移指令中。
基址寻址(取指令一次访存+执行指令一次访存)
基址寻址:将CPU中基址寄存器BR中的内容加上指令格式中的形式地址A,从而形成操作数的有效地址,也即EA=(BR)+A
如下,采用基址寻址,指令中会包含一个形式地址A, BR会指向当前程序存放的起始位置

需要注意,有的计算机内部不会专门设计一个基址寄存器,而会使用通用寄存器代替基址寄存器。如果采用通用寄存器,除了要给明寻址特征外,指令中还要多出几位,用于说明要将哪个通用寄存器作为基址寄存器使用。

基址寻址作用:基址寻址利于程序浮动,程序存储位置可以更改,但指令内容不需要修改,只需要更改基址寄存器,让其始终指向程序的起始地址,这有利于多道程序并发运行;另外,采用基址寻址可以扩大寻址范围(因为基址寄存器的位数大于形式地址的位数)
变址寻址(取指令一次访存+执行指令一次访存)
变址寻址:有效地址EA等于形式地址A与变址寄存器IX中的内容相加之和,也即EA=(IX)+A。其中IX可以是专用的,也可以将通用寄存器用作变址寄存器。

可以看出变址寄存器和基址寄存器非常相像,那么他们的区别又在哪里呢?
基址寄存器是面向操作系统的,其内容由操作系统或管理程序确定,主要用于解决程序逻辑空间与存储器物理空间无关性。在程序执行过程中,基址寄存器内容不变(作为基地址)、形式地址可变(作为偏移量);另外,采用通用寄存器作为基址寄存器时,可由用户决定哪个寄存器作为基址寄存器,但是其内容仍然由操作系统确定
变址寄存器是面向用户的,在程序的执行过程中,变址寄存器中的内容可以由用户改变,其中IX作为偏移量,形式地址A不变,作为基地址,这一点和基址寄存器恰好相反
变址寻址作用:变址寻址特别适合编写循环程序。多用于处理数组问题
堆栈寻址

堆栈寻址:是指操作数存放在堆栈中,隐含使用堆栈指针(SP)作为操作数地址。其中,堆栈是存储器(或专用寄存器组)中一块特定的按“先进后出”原则管理的存储区,该存储区中被读写单元的地址由一个特定的寄存器给出的,也就是我们上面说到的堆栈指针(SP)。
硬堆栈:又称为寄存器堆栈,其成本较高,不适合做大容量的堆栈,速度快,执行不访存
软堆栈(最常用):是指从主存中划分一段区域来做堆栈,速度慢,执行访问内存一次

4.3 程序的机器级代码表示
4.3.1 x86指令结构
x86的汇编层表示:x86指令通常表示为助记符 目标地址,源地址
助记符:是人类可读的机器指令表示
源地址和目标地址:指令的操作数
如汇编指令mov rbx,rax 就是将寄存器rax的值赋给rbx
x86中操作数有三个来源:
- 寄存器
- 内存
- 操作数
x86寄存器操作数:x86下只需关注如下8个寄存器,每个寄存器以E开头,表示Extended,长度为32bit。分为三类。
以X结尾:通用寄存器
以I结尾:变址寄存器
以P结尾:堆栈寄存器

内存操作数:在x86中,可以用[base+index*scale+displacement]指定内存操作数
- base和index:是64位寄存器
- scale:1、2、4或8的整数值
- displacement(偏移):是32位常量或符号
例如mov eax,dword ptr[af966h]就表示将内存地址af996h所指的32bit值复制到寄存器eax中
- dword ptr:双字(32bit)
- word ptr:单字(16bit)
- byte ptr:字节(8bit)
立即数:立即数就是指令中硬编码的常量整数操作数,如指令add rax,42,其中42就是一个立即数
表示x86机器指令的语法格式主要有两种,其中Intel格式考试最为常见
- AT&T格式(Linux):显式地在每个寄存器名称的前面加上%符号,每个常量前面加上$符号;原操作数在目的操作数前面
- Intel格式(Windows): 相对简洁、不加符号;源操作数在目的操作数后面

4.3.2 常见算数运算指令

除法中s作除数,被除数会被提前放置到edx和eax当中
edx:eax:在进行除法运算之前,需要把被除数进行位扩展为64bit,所以需要两个寄存器
4.3.3 常见逻辑运算指令

4.3.4 数据传送类指令
- mov:将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器或内存)。但不能用于直接从内存复制到内存。
- push:将操作数压入内存的栈,常用于函数调用。ESP是栈顶,压栈前先将ESP值减4(栈增长方向与内存地址增长方向相反),然后将操作数压入ESP指示的地址。
- pop:与push指令相反,pop指令执行的是出栈工作,出栈前先将ESP指示的地址中的内容出栈,然后将ESP值加4。
4.3.5 控制流指令
指令指针寄存器IP(相当于ARM型CPU中的程序计数器PC):x86处理器维持着一个指示当前执行指令的指令指针P),当一条指令执行后,此指针自动指向下一条指令。IP寄存器不能直接操作,但可以用控制流指令更新。通常用标签(label)指示程序中的指令地址,在x86汇编代码中,可在任何指令前加入标签。
jmp:无条件转移指令,控制lP转移到label所指示的地址(从label中取出指令执行)
jcondition:条件转移指令,依据CPU状态字中的一系列条件状态转移。CPU状态字中包括指示最后一个算术运算结果是否为0,运算结果是否为负数等
cmp/test: cmp指令用于比较两个操作数的值,test指令对两个操作数进行逐位与运算,这两类指今都不保存操作结果,仅根据运算结果设置CPU状态字中的条件码
call/ret:无条件转移指令,分别用于实现子程序(过程、函数等)的调用及返回
对于无条件转移指令call/ret,其过程调用的执行步骤如下,假设P调用Q
- P将入口参数(实参)放在Q能访问到的地方
- P将返回地址存到特定的地方,然后将控制转移到Q(call)
- Q保存P的现场(通用寄存器的内容),并为自己的非静态局部变量分配空间
- 执行过程Q
- Q恢复P的现场,将返回结果放到P能访问到的地方,并释放局部变量所占空间
- Q取出返回地址,将控制转移到P(ret)
对于jmp指令,有如下四种使用方法
- jmp128:地址由常数给出
- jmp eax:地址可以来自于寄存器
- jmp[999]:地址可以来自于主存
- jmp NEXT:地址可以由“标号"给出,类似于C语言中的goto语句
jmp指令是很不灵活的,无法实现一些复杂条件转移操作,所以需要借助条件转移指令jcondition来完成,同时要借助cmp a , b (比较a和b的大小)
- je<地址>:若a=b则跳转(ZF==1)
- jne<地址>:若a!=b则跳转(ZF==0)
- jg<地址>:若a>b则跳转(ZF==0&&SF=OF)
- jge<地址>:若a>=b则跳转(SF==OF)
- jl<地址>:若a<b则跳转(SF!=OF)
- jle<地址>:若a<=b则跳转(SF!=OF||ZF==1)
4.4 CISC和RISC的基本概念
指令系统的设计如今朝着两个截然不同的方向发展:
一是增强原有指令的功能,设置为更复杂的新指令实现软件功能的硬化,这类机器称为复杂指令系统计算机(CISC),典型的如×86架构的计算机;
二是减少指令种类和简化指令功能,提高指令的执行速度,这类机器称为精简指令系统计算机(RISC),典型的有ARM,MIPS架构的计算机。
CISC和RISC的对比

Comments NOTHING