红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【计算机组成原理】指令系统

指令基本格式

通常包括操作码字段和地址码字段两个部分

分类

按操作数

零地址指令:

  • 不需要操作数,如空操作,停机和关中断等
  • 堆栈计算机,两个操作数隐含在栈顶和次栈顶,计算结果压入栈顶

一地址指令:

  • 只要单操作数,如++,–,取反,求补等

    格式:\(OP(A_1)\rarr A_1\),即取\(A_1\)中的内容操作后放回\(A_1\)

    三次访存:取指、读A1、写A1

  • 需要俩操作数,但其中一个隐含在某个寄存器中(如ACC)

    格式:\((ACC)OP(A_1)\rarr ACC\)

​ 两次访存:取指、读A1

二地址指令:

  • 用于两个操作数的算术运算和逻辑运算等

    格式:\((A_1)OP(A_2)\rarr A_1\)

    四次访存:取指、读A1、读A2、写A1

三地址指令:

  • 用于两个操作数的算术和逻辑运算等

    格式:\((A_1)OP(A_2)\rarr A_3\)

    四次访存:取指、读A1、读A2、写A3

四地址指令:

  • 格式:\((A_1)OP(A_2)\rarr A_3, A_4\)是吓一跳将要执行指令的地址

    四次访存:取指、读A1、读A2、写A3

也就是执行指令后,PC的值修改为A4所指地址

按指令长度

指令字长:指令的总长度

  • 定长指令字结构:指令系统中所有指令长度都相等
  • 变长指令字结构:指令系统中所有指令长度不等

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

存储字长:一个存储单元中的二进制代码位数(和MDR位数相同)

分为半字长、单字长、双字长指令,会影响取指令所需时间

按操作码长度

定长操作码:所有操作码长度都相同(若有n为则最多\(2^n\)条指令)

可变长操作码:各指令操作码长度可变

按操作类型

数据传送:如LOAD,STORE

算术逻辑操作

移位操作

转移操作:如JMP,JZ1

输入输出操作

扩展操作码

定长指令字结构+可变长操作码即为扩展操作码

设计扩展操作码时,要注意:

  • 不允许短码是长码的前缀
  • 各指令操作码一定不能重复

对使用频率较高的指令分配较短的操作码,较低的分配较长的

例:指令字长固定16位,地址码需要四位,需要满足

  • 15条三地址指令
  • 12条二地址指令
  • 62条一地址指令
  • 32条零地址指令

则三地址指令的指令码设为0000-1110

二地址开头4bit为1111,后四位设为0000-1011,剩余的开头11xx的留给下一位地址

所以一地址开头设为111111,后六位设为1地址指令,范围是110000-111101,剩余开头11111x的留给下一位地址

所以零地址开头设为11111111111,后五位正好0-31装32条,00000-11111

总结一下:若地址长度为n,上一层留出m种状态,下一层可以扩展出\(m\times 2^n\)种状态

寻址

指令寻址

PC始终用于指向下一条要执行的指令的地址

若对一条2字节的指令寻址,但是如果是按字节编址,一条指令需要+2才能得到下一条,或者按变长指令字,PC不知道一条指令多长,无法按指定值递增,会出现问题

看两个例子:

按字编址

按字节编址

变长指令字结构

图中相同颜色的是一条指令,可以看出每条指令占的字节数都不同

跳跃寻址

可以由转移指令JMP给出指定地址码,将PC的值修改为对应地址,实现无条件转移

数据寻址

确定本条指令的地址码指明的真实地址

数据寻址有十种寻址方式,为了指定每次寻址的寻址特征,可以在每个形式地址前添加一个四位的寻址方式位,称为寻址特征

寻址特征+形式地址才是操作数的真实地址,称为有效地址(EA)

直接寻址

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

需要访存两次:取指令、执行指令

优点:执行指令仅访问一次主存,不需要专门计算操作数地址

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

间接寻址

指令地址字段给出的是操作数有效地址所在的存储单元的地址,即地址的地址,EA=(A)

间接寻址可以间接若干次

访存三次:取指令一次,执行指令两次

优点:可以扩大寻址范围(有效地址EA的位数大于形式地址A的位数),便于编制程序

缺点:多次访存

寄存器寻址

指令字中直接给出操作数所在寄存器编号,即\(EA=R_i\),操作数位于\(R_i\)所指的寄存器内

访存一次:取指令一次

优点:执行阶段不访问主存,只访问寄存器,指令字短且速度快

缺点:价格昂贵,寄存器个数有限

寄存器间接寻址

寄存器给出的是操作数所在主存单元地址\(EA=(R_i)\)

访存两次:取指令一次,执行指令一次

特点:比一般间接寻址快

隐含寻址

指令中隐含着操作数的地址

优点:有利于缩短指令字长

缺点:需要增加存储操作数或硬件

立即寻址

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

访存一次:取指令

偏移寻址

基址寻址

将CPU中基址寄存器(BR)的内容加上指令格式中的形式地址A,形成操作数的有效地址,即EA=(BR)+A

基址寻址

有的设备中会使用若干通用寄存器代替基址寄存器的功能,需要花\(\log_2n\)个bit位指明用哪个寄存器作为基址寄存器

优点:便于程序浮动,方便多道程序并发运行

注意:基址寄存器是面向操作系统的,内容由操作系统或管理程序决定,程序执行时基址寄存器内容不变

采用通用寄存器作为基址寄存器时,用户只可以决定用哪个寄存器作为基址寄存器

