指令系统(计组)

        第二章数据的表示和运算中,我们学到了计算机中数据运算的具体过程,以及浮点数的表示;第三章存储系统中,我们学到了各种存储器之间的层级关系以及他们的具体实现方式及功能。而本章指令系统主要学的分为如何设计指令,指令的寻址,以及汇编语言中的指令这三个方面。

一、如何设计指令

        在第二章的学习中,我们在学习CPU是如何进行算术运算的时候,了解到了指令是分为两个部分组成的,第一部分在高位叫做操作码,第二部分在低位叫做地址码,操作码决定了该指令是要执行什么具体操作,而地址码则决定了该指令中所需要的操作数所在的地址。

        1.1指令的分类

        1.按照地址码的数量分类:了解了指令的结构之后,一条指令可能需要多个操作数,也有可能不需要操作数,这样一来地址码的数量就会有所变化,所以首先我们可以根据地址码的数量来为指令进行分类。零地址指令就是没有操作码的指令,该指令通常是终止、空操作、关中断指令等不需要操作数的指令;一地址指令是只有一个操作码的指令,该指令通常是移位运算、求反码、自增自减等只需要一个操作数的指令,但是也存在隐含寻址方式的指令,就是该指令需要两个操作数,但其中一个操作数在寄存器中,不需要用地址码来表示出来,所以该指令只有一个操作码;二地址指令和三地址指令则是那种C语言中双目操作符的指令,比如说求两个数的和并将结果存放在目的操作数的地址中,而三地址指令则是将结果存放在一个新的地址码所表示的地址中,而不是目的操作数的地址;当一个指令执行完毕后,PC会自动跳转到下一条指令,但四地址指令中的第四个地址码此时就会被复制到PC中,即下一条指令的地址由四地址指令的第四个地址码决定。

        2.按照指令的长度分类:在计算机中,有的指令十分麻烦,需要的操作数也很多,那么此时指令所对应的二进制编码自然会很长,所以根据定长指令和变长指令可以将指令再次分类,定长指令通常是一些最基础的指令,而变长指令通常是由很多基础指令的功能合并而成,定长指令的编写指令的方式是RISC,而变长指令的编写指令的方式是CISC,RISC指令集通常被用于手机以及平板等移动设备,而CISC指令集通常被用于笔记本和台式机等PC端。

        3.按照操作码的长度分类:一个固定位数的二进制数所能表示的内容是有限的,而我们又知道指令按照地址码分类之后,存在着零地址指令、一地址指令、二地址指令等各种指令,那么我们需要解决的问题就是如何在有限的二进制位中将这些指令全都表示出来,于是便有了操作码长度不同的指令。按照地址码数量来分类我们发现,零地址指令和四地址指令的二进制位都相同,但是四地址指令中需要留出四个位置来存放地址,所以零地址指令操作码长度自然比四地址操作码长度要长,其实按照地址码数量分类和按照操作码长度来分类本质上就有一定的联系,具体内容将在下一个小节深究。所以按照操作码长度可以分为定长操作码和变长操作码。

        4.按照指令类型分类:在汇编语言中,指令的类型是多种多样的,比如进行算数运算的add、sub以及进行逻辑运算的and、or、not、xor;进行数据传输的mov;进行移位运算;以及进行跳转的jmp、jx、call、ret,jmp叫做无条件跳转,而jx是条件跳转,而jx中的x就是条件,具体内容会在汇编语言中的指令中深究。

        1.2扩展操作码

        在按照操作码的长度分类中,我们说过,固定的二进制位,想要表示各种指令,并且还要保证各种指令的数量,没有一个合理的设计规则是很难做到的。此处我将用固定的16bit位来讲解如何设计来扩展操作码。         图中希望我们能够设计出15条三地址指令,12条二地址指令,62条一地址指令和32条零地址指令。首先是15条三地址指令,显然高位的4个bit即可表示16种指令,而要求15条三地址指令,所以我们用前4位表示三地址指令,但是前4位可以表示16种状态,而此时我们只使用了15种,那么我们就可以利用这多余的一种状态来作为二地址指令的扩充状态,换句话就是说,只要前4位表示的是此种状态,那么我们就可以认定该指令为二地址指令。接下来需要设计12条二地址指令,显然还是4个bit位就可以表示所以,我们用第二个4bit位就可以表示,但是第二个4bit位可以表示16种状态,我们只使用了12种,还剩下4种状态,那么此时我们就可以利用这四种状态来作为一地址指令的扩充状态,也就是说,只要前8位出现了这4种状态,我们就可以认定该指令为一地址指令,而62条一地址指令虽然理论上需要6个bit位才能全部表示,但是由于在设计二地址指令的时候我们留下了4种状态来表示一地址指令,所以我们只需要4个bit位就可以表示4×16种也就是64种状态,表示62条一地址指令绰绰有余,那么我们就又剩下了64-62两种状态来作为零地址指令的扩充状态,所以使用剩下的4个bit位就可以表示2×16也就是32条零地址指令。

        接下来我们分析图中的指令格式,首先是三地址指令,高四位bit位从0000-1110就可以表示,剩下了1111这种状态作为二地址指令的扩充,所以二地址指令的标志就是前4个bit位为1111,而二地址指令也是需要4个bit位就可以表示,需要12条所以只需要0000-1011即可,剩下的1100、1101、1110、1111这四种状态用来扩充一地址指令,而我们发现这四种状态的前两位全都是1,所以一地址指令的标志就是前6位bit位为111111,下面的分析同理即可不做赘述。

