• 计算机指令


    指令系统是计算机硬件的语言系统。
    如果我们从软件工程师的角度来讲,CPU 就是一个执行各种计算机指令(Instruction Code)的逻辑机器。这里的计算机指令,就好比一门 CPU 能够听得懂的语言,我们也可以把它叫作机器语言(Machine Language)。

    指令格式

    指令由两部分构成:操作码字段地址码字段

    • 操作码一般用OP来表示
    • 地址码一般用A来表示,不同的地址码位数不同

    操作码

    • 定长操作码:规整,RISC采用定长
    • 变长操作码:操作码于地址码位数可变,总和一致

    地址码

    根据一条指令有多少操作数地址,可将该指令称为几操作数指令。

    操作数分为被操作数操作数操作结果三部分。

    shuyu

    • 四地址指令A1代表第一个操作数的地址,A2代表第二个操作数的地址,A3用来保存(A1)OP(A2)的计算结果,A4用来指出下一条指令的地址。指令可以明确的支出下一条指令的地址,但是指令长度太大,实际应用中几乎不采用这种指令格式。
    • 三地址指令A1代表第一个操作数的地址,A2代表第二个操作数的地址,A3用来保存(A1)OP(A2)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。指令长度依然很长,执行一次指令要四次访存,取指令一次,取操作数两次,存结构一次;操作数不会被破坏。
    • 二地址指令A1代表第一个操作数的地址,A2代表第二个操作数的地址,A1用来保存(A1)OP(A2)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。执行一次指令仍要四次访存,取指令一次,取操作两次,存结果一次;执行完指令之后,计算结果覆盖掉一个操作数。
    • 一地址指令:一般出现在需要累加计算的地方,累计寄存器Acc就是用来保存计算结果的,A1代表第一个操作数的地址,Acc用来保存(Acc)OP(A1)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。执行一次只需要访存两次,取指令一次,去操作一次;被操作数和运算结构都放在累加寄存器中,所以对于被操作数的读取和存入都不需要访存。
    • 零地址指令:主要用在堆栈计算机中,参加运算的操作数从栈顶弹出,运算后结果压入堆栈中。

    寻址方式

    寻址分为数据寻址(data)指令寻址(instruction)

    数据寻址

    1. 隐含寻址
    2. 立即寻址
    3. 直接寻址
    4. 间接寻址
    5. 寄存器寻址
    6. 寄存器间接寻址
    7. 相对寻址
    8. 基址寻址

    指令寻址

    • 指令寻址顺序寻址跳跃寻址两种方式。

    shuyu

    解析指令和机器码

    常见的指令可以分成五大类。

    • 第一类是算术类指令。我们的加减乘除,在 CPU 层面,都会变成一条条算术类指令。
    • 第二类是数据传输类指令。给变量赋值、在内存里读写数据,用的都是数据传输类指令。
    • 第三类是逻辑类指令。逻辑上的与或非,都是这一类指令。
    • 第四类是条件分支类指令。日常我们写的“if/else”,其实都是条件分支类指令。
    • 最后一类是无条件跳转指令。写一些大一点的程序,我们常常需要写一些函数或者方法。在调用函数的时候,其实就是发起了一个无条件跳转指令。

    shuyu

    下面我们来看看,汇编器是怎么把对应的汇编代码,翻译成为机器码的。

    不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和不同的机器码。为了方便你快速理解这个机器码的计算方式,我们选用最简单的 MIPS 指令集,来看看机器码是如何生成的。

    MIPS 是一组由 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集。就在最近,MIPS 公司把整个指令集和芯片架构都完全开源了。

    shuyu

    MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。

    R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。

    I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。

    J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。

    加法执行过程

    例如使用 a = 1 + 2 。程序编译过程中,编译器通过分析代码,发现 1 和 2 是数据,于是程序运行时,内存会有个专门的区域来存放这些数据,这个区域就是「数据段」。如下图,数据 1 和 2 的区域位置:

    • 数据 1 被存放到 0x100 位置;
    • 数据 2 被存放到 0x104 位置;
      注意,数据和指令是分开区域存放的,存放指令区域的地方称为「正文段」。

    shuyu

    编译器会把a = 1 + 2翻译成 4 条指令,存放到正文段中。如图,这 4 条指令被存放到了 0x200 ~ 0x20c 的区域中:

    • 0x200 的内容是 load 指令将 0x100 地址中的数据 1 装入到寄存器 R0
    • 0x204 的内容是 load 指令将 0x104 地址中的数据 2 装入到寄存器 R1
    • 0x208 的内容是 add 指令将寄存器 R0 和 R1 的数据相加,并把结果存放到寄存器 R2
    • 0x20c 的内容是 store 指令将寄存器 R2 中的数据存回数据段中的 0x108 地址中,这个地址也就是变量 a 内存中的地址;
      编译完成后,具体执行程序的时候,程序计数器会被设置为 0x200 地址,然后依次执行这 4 条指令。

    上面的例子中,由于是在 32 位 CPU 执行的,因此一条指令是占 32 位大小,所以你会发现每条指令间隔 4 个字节。

    而数据的大小是根据你在程序中指定的变量类型,比如 int 类型的数据则占 4 个字节,char 类型的数据则占 1 个字节。

  • 相关阅读:
    N种内部类(成员内部类、静态内部类、局部内部类、匿名内部类)
    【Linux】Linux+Nginx部署项目
    WPF -- MVVM框架 - CommunityToolkit.Mvvm
    报错AttributeError: ‘DataFrame‘ object has no attribute ‘ix‘
    Hashmap经典高频问题,让面试成为你的主场
    数据规范化与数据离散化
    java 字符串替换
    Kotlin内置函数let、run、apply的区别
    基于node.js自动写入版本号解决前端vue或webpack项目因分包发版引起的报错问题
    Chromium VIZ架构详解
  • 原文地址:https://blog.csdn.net/weixin_44988085/article/details/127806092