变址寻址

有效地址EA等于指令字中的形式地址A与变址寄存器IX的内容相加之和,即EX=(IX)+A

同样可以用通用寄存器作为变址寄存器

注:变址寄存器是面向用户的,程序执行过程中,变址寄存器的内容可以由用户改变

例:

相对寻址

把程序计数器PC的内容加上指令格式中的形式地址A而形成操作数的有效地址,即EA=(PC)+A,A是相对于PC所指地址的偏移量,补码表示

例:

优点:代码在程序内浮动时不用更改跳转指令的地址码

堆栈寻址

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

堆栈是存储器内一块特定按LIFO管理的存储区,该存储区中被读写单元的地址是用一个特定的寄存器给出的,称为堆栈指针SP

汇编语言

指令格式

以mov为例,格式为mov d,s

作用为将源操作数s复制到目的操作数d所指位置

  • mov eax, ebx:将寄存器ebx的值复制到eax
  • mov eax, 5:将立即数5复制到寄存器eax
  • mov eax, dword ptr [地址]:将指定内存地址所指的双字值复制到寄存器eax
  • mov byte ptr [地址], 5:将立即数5复制到指定内存地址所指的一字节中

其中内存的读写长度:

  • dword ptr:双字,32bit
  • word ptr:单字,16bit
  • byte ptr:字节,8bit

x86的常用寄存器

寄存器
  • 以E开头的寄存器长为32bit
  • 前四个寄存器存储未知数据,结尾为X,称为通用寄存器
  • 下面两个寄存器 处理字符串等,结尾为I,称为变址寄存器
  • EBP为堆栈基指针
  • ESP为堆栈顶指针
  • 前四个若只想使用16bit,可以去掉前面的E
  • 前四个若只想使用8bit,对应的寄存器为AH, AL,BCD同理

常用x86汇编指令

算术运算

除法这里,s为除数,被除数被隐含寻址于edx:eax

这里的意思是两个寄存器拼接,因为在进行除法操作时,被除数会扩展其位数到原来的两倍

逻辑运算

AT&T与Intel格式

选择语句

无条件转移指令

jmp 地址 将PC无条件的转移到对应地址,或寄存器指向的地址

jmp 标号无条件转移到对应标号指明位置,标号的格式名字:

条件转移指令

条件转移指令

一般要和cmp a,b一起使用,先比较,若上面的比较成立则执行下面的跳转指令

循环语句

条件转移指令实现

由四个部分组成:

  • 循环前初始化
  • 是否直接跳过循环
  • 循环主体
  • 是否继续循环

loop指令实现

loop默认用ecx作循环计数器

loop对应的还有某些loopx指令,如loopnz表示当ecx!=0且ZF==0时继续循环

函数调用

call与ret

函数调用:call 函数名

函数返回:ret

通常用函数名作为函数起始地址的标号

注:Intel里PC被叫做IP

  • 执行call后,会将IP内的值(call的下一条)压入栈顶
  • 然后进入调用的函数,将IP新值设为被调用函数第一条指令
  • 调用函数栈帧出栈,ret后将IP旧值写回

访问栈帧

在虚拟内存用户区中,用户栈的栈底位于高地址,栈顶位于低地址,所以一般表示栈是倒着画的,且x86系统中,默认以四字节为栈的操作单位

寄存器ebp指向当前栈帧的底部

寄存器esp指向当前栈帧的顶部

指令:

  • push:入栈操作,先让esp减4,再将数据压入
  • pop:出栈操作,栈顶元素出栈,写入指定寄存器,再让esp加4
  • mov:访问栈其他位置 ,结合ebp和esp指针访问数据,可以用sub/add指令修改栈顶指针esp的值

切换栈帧

当call一个新函数时,我们进入对应栈帧:

  • 首先call指令会将IP旧值(call的下一句)压栈保存,并设置IP新值为被调用函数的第一条指令
  • push ebp把上一层函数的ebp旧值压入栈,这个地址就是返回地址
  • 然后move ebp, esp,把ebp寄存器指向到esp的位置(新函数地址)
  • 接下来用mov等压入数据,执行操作
  • 前两个操作可以用一个enter指令代替
  • 函数返回后,新的栈帧消失,栈顶此时就是原来的ebp旧值和IP旧值
  • 首先执行mov esp, ebp,将esp指向当前栈帧底部

  • 然后执行pop ebp,将esp所指元素出栈,写入寄存器ebp,实现让ebp重新指回上一层函数栈帧基址

  • 上面两个命令等价于leave指令,即从函数栈帧顶部找到IP旧值,出栈并恢复IP寄存器

传参与返回值

一个栈帧内可能包含的内容:

  • 最底部:上一层栈帧基址(ebp、旧值)
  • 最顶部:返回地址(当前函数栈帧除外)
  • 靠近底部:用于存储局部变量,越靠前定义的越靠近栈顶
  • 靠近顶部:用于存储形式变量,参数列表中越靠前的参数越靠近栈顶
  • 中间:可能有未使用区域没因为栈帧大小要求16字节整数倍

CISC和RISC

CISC:复杂指令集,一条指令完成一个复杂的基本功能,用于x86架构

如果有复杂指令用纯硬件实现困难,可以采用存储程序的设计思想,由一个比较通用的电路配合存储部件完成一条指令,即形成微程序

RISC:精简指令集,一条指令完成一个基本动作,多条指令组合完成一个复杂的基本功能 ,用于arm架构