计算机组成原理 - 指令
CPU 能处理的只有指令,我们写的程序归根结底就是指令,高级语言只有翻译成机器码,即计算机能够识别的指令,才能够被执行。
不同的 CPU 拥有不同的指令集,一般 PC 使用 Intel 的 CPU,iPhone 使用 ARM 的 CPU。所以如果在电脑上写个程序,装到手机上,一般无法运行,因为语言不通,而装到另一台电脑上,通常能正常运行,就是因为语言相通。
代码怎样变成机器码
来看段 C 代码,究竟如何变成机器码的。
1 | // test.c |
要在 Linux 上跑起来,首先需要编译成汇编语言,然后通过汇编器翻译成机器码。
通过 gcc 和 objdump 两个命令,来将对应的汇编和机器代码打印。
1 | gcc -g -c test.c |
可以看到,左侧有一堆数字,这些就是一条条机器码;右边有一系列的 push、mov、add、pop 等,这些就是对应的汇编代码。一行 C 语言代码,有时候只对应一条机器码和汇编代码,有时候则是对应两条机器码和汇编代码。汇编代码和机器码之间是一一对应的。
1 | test.o: file format Mach-O 64-bit x86-64 |
这里可以想象一下,高级语言转换成机器码,为什么要多出中间一步转换,即先转换成汇编代码,如果直接转换有哪些不妥?
CPU 如何解析机器码
当 CPU 拿到一条指令(一条机器码)的时候,就会去对应的指令集中看,这条指令是什么操作。
这里介绍一下最简单的 MIPS 指令集。
MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。
R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
我们常用的 Intel CPU ,大概 2000 左右个指令集,正对应了上图中五类操作。
- 算术类指令
- 数据传输类指令
- 逻辑类指令
- 条件分支类指令
- 无条件跳转指令
CPU 如何执行指令
还拿 Intel CPU 来说,里面差不多有几百亿个晶体管,通过电路不断切换运转起来。逻辑上,可以认为 CPU 其实就是由一堆寄存器组成的。而寄存器就是 CPU 内部,由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路。
触发器和锁存器,其实就是两种不同原理的数字电路组成的逻辑门。
N 个触发器或者锁存器,就可以组成一个 N 位(Bit)的寄存器,能够保存 N 位的数据。
一个 CPU 里面会有很多种不同功能的寄存器。这里介绍三种比较特殊的。
一个是 PC 寄存器,我们也叫指令地址寄存器。用来存放下一条需要执行的计算机指令的内存地址。
第二个是指令寄存器,用来存放当前正在执行的指令。
第三个是条件码寄存器,用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。
实际上,一个程序执行的时候,CPU 会根据 PC 寄存器里的地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。可以看到,一个程序的一条条指令,在内存里面是连续保存的,也会一条条顺序加载。
而有些特殊指令,比如 J 类指令,也就是跳转指令,会修改 PC 寄存器里面的地址值。这样,下一条要执行的指令就不是从内存里面顺序加载的了。事实上,这些跳转指令的存在,也是我们可以在写程序的时候,使用 if…else 条件语句和 while/for 循环语句的原因。
Title: 计算机组成原理 - 指令
Author: mjd507
Date: 2020-10-12
Last Update: 2024-01-27
Blog Link: https://mjd507.github.io/2020/10/12/Computer-Organization-3/
Copyright Declaration: This station is mainly used to sort out incomprehensible knowledge. I have not fully mastered most of the content. Please refer carefully.