二、指令的寻址

         指令的寻址在大体上分为两种,一种是寻找下一条应该执行的指令地址,也称指令寻址;另一种是根据指令寻找地址码中的数据,也称数据寻址。

        2.1指令寻址

        2.1.1顺序寻址

        在第一章我们学过CPU中含有一个程序计数器PC,它可以记录下一条指令的地址,而顺序寻址的意思就是说,PC每次记录的都是紧挨着正在执行的指令的物理地址意义上的下一条。当指令采取RISC方式时,每个指令大小都是一样的,这里我们假设每个指令大小都为一字节,根据题中的具体条件,PC到底是取出多少bit位的指令才加1来判断PC每取出一个指令后加多少,比如,每取1个字节PC加1,那么此处就是每取出一个指令PC加1。

        定长指令结构中PC的自增就像是等差数列一样,每次都是加同样的值,那么对于变长指令结构来说,PC的变化是如何的呢?我们假设PC每取一个字节后加1。

         图中的不同颜色代表着不同指令,比如果黄色代表的指令占32位。假设我们此时正在执行黄色的指令,那么此时的PC指向绿色指令也就是PC=4,当黄色指令执行完毕后,CPU会取PC所指向的指令,当取出第一个字节时PC=4+1,当取出第二个字节时,PC=4+1+1,此时绿色指令全部取出,此时PC=6正好指向灰色指令。所以在变长指令结构中,PC的每次自增是随着指令的大小而定的,不再是等差数列。

        2.1.2跳跃寻址

        为了实现类似C语言中的循环,函数调用等功能,汇编语言中存在着JMP无条件跳转和JX条件跳转指令,而跳跃寻址就与该跳转指令有关,当指令执行到跳转指令的时候,程序计数器PC中的指令地址会被修改为跳转指令所指向的指令地址,于是形成了跳跃寻址。

        2.2数据寻址

        指令分为操作码与地址码,其中数据寻址就是对于地址码进行解读,而由于解读方式多种多样,所以计算机为了分辨此处的地址码是用何种方式进行解读,需要为指令额外留出几个bit位来区分各种数据寻址方式。此外有的时候题目中可能还有一些其他的方式用来重定位地址,比如一个地址偏移量为0011,而重定位的意义在于给该偏移量一个起点,这也是操作系统中学过的重定位寄存器,此时我们就额外需要一些bit位用来区分每个寄存器,具体问题还应具体分析。

1.直接寻址:该寻址方法简单粗暴,地址码就是该操作数真实的物理地址。取指令访存一次,取操作数访存一次,共两次访存。

2.间接寻址:该寻址方式是地址码中的地址指向的是操作数的地址,也就是地址码是操作数地址的地址,这也是一次间接寻址,取指访存一次,取操作数地址访存一次,取操作数访存一次,共三次访存。

3.寄存器寻址:该寻址方式中,地址码指向的是某个寄存器,而寄存器中存储着操作数本数。取指令访存一次,取操作数访问一次寄存器,共一次访存。

