408计算机组成原理知识点——第四章 指令系统


注:内容参考王道2024考研复习指导

指令系统

指令集体系结构

机器指令(简称指令)是指示计算机执行某种操作的命令。一台计算机的所有指令的集合构成该机的指令系统,也称指令集。指令系统是指令集体系结构(ISA)中最核心的部分,ISA完整定义了软件和硬件之间的接口。

ISA规定的内容主要包括:
1)指令格式,指令寻址方式,操作类型,以及每种操作对应的操作数的相应规定。
2)操作数的类型,操作数寻址方式,以及是按大端方式还是按小端方式存放。
3)程序可访问的寄存器编号、个数和位数,存储空间的大小和编址方式。
4)指令执行过程的控制方式等,包括程序计数器、条件码定义等。

指令格式

指令

又称机器指令,是指示计算机执行某种操作的命令,是计算机运行的最小功能单位。

一台计算机的所有指令的集合构成该机的指令系统,也称为指令集。

指令格式

一条指令就是机器语言的一个语句,是一组有意义的二进制代码。

一条指令通常包括操作码和地址码字段两部分。

image-20240405204920753

指令-按地址码数目分类

image-20240405205031603

指令-按指令长度分类

定长指令字结构:指令系统中所有指令的长度都相等。

变长指令字结构:指令系统中各种指令的长度不等。

关于字不同字长的知识

指令字长:一条指令的总长度,一般取字节的整数倍(可能会变)。

机器字长:CPU进行一次整数运算所能处理的二进制数据的位数(通常和ALU直接相关)。

存储字长:一个存储单元中的二进制代码位数(通常和ALU直接相关)。

根据指令长度是机器字长的多少倍,可以分为半字长指令、单字长指令、双字长指令。

:指令字长会影响取指令所需时间。如:机器字长=存储字长=16bit,则取一条双字长指令需要两次访存。

指令-按操作码长度分类

定长操作码:指令系统中所有指令的操作码长度都相同。

可变长操作码:指令系统中各指令的操作码长度可变。

定长指令字结构+可变长操作码 → \rightarrow 扩展操作码指令格式(不同地址数的指令使用不同长度的操作码)。

指令-按操作类型分类

image-20240405205714445

扩展操作码

指令字长为16位,每个地址码占4位:前4位为基本操作码字段OP,另有3个4位长的地址字段A1、A2和A3。

image-20240405210035899

4位基本操作码若全部用于三地址指令,则有16条。但至少须将1111留作扩展操作码之用,即三地址指令为15条。

image-20240405210234809

1111 1111留作扩展操作码之用,二地址指令为15条;

image-20240405210302204

1111 1111 1111留作扩展操作码之用,一地址指令为15条;零地址指令为16条。

在设计扩展操作码指令格式时,必须注意以下两点:

  1. 不允许短码是长码的前缀,即短操作码不能与长操作码的前
    面部分的代码相同。
  2. 各指令的操作码一定不能重复。

对使用频率较高的指令,分配较短的操作码;对使用频率较低的指令,分配较长的操作码,从而尽可能减少指令译码和分析的时间。

举例:

image-20240405210533486

说明:地址长度为n,上一层留出m种状态,下一层可扩展出 m ∗ 2 n m*2^n m2n种状态。

指令的寻址方式

寻址范围和寻址空间

寻址空间一般指的是CPU对于内存寻址的能力。通俗地说,就是能最多用到多少内存的一个问题,即能够寻址的最大容量。

寻址范围仅仅是一个数字范围,不带有单位,而寻址范围的大小很明显是一个数,指寻址区间的大小。

:M为数量单位。1024=1K,1024K=1M。MB指容量大小。1024B=1KB,1024KB=1MB。 如寻址范围为1M,寻址空间为1MB。

指令寻址

指令寻址,下一条欲执行指令的地址: ( P C ) + 1 → P C (PC)+1 \rightarrow PC (PC)+1PC(始终由程序计数器PC给出)。

:每次取指令之后,PC一定会自动+1,指向下一条应该执行的指令(即当前取完指令1,未执行,PC已经指向指令2)。

