• 指令和运算


    指令和运算 - 计算机指令:高级语言是如何翻译成计算机指令

    计算机组成原理目录:https://www.cnblogs.com/binarylei/p/12585607.html

    本小节聚焦在高级语言是如何翻译成机器码,以及机器码的格式。之后的两小节则深入讲解 CPU 如何执行条件、循环、函数、递归这些完整的语句。

    1. 机器码 vs 计算机指令

    计算机只认知 "0" 和 "1",那我们每天用高级语言的程序,最终都会是怎么变成一串串 "0" 和 "1" 的?这一串串 "0" 和 "1" 又是怎么在 CPU 中处理的?今天,我们就来仔细介绍一下,“机器码”和“计算机指令”到底是怎么回事。

    我们常说,CPU(Central Processing Unit,中央处理器) 就是计算机的大脑。

    • 从硬件的角度:CPU 就是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑。
    • 从软件的角度:CPU 就是一个执行各种计算机指令(Instruction Code)的逻辑机器。这里的计算机指令,就好比一门 CPU 能够听得懂的语言,我们也可以把它叫作机器语言(Machine Language)

    不同的 CPU 能够听懂的语言不太一样。比如 Intel CPU 和 ARM CPU 各自支持的语言,就是两组不同的计算机指令集(Instruction Set)

    2. 从编译到汇编,代码怎么变成机器码

    了解了计算机指令和计算机指令集,接下来我们来看看,平时编写的代码,到底是怎么变成一条条计算机指令,最后被 CPU 执行的呢?我们拿一小段真实的 C 语言程序来看看。

    // test.c
    int main()
    {
      int a = 1; 
      int b = 2;
      a = a + b;
    }
    

    要让这段程序在一个 Linux 操作系统上跑起来,我们需要将 C 语言翻译计算机指令:

    • 编译(Compile):把整个程序翻译成一个汇编语言(ASM,Assembly Language)
    • 汇编(Assembly):针对汇编代码,再用汇编器(Assembler)翻译成机器码(Machine Code)。这些机器码由 "0" 和 "1" 组成的机器语言表示。这一条条机器码,就是一条条的计算机指令。这样一串串的 16 进制数字,就是我们 CPU 能够真正认识的计算机指令。

    在 Linux 操作系统上,我们可以简单地使用 gcc 和 objdump 这样两条命令,把对应的汇编代码和机器码都打印出来。

    gcc -g -c test.c
    objdump -d -M intel -S test.o
    

    可以看到,左侧有一堆数字,这些就是一条条机器码;右边有一系列的 push、mov、add、pop 等,这些就是对应的汇编代码。一行 C 语言代码,有时候只对应一条机器码和汇编代码,有时候则是对应两条机器码和汇编代码。汇编代码和机器码之间是一一对应的。

    test.o:     file format elf64-x86-64
    Disassembly of section .text:
    0000000000000000 <main>:
    int main()
    {
       0:   55                      push   rbp
       1:   48 89 e5                mov    rbp,rsp
      int a = 1; 
       4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
      int b = 2;
       b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
      a = a + b;
      12:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
      15:   01 45 fc                add    DWORD PTR [rbp-0x4],eax
    }
      18:   5d                      pop    rbp
      19:   c3                      ret  
    

    从高级语言到汇编代码,再到机器码,就是一个日常开发程序,最终变成了 CPU 可以执行的计算机指令的过程。

    图1:从高级语言到汇编代码,再到机器码

    3. 解析指令和机器码

    日常用的 Intel CPU,有 2000 条左右的 CPU 指令。不过一般来说,常见的指令可以分成五大类。

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

    我们说过,不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和不同的机器码。为了方便你快速理解这个机器码的计算方式,我们选用最简单的 MIPS 指令集,来看看机器码是如何生成的。(intel 指令集有 2000 条左右,而且是变长,不太好理解)

    3.1 MIPS 指令集

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

    图3:MIPS 指令集格式

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

    • R 指令:一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
    • I 指令:则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
    • J 指令:跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。

    3.2 示例:add 指令

    add $t0,$s2,$s1
    

    add 对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器 s1 的地址是 17,rt 代表第二个寄存器 s2 的地址是 18,rd 代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就变成了一个 MIPS 的加法指令。

    为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。

    图4:add 指令表示方法
      000000 10001 10010 01000 00000 100000     // add MIPS指令格式
    = 0000 0010 0011 0010 0100 0000 0010 0000   // add 二进制表示
    = 0X02324020                                // add 十六进制表示
    

    4. 总结延伸

    这一讲里,我们看到了一个 C 语言程序,是怎么被编译成为汇编语言,乃至通过汇编器再翻译成机器码的。

    除了 C 这样的编译型的语言之外,不管是 Python 这样的解释型语言,还是 Java 这样使用虚拟机的语言,其实最终都是由不同形式的程序,把我们写好的代码,转换成 CPU 能够理解的机器码来执行的。只是解释型语言,是通过解释器在程序运行的时候逐句翻译,而 Java 这样使用虚拟机的语言,则是由虚拟机对编译出来的中间代码进行解释,或者即时编译成为机器码来最终执行。

    然而,单单理解一条指令是怎么变成机器码的肯定是不够的。接下来的两个小节,我会深入讲解,包含条件、循环、函数、递归这些语句的完整程序,是怎么在 CPU 里面执行的。

    推荐阅读:

    1. 《计算机组成与设计:软 / 硬件接口》第 5 版的 2.17 小节:Intel CPU 的指令集

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    [opentwebst]一个简单的登陆脚本
    opentwebst一个ie自动化操作测试软件-功能强大
    给X9DRL-iF双路服务器主板刷BIOS
    在ubuntu16下面通过kvm+lvm安装ubuntu16的虚拟机
    ubuntu16安装KVM
    PowerShell全自动分配CPU
    在ubuntu16编译安装nginx-1.10.2(full)完全自带组件
    将博客搬至CSDN
    乌邦图ubuntu配置iptables的NAT上网
    LVM增大和减小ext4、xfs分区
  • 原文地址:https://www.cnblogs.com/binarylei/p/12603230.html
Copyright © 2020-2023  润新知