4.寄存器间接寻址:该寻址方式中,地址码指向的也是某个寄存器,而寄存器中存放的是操作数的真实物理地址。取指令访存一次,取操作数地址访存0次,取操作数访存一次,共访存两次。

5.隐含寻址:该寻址方式可以取两个操作数,其中一个操作数的寻址方式为直接寻址,而另一个操作数则隐含在ACC中。取指令访存一次,取操作数访存一次,共访存两次。

6.立即寻址:该寻址方式是最快的寻址方式,比寄存器寻址还要更快,该方式中,地址码就是操作数本数,无需访存,甚至不需要访问寄存器。取指令访存一次,取操作数访存0次,共访存1次。

7.基址寻址:该寻址方式需要借助重定位寄存器的帮助才可以实现,重定位寄存器的功能就是记录该程序在内存中的起始地址,我们此时假定该段代码是存放在了内存中的一片连续空间,那么当我们有了该段代码的起始地址后,只需要在地址码中记录我们要查找的具体某句代码在该段代码中的相对位置即可,也就是偏移量,这样起始地址+偏移量就可以找到我们想找的那句代码,这种寻址方式就是基址寻址。

        在操作系统中,我们学到了多道程序并发运行的原理,就是只需要将程序中需要用到的代码段装入内存,就可以使该程序运行,这种方式可以使多个程序同时在空间很小的内存中运行,但是需要注意的就是,我们需要知道每段代码都存放在了内存中的什么位置,那么基址寻址就可以做到,我们只需要为每段代码设置一个起始地址放入基址寄存器中,再通过偏移量就可以找到任意一句代码,所以基址寻址可以实现多道程序并发运行。

        需要注意的是,程序员有权限设置哪个通用寄存器用来作为基址寄存器,而没有权限改变基址寄存器中的值,基址寄存器中具体的值是由操作系统来决定的。

8.变址寻址:在基址寻址中,代码段的起始地址放在重定位寄存器中,偏移量在指令的地址码中,而变址寻址正好相反,代码段的起始地址放在指令的地址码中,代码段的偏移量放在寄存器中,在基址寻址中,程序员没有权限控制基址寄存器中的值,而在变址寻址中,程序员有权限控制变址寄存器(IX)中的值,通过改变IX中的值来实现代码的跳转以及循环等操作,具体实现方式如图:       

        比如我们现在想要实现图左中的循环,按照如果没有变质寻址,按照汇编语言的写法,我们需要一步一步的实现循环,但是这对于编写程序是极为不方便的,所以变址寻址就是为了解决循环和数组问题而诞生的。首先我们看到循环中i和a[i]的初始化都为0,所以取0到ACC,取0到IX寄存器中,来到第2条指令,我们看到指令操作码为ACC加法,而地址码为7,前面讲过变址寻址指令的地址码为起始地址,IX中的值为偏移量,故7+0=7,而(7+(IX))的含义是取走7+(IX)地址中的值,也就是a[0],接下来执行第三条指令,也是变址寻址的精髓所在即程序员有权限改变IX中的值,我们令IX+1,就达到了C语言中的i++操作,然后是IX比较,也就是实现C语言中i<10的操作,在汇编语言中,比较的基本原理就是两个数相减后得到的结果与0进行比较,比较后进行跳转,也就是指令4和指令5的效果。其中比较的原理就是将结果存入程序状态字寄存器(PSW)中,PSW中存放着该次相减后的CF、OF、SF和ZF中的值,CF代表无符号数的进位和借位、OF代表有符号数的溢出、ZF代表此次相减结果是否为0、SF表示此次相减结果的符号,通过这4个标志位即可判断有符号数相减后结果如何,以及无符号数是否够减。指令5执行完毕后发生条件跳转循环继续,后续内容不做赘述。

9.相对寻址:在程序员编写程序的过程中,难免会想要将一个程序中的某一段代码移动到该程序的其他位置中,但是如果该段代码中存在着跳转代码,那么一旦移动了该段代码的位置,那么就极有可能会影响到跳转代码的正确性,那么为了解决这种问题,相对寻址便诞生了。相对寻址的原理是将跳转代码的跳转地址从一个绝对地址改成相对于该段代码的相对位置来实现保护跳转代码的正确性。举个例子,比如该段代码的起始地址为0,而跳转代码的地址码则为0,这样如果改变代码段的位置,那么JMP的地址码则不再正确,相对寻址就是通过为JMP的地址码找到该段代码中的某一个位置作为起始位置5,然后通过对这个起始位置5进行减5的操作来达到跳转的目的,因为该起始位置是随着代码段移动而移动的,那么按照这种方法,无论代码段怎么移动,JMP的跳转位置则永远都是该代码段的0位置。

        这就是相对寻址的基本逻辑,那么我们怎么设置这个起始位置呢?答案是PC,由于PC中存放着的是即将执行的下一条指令,那么每当程序执行到跳转指令的时候,PC中的值永远都是该跳转指令的下一条指令的地址,然后合理的设置一个加数或减数,即可达到跳转的目的。