现假设该系统采用定长指令字结构,且指令字长=存储字长=16bit=2B,若主存按字编址则PC=PC+1;若主存按字节编址则PC=PC+2。

再该系统采用变长指令字结构,主存按字节编址,读入一个字,根据操作码判断这条指令的总字节数n,则PC=PC+n

顺序寻址

( P C ) + 1 → P C (PC) + 1 \rightarrow PC (PC)+1PC,此处的“1”理解为一个指令字长,实际值会因指令长度、编址方式而不同。

跳跃寻址

由转移指令给出。

跳跃的方式分为绝对转移(地址码直接指出转移目标地址)和相对转移(地址码指出转移目的地址相对于当前PC值的偏移量),由于CPU总是根据PC的内容去主存取指令的,因此转移指令执行的结果是修改PC值,下一条指令仍然通过PC给出。

数据寻址

数据寻址,确定本条指令的地址码指明的真实地址。在指令字中设置一个寻址特征字段,用来指明属于哪种寻址方式(其位数决定了寻址方式的种类)。

image-20240405212242905

直接寻址

指令字中的形式地址A就是操作数的真实地址EA,即EA=A

image-20240405212320516

一条指令的执行:取指令访存1次,执行指令访存1次,暂不考虑存结果,共访存2次。

优点:简单,指令执行阶段仅访问一次主存,不需专门计算操作数的地址。

缺点:A的位数决定了该指令操作数的寻址范围,操作数的地址不易修改。

间接寻址

指令的地址字段给出的形式地址不是操作数的真正地址,而是操作数有效地址所在的存储单元的地址,也就是操作数地址的地址,即EA=(A)

image-20240405212602766

优点:可扩大寻址范围(有效地址EA的位数大于形式地址A的位数),便于编制程序(用间接寻址可以方便地完成子程序返回)。

缺点:指令在执行阶段要多次访存(一次间址需两次访存,多次寻址需根据存储字的最高位确定几次访存)。

寄存器寻址

在指令字中直接给出操作数所在的寄存器编号,即EA =Ri,其操作数在由Ri所指的寄存器内。

image-20240405212715741

一条指令的执行:取指令访存1次,执行指令访存0次,暂不考虑存结果,共访存1次。

优点:指令在执行阶段不访问主存,只访问寄存器,指令字短且执行速度快,支持向量/矩阵运算。

缺点:寄存器价格昂贵,计算机中寄存器个数有限。

寄存器间接寻址

寄存器Ri中给出的不是一个操作数,而是操作数所在主存单元的地址,即EA=(Ri)

image-20240405212846374

一条指令的执行:取指令访存1次,执行指令访存1次,暂不考虑存结果,共访存2次。

特点:与一般间接寻址相比速度更快,但指令的执行阶段需要访问主存(因为操作数在主存中)。

隐含寻址

不是明显给出操作数的地址,而是在指令中隐含着操作数的地址。

例如,单地址的指令格式就隐含约定第二个操作数由累加器(ACC)提供。

image-20240405213007367

优点:有利于缩短指令字长,简化地址结构。

缺点:需增加存储操作数或隐含地址的硬件。

立即寻址

形式地址A就是操作数本身,又称为立即数,一般采用补码形式。#表示立即寻址特征。

image-20240405213059148

一条指令的执行:取指令访存1次,执行指令访存0次,暂不考虑存结果,共访存1次。

优点:指令执行阶段不访问主存,指令执行时间最短。

缺点:A的位数限制了立即数的范围,如A的位数为n,且立即数采用补码时,可表示的数据范围为 − 2 n − 1 ∼ 2 n − 1 − 1 -2^{n-1} \sim 2^{n-1}-1 2n12n11

偏移寻址

三种偏移寻址的区别在于偏移的”起点“不一样。

基址寻址

以程序的起始存放地址作为“起点”,将CPU中基址寄存器(BR)的内容加上指令格式中的形式地址A,而形成操作数的有效地址,即EA=(BR)+A

拓展:程序运行前,CPU将BR的值修改为该程序的起始地址(存在操作系统PCB中)。

image-20240405213728087

