美文网首页
5. 计算机指令:让我们试试用纸带编程

5. 计算机指令:让我们试试用纸带编程

作者: Jason_Shu | 来源:发表于2019-12-14 21:15 被阅读0次

    早期计算机程序是通过「打孔卡」来实现的,人们在特定位置上打洞或者不打洞,来表示“0”或者“1”。

    打孔卡

    那为什么我们每天用高级语言的程序,最终是怎么变成一串串“0”和“1”的?这一串串“0”和“1”是怎么在CPU中处理的?今天来说说「机器码」和「计算机指令」。

    在软硬件接口中,CPU帮我们做了什么事?

    CPU,又名「中央处理器」,是计算机的电脑。

    硬件的角度,是一个超大规模集成电路,通过电路实现加法和乘法等各种处理逻辑。

    软件的角度,CPU是一个执行各种计算机指令的逻辑机器。这里的「计算机指令」好比一门CPU能听得懂的语言,我们也称之为“机器语言”。

    不同的CPU能听懂的语言也不同。比如,「个人电脑的Intel的CPU」和「苹果手机ARM的CPU」就是不同的CPU,这两者听懂的语言也不同。类似这样两种CPU各自支持的语言,就是两组不同的计算机指令集

    一个计算机程序,不可能只有一条指令,而是有成千上万条指令。但是CPU不能一直放着这些指令,所以计算机程序一般是存储在存储器里的。这种程序指令存储在存储器里的计算机,我们就叫做存储程序计算机

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

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

    这段代码,要在Linux操作系统上跑起来,我们首先需要把整个程序翻译成汇编语言的程序。这个过程,我们称之为「编译成汇编代码」。

    针对汇编代码,我们可以再用「汇编器」翻译成「机器码」。这些机器码是由“0”和“1”组成的机器语言表示。这一条条机器码,也就是「计算机指令」。这一串串16进制数字,就是CPU能够识别的计算机指令。

    在Linux操作系统上,运行gcc和objdump这两条指令。

    $ gcc -g -c test.c
    $ objdump -d -M intel -S test.o
    
    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    
    

    我们可以看到,左侧一对数字,那就是「机器码」。右侧的push,mov,add这些是「汇编代码」。你可能会问,我们实际在用GCC(GUC编译器套餐)编译器的时候,可以直接把「代码」编译成「机器码」呀,为啥还要先编译成「汇编代码」?原因是你看这些机器码看不懂,你看「汇编代码」还能猜出一些含义。

    因为「汇编代码」就是「给程序员看的机器码」。也正是这样,「汇编代码」和「机器码」是一一对应的。

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

    解析指令和机器码

    接着我们看看一行行「汇编代码」和「机器码」都是啥意思。一般来说,常见的指令分为五大类。

    第一类是算术类指令。加减乘除,在CPU层面,都会变成一个个算术类指令。

    第二类是数据传输类指令。给「变量赋值」,在内存里面「读写数据」,用的是数据传输类指令。

    第三类是逻辑类指令。逻辑上的与或非。

    第四类是条件分支类指令。日常写的「if」「else」。

    第五类是无条件跳转类指令。写一些大一点的程序,需要调用函数,在「调用函数」的时候就是无条件跳转类指令。

    接着我们看看,「汇编代码」如何变成「机器码」的。

    上述说过,不同的CPU对应不同的「计算机指令集」,也对应着不同的汇编语言和不同的机器码。选用一个简单的MIPS指令集,来看看「机器码」是如何生成的。

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

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

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

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

    add $t0,$s2,$s1
    

    我以一个简单的加法算术指令 add t0,s1, $s2, 为例,给你解释。为了方便,我们下面都用十进制来表示对应的代码。

    对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器 s1 的地址是 17,rt 代表第二个寄存器 s2 的地址是 18,rd 代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就变成了一个 MIPS 的加法指令。为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。

    回到开头我们说的打孔带。如果我们用打孔代表 1,没有打孔代表 0,用 4 行 8 列代表一条指令来打一个穿孔纸带,那么这条命令大概就长这样:

    如果你想一起学习这门课,可以扫下面的二维码购买:


    相关文章

      网友评论

          本文标题:5. 计算机指令:让我们试试用纸带编程

          本文链接:https://www.haomeiwen.com/subject/mjkhnctx.html