10.堆栈寻址:利用栈的后进先出的特性来实现的寻址方式,堆栈可以用寄存器形成,也可以用一片内存空间形成,用寄存器形成的堆栈叫硬堆栈,而用内存形成的堆栈叫做软堆栈。

        这里以硬堆栈举例,假设由4个寄存器形成一个栈。首先利用栈顶指针SP来指向栈顶元素,而4个寄存器利用两个bit位就可以表示,所以SP为两个bit位。当利用POP指令来弹出栈顶元素之后,根据栈的地址排放方式来判断SP进行加1还是减1。

三、汇编语言中的指令

        3.1认识汇编语言的格式

        汇编语言中的指令与操作码和地址码是一一对应的,一条指令有两个作用,其一是对数值进行运算操作,另一个就是改变指令的执行流,也就是跳转。我们以对数值进行操作来引入在汇编语言中,是如何实现对一个数值进行操作的。

MOV EAX,EBX                        #将EBX中的值放入EAX
MOV EAX,5                          #将立即数5放入EAX
MOV EAX,dword ptr [af996ah]        #将内存中af996ah所指的地址中取两个字放入EAX中
MOV byte ptr [af996h],5           #将立即数5放入内存中af996ah所指地址的一个字节中

        我们以该串代码举例,首先我们来认识EAX和EBX,E指的是32位,而AX和BX指的是计算机中不同的通用寄存器,如果直接就是AX和BX而没有E,就表示16bit,当然计算机中还有CX、DX等通用寄存器。其次是代码中的5,如果汇编语言中出现一个数字,而该数字没有任何符号的修饰,那么就代表该数字是立即数,也就是立即寻址中的立即数,直接使用即可,而如果一个数用了中括号来修饰,比如[AF996AH],这个数中AF996A指的是地址,而H指的是该数字为16进制数,所以遇到带有[ ]修饰的16进制数指的就是内存中的地址,最后是dword ptr以及byte ptr,这两个代码中word指的是一个字,在8086汇编语言中,一个字就是16位,byte指的是一个字节,一个字节是8位,而dowrd中的d指的是double,也就是说dword实际上指的是两个字。

        值得注意的是,汇编语言的格式中,两个操作数,左边的是目的操作数,而右边的是源操作数,也就是指令的操作码执行完毕后对数值进行改动后需要将数值放入左边的寄存器或者内存地址中,所以汇编语言代码中,立即数不可能出现在左侧,而且左侧和右侧不能同时出现内存地址。

        3.2汇编语言中的运算指令

d为目的操作数,s为源操作数
算数运算:
add d,s/sub d,s      #加法/减法
mul d,s/div d,s      #乘法/除法
neg d                #取负数
inc d /dec d         #自增/自减
逻辑运算:
and d,s/or d,s/xor d,s    #与/或/异或
not d          #非
shl d,s/shr d,s      #逻辑左移/逻辑右移

        3.3用汇编语言实现 if 语句  

        首先将立即数7放入寄存器eax中,然后将立即数6放入寄存器ebx中,cmp指令为比较指令,会通过标志位来判断,jg指令是指greater,也就是a大于b的话就跳转到NEXT, 也就是实现了if语句,如果a小于b则不跳转,于是实现了else。

        3.4用汇编语言实现循环

        首先将立即数0存入寄存器eax中达到初始化int result的目的,然后将立即数1存入寄存器edx中,然后cmp指令比较edx与100的大小不满足i>100,所以不跳转继续执行指令,下面是循环主体,add edx , eax指的是将i加到result中,然后inc指令实现 i 自增,然后cmp指令比较 i 与100的值,jle指的是leter更小的,也就是若 i 更小则跳转到L1,于是就实现了循环。

        

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值