:基址寄存器是面向操作系统的,其内容由操作系统或管理程序确定。在程序执行过程中,基址寄存器的内容不变(作为基地址),形式地址可变(作为偏移量);当采用通用寄存器作为基址寄存器时,可由用户决定哪个寄存器作为基址寄存器,但其内容仍由操作系统确定。

优点:可扩大寻址范围(基址寄存器的位数大于形式地址A的位数);用户不必考虑自己的程序存于主存的哪一空间区域,故有利于多道程序设计,以及可用于编制浮动程序(整个程序在内存里边的浮动),方便实现多道程序并发运行。

变址寻址

程序员自己决定从哪里作为“起点”,有效地址EA等于指令字中的形式地址A与变址寄存器IX的内容相加之和,即EA= (IX)+A,其中IX可为变址寄存器(专用),也可用通用寄存器作为变址寄存器。

image-20240405214109440

:变址寄存器是面向用户的,在程序执行过程中,变址寄存器的内容可由用户改变(IX作为偏移量),形式地址A不变(作为基地址),这是基址寻址与变址寻址的区别。

优点:在数组处理过程中,可设定A为数组的首地址,不断改变变址寄存器IX的内容,便可很容易形成数组中任一数据的地址,特别适合编制循环程序。偏移量的位数足以表示整个存储空间。

基址&变址复合寻址

image-20240405214341788

先基址后变址寻址:EA=(IX)+(BR)+A

多种寻址方式复合使用,可理解为复合函数。

相对寻址

以程序计数器PC所指地址作为“起点“,把程序计数器PC的内容加上指令格式中的形式地址A而形成操作数的有效地址,即EA=(PC)+A,其中A是相对于PC所指地址的位移量,可正可负,补码表示。

image-20240405214557547

优点:操作数的地址不是固定的,它随着PC值的变化而变化,并且与指令地址之间总是相差一个固定值,因此便于程序浮动(一段代码在程序内部的浮动)。相对寻址广泛应用于转移指令。

堆栈寻址

操作数存放在堆栈中,隐含使用堆栈指针(SP)作为操作数地址。

堆栈是存储器(或专用寄存器组)中一块特定的按“后进先出(LIFO)”原则管理的存储区,该存储区中被读/写单元的地址是用一个特定的寄存器给出的,该寄存器称为堆栈指针(SP)。

image-20240405214747292

image-20240405215343842

image-20240405215139087

:寄存器堆栈也称硬堆栈,硬堆栈的成本较高,不适合做大容量的堆栈。而从主存中划出一段区域来做堆栈是最合算且最常用的方法,这种堆栈称为软堆栈

程序的机器代码表示

高级语言与机器级代码之间的对应

image-20240406080712716

考试要求:题目给出某段简单程序的C语言、汇编语言、机器语言表示。能结合C语言看懂汇编语言的关键语句(看懂常见指令、选择结构、循环结构、函数调用)

汇编语言、机器语言一一对应,要能结合汇编语言分析机器语言指令的格式、寻址方式。

x86汇编语言指令基础

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

mov指令为例

image-20240406081100182

mov指令功能:将源操作数s复制到目的操作数d所指的位置

  1. mov eax, ebx #将寄存器ebx的值复制到寄存器eax
  2. mov eax, 5 #将立即数5复制到寄存器eax
  3. mov eax, dword ptr [af996h] #将内存地址af996h所指的32bit值复制到寄存器eax
  4. mov byte ptr [af996h], 5 #将立即数5复制到内存地址af996h所指的一字节中

内存读写长度:dword ptr——双字,32bit,word ptr——单字,16bit,byte ptr——字节,8bit

若未指明主存读写长度,默认32 bit。

x86架构CPU中的寄存器

image-20240422194435601

每个寄存器都是32bit。

  • 通用寄存器:eax、ebx、ecx、edx(ax表示eax通用寄存器使用低16bit,al表示eax通用寄存器的第8位bit,ah表示eax的低16位bit的高8位bit)
  • 变址寄存器:esi、edi
  • 堆栈寄存器:ebp、esp

变址寄存器可用于线性表、字符串的处理,堆栈寄存器用于实现函数调用。

:寄存器是由触发器构成的,一个触发器能够存储一位二进制码,所以把n个触发器的时钟端口连接起来就能构成一个存储n位二进制码的寄存器。

常用的x86汇编指令

:双操作数指令的两个操作数不允许同为内存地址。

算数运算指令

image-20240406082027511

逻辑运算指令

image-20240406082057247

其他指令

image-20240406082127624

数据传送指令

mov 指令

mov 指令将第二个操作数(寄存器、内存中或常数),复制到第一个操作数(寄存器、内存),但不能用于直接从内存复制到内存。

mov <reg>/<mem>, <reg>/<mem>/<con>

mov eax, ebx;
mov byte ptr [var], 5;

push 指令

push 指令将操作数压入内存的栈,常用于函数调用。栈中元素固定为 32 位。

push <reg32>/<mem>/<con32>
    
push eax;
push [var];//将 var 指示的内存地址的4字节值压入栈

pop 指令

pop 指令执行出栈工作,出栈前先将 ESP 指示的地址种内容取出栈,然后将 ESP 值加 4。

pop edi;//弹出栈顶元素送到 edi
pop [ebx];//弹出栈顶元素送到 ebx 值指示的内存地址的 4 字节中

算术和逻辑运算指令

add/sub 指令

add 指令将两个操作数相加,相加的结果保存到第一个操作数中。

sub 指令用于两个操作数相减,相减的结果保存到第一个操作数中。

add/sub <reg>/<mem>, <reg>/<mem>/<con>

sub eax, 10;//eax-10->eax
add byte ptr [var], 10;//10与var值指示的内存地址的一字节值相加,并将结果保存在与 var 值指示的内存地址的字节中

inc/dec 指令

inc、dec 指令分别表示操作数自加 1、自减 1。

inc/dec <reg>
inc/dec <mem>

dec eax;//eax 值自减 1
inc dword ptr [var];//var 值指示的内存地址的 4 字节值自加 1

imul 指令

带符号整数乘法指令,有两种格式:

  1. 两个操作数,将两个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器
  2. 三个操作数,将第二个和第三个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器。
imul <reg32>,<reg32>/<mem>
imul <reg32>,<reg32>/<mem>,<con>

imul eax,[var];//eax*[var]->eax
imul esi,edi,25;//edi*25->esi

idiv 指令

带符号整数除法指令,只有一个操作数,即除数,而被除数则为 edx:eax 中的内容,64 位整数;操作结果分为两部分:商和余数,商送到 eax,余数送到 edx。

idiv <reg32>
idiv <mem>

idiv ebx
idiv dword ptr [var]

and/or/xor 指令

逻辑与、逻辑或、逻辑异或操作指令,用于操作数的位操作,操作结果放在第一个操作数中。

and <reg>/<mem>, <reg>/<mem>/<con>

and eax, ofH;//将eax中的前28位全部置零,最后4位不变
xor edx, edx;//置edx中的内容为0

not 指令

位翻转指令,将操作数的每一位翻转,即0⟶1,1⟶0。

not <reg>/<mem>

not byte ptr [var];//将 var 值指示的内存地址的一字节的所有位翻转。

neg 指令

取负指令。

neg <reg>/<mem>

neg eax;

shl/shr 指令

逻辑位移指令,shr 为逻辑右移,shl 表示逻辑左移,第一个操作数表示被操作数,第二个操作数指示移位的位数;语法格式如下:

shl <reg>/<mem>, <con8>/<cl>

shl eax, 1;//将eax的值左移1位相当乘于2
shr ebx, cl;//将ebx值右移n位n为cl 中的值,相当于除于2^n

控制流指令

X86 处理器维持这一个指示当前执行指令的指令指针(IP),当执行一条指令后,此指针自动指向下一条指令。IP 寄存器不能直接操作,但可以用控制流指令更新。通常用标签(label)指示程序中的指令地址,在 X86 汇编代码中,可在任何指令前加入标签,例如:

		mov esi, [edp+8]
begin:	xor ecx, ecx
		mov eax, [esi]

AT&T格式和Intel格式

image-20240406082616835

函数调用机器级表示

image-20240406082906116

函数的栈帧(Stack Frame),保存函数大括号内定义的局部变量、保存函数调用相关的信息。

call、ret指令

image-20240406083047230

image-20240406083547996

call指令的作用:

  1. 将IP旧值压栈保存(保持在当前函数的栈帧顶部);
  2. 设置IP新值,无条件转移至被调用函数的第一条指令。

ret指令作用:

  1. 从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器(x86处理器中程序计数器PC通常被称为IP)。

访问栈帧

函数调用栈在内存中的位置

image-20240406083823316

栈底为高地址,栈顶为地址值,图示通常栈底在上,栈顶在下。

标记栈帧范围

image-20240406084041899

通过堆栈基指针ebp和堆栈顶指针esp分别指向当前栈帧的底部和顶部。

对栈帧内数据的访问,都是基于ebp、esp进行的。

:x86系统中,默认以4字节为栈的操作单位。

访问栈帧数据

  1. push、pop指令实现入栈、出栈操作。
push 🐶//先让esp-4,再将🐶压入栈
pop 🐶//栈顶元素出栈,并写入🐶,再让esp+4

栈底为高地址,栈顶为低地址。要想入栈,即指针地址变低,即-4;要想出栈,即指针地址变高,即+4。

  1. 可以用mov指令,结合esp、ebp指针访问栈帧数据。可以用减法/加法指令,即sub/add修改栈顶指针esp的值。

切换栈帧

image-20240406084934881

当执行到caller函数的call指令时,IP指向下一条mov指令。具体执行流如下:

  1. IP旧值(即下一条mov指令地址)压入当前函数(caller)栈帧顶部(esp-4),同时设置IP新值(指向add函数的push指令),无条件转移至被调函数(add)的第一条指令。
  2. 执行add函数的push指令,将ebp(堆栈基地址)压入(esp-4)当前函数(add)的栈底部(IP自动加“1”)。
  3. 执行add函数的mov指令,将esp的值移动到ebp中,即将栈底指针指向此时的栈顶指针(此时esp指向add函数栈底,ebp未移动时指向caller函数栈底)。

:指令enter等同于(push ebp;mov ebp,esp)两条指令。指令leave等同于(mov esp,ebp;pop ebp)两条指令。

  1. add函数指令顺序执行…
  2. 执行到add函数的leave指令(此时,ebp指向add函数栈底,esp指向add函数栈顶,栈底保存着caller函数的栈底地址),将ebp的值移动到esp中(转移后,add函数的整个栈帧清除,只剩下栈底4字节)。
  3. leave指令还会将栈顶的值出栈(此时add函数的栈顶值为caller函数的栈底),赋值给ebp(esp+4,回到caller函数栈帧,且指向栈顶;ebp指向caller函数的栈底)。
  4. add函数指向ret指令,将栈顶(即esp指向地址,已经是cller函数的栈帧了)的IP旧值出栈,恢复IP寄存器。

栈帧内容及如何传参

一个栈帧的内容

image-20240406132023631

GCC编译器为保证数据的严格对齐而规定的每个函数的栈帧大小必须是16字节的倍数(当前函数的栈帧除外),因此栈帧内可能出现空闲未使用的区域。

  1. 通常将局部变量集中存储在栈帧底部区域(越后定义的局部变量越靠近栈底,即越在上方)
  2. 通常将调用参数集中存储在栈帧顶部区域,从右往左依次保存(第一个调用参数最靠近栈顶)。
  3. 栈帧最底部一定是上一层栈帧基址(ebp旧值)。
  4. 栈帧最顶部一定是返回地址(当前函数的栈帧除外)。

汇编代码示例

image-20240406132622122

  1. caller函数前两行的push和mov是进入函数的操作,将上个函数的栈底位置压栈,同时让栈底指针指向这个位置。
  2. sub指令使得esp的值-24,即esp向下(低地址)移动24字节的大小,为caller函数开辟24字节大小的栈帧。
  3. 4-5行的mov指令表示局部变量的存放,每个局部变量是int型,占四个字节,有三个局部变量,最先定义的局部变量在最下面(靠近栈顶),即[esp-12]存放125(temp1)这个数据,第三个局部变量sum在[esp-4]的位置,未初始化。
  4. 6-9行的mov指令,进行函数调用的传参准备,函数需要传入两个参数x,y。先将[ebp-8](temp2)的值给eax寄存器,再从eax中转移到[esp+4](esp的上一行)作为参数y。再将[ebp-12](temp1)的值给eax寄存器,再从eax中转移到esp的位置作为参数x。

此时IP指向call指令,函数的栈帧如下图所示:

image-20240406134045425

需要进行函数调用,即切换栈帧,具体内容在切换栈帧中有详细描述。

  1. add函数将[ebp+12](x)和[ebp+8](y)分别移动到寄存器,进行加法运算,最后将结果存储eax寄存器。

(注:此时的ebp=esp(未给add分配更多的栈帧空间),而esp经过切换栈帧,压入了IP旧值和caller栈底地址,esp-8)

  1. 执行add函数的leave和ret指令。
  2. 回到caller函数,11行的maov指令将add函数保存在eax的计算结果,赋值给ebp-4(sum)中。
  3. 12行mov指令将sum值转移到eax中,准备返回值。

总结

image-20240406135109669

:寄存器EAX、ECX和EDX是调用者保存寄存器,当P调用Q时,若Q需用到这些寄存器,则由P将这些寄存器的内容保存到栈中,并在返回后由P恢复它们的值。若保存,则紧挨着调用参数上方存储。

寄存器EBX、ESI、EDI是被调用者保存寄存器,当P调用Q时,Q必须先将这些寄存器的内容保存在栈中才能使用它们,并在返回P之前先恢复它们的值。

选择语句机器级表示

无条件转移指令——jmp

jmp <地址> #PC无条件转移至<地址>。

此处的地址可以是由常数给出,可以来自于寄存器,可已来自于主存。

同时,jmp NEXT #<地址>可以用“标号”(标签)锚定。

标号特征,有冒号,名字可以自己取。

条件转移指令——jxxx

指令含义
cmp a,b#比较a和b两个数
je <地址>#jump when equal,若a==b则跳转
jne <地址>#jump when not equal,若a!=b则跳转
jg <地址>#jump when greater than,若a>b则跳转
jge <地址>#jump when greater than or equal to,若a>=b则跳转
jl <地址>#jump when less than,若a<b则跳转
jle <地址>#jump when less than or equal to,若a<=b则跳转
jbe <地址>#jump when below or equal to,和jle一样的效果

主要依靠上述指令完成条件转移,使用cmp进行比较,通过比较结果进行跳转。

具体操作如下:

cmp eax,ebx//比较寄存器eax和edx里的值
jg NEXT//若eax>ebx,则跳转到NEXT

test eax,eax//测试eax是否为0
jz xxxx//为零则标志位ZF=1,跳转到xxxx执行

:条件转移指令根据相应标志位进行条件判断

image-20240406141446645

示例

image-20240406141014003

注意使用不同的条件判断,代码结果会不一样。

如果按照C语言代码使用汇编语言条件指令,则紧跟着条件指令的代码部分为C语言代码中的else部分,而if代码部分则在标号之后。

如果使用C语言逻辑判断的反,使用汇编指令,则代码结构跟C语言代码相同。

image-20240406141040808

循环语句机器级表示

常见的循环结构语句由while、for、do-while,汇编中没有相应的指令存在,使用条件测试和跳转组合结合起来实现循环效果,大多数编译器将这三种循环结构都转化为do-while形式产生机器代码。

用条件转移指令实现循环

image-20240406141809621

用loop指令实现循环

image-20240406141853791

loop Looptop=(dec ecx;cmp ecx,0;jne Looptop)

理论上,能用loop指令实现的功能一定能用条件转移指令实现,使用loop指令可能会使代码更清晰简洁。

补充:loopx指令——如loopnz, loopz;loopnz——当ecx!=0 && ZF==0时,继续循环;loopz——当ecx!=0 && ZF==1时,继续循环。

CISC和RISC

两种设计方向

image-20240406142055966

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值