Skip to main content

09 机器代码:二进制机器码如何被CPU执行

V8 首先需要将 JavaScript 编译成字节码或者二进制代码,然后再执行。二进制代码被 CPU 执行时,在编译流水线中的位置:

将源码编译成机器码

int main()
{
int x = 1
int y = 2;
int z = x + y;
return z;
}

先通过 GCC 编译器将这段 C 代码编译成二进制文件:

gcc -O0 -o code_prog code.c

接下来再将编译出来的 code_prog 程序进行反汇编,就可以看到二进制代码和对应的汇编代码:

objdump -d code_prog

左边就是编译生成的机器码,使用十六进制来展示,每一行都是一个指令,该指令可以让 CPU 执行指定的任务。

中间的部分是汇编代码,采用助记符(memonic)来编写程序,原本是二进制表示的指令,在汇编代码中可以使用单词来表示。汇编语言和机器语言是一一对应的。

一堆指令按照顺序集合在一起就组成了程序,程序的执行,本质上就是 CPU 按照顺序执行这一堆指令的过程。

CPU 是怎么执行程序的

典型的计算机系统的硬件组织结构:

程序装进内存

在程序执行之前,程序需要被装进内存。CPU 可以通过指定内存地址,从内存中读取数据,或者往内存中写入数据,有了内存地址,CPU 和内存就可以有序地交互。内存中的每个存储空间都有对应的独一无二的地址,而且地址是按照顺序排放的。

当二进制代码被加载进了内存后,内存中的每条二进制代码便都有了自己对应的地址:

取出指令、分析指令、执行指令

一旦二进制代码被装载进内存,CPU 便可以从内存中取出一条指令,然后分析该指令,最后执行该指令。把取出指令、分析指令、执行指令这三个过程称为一个 CPU 时钟周期。

CPU 是怎么知道要取出内存中的哪条指令的:

CPU 中有一个 PC 寄存器,它保存了将要执行的指令地址,当二进制代码被装载进了内存之后,系统会将二进制代码中的第一条指令的地址写入到 PC 寄存器中,到了下一个时钟周期时,CPU 便会根据 PC 寄存器中的地址,从内存中取出指令。

PC 寄存器中的指令取出来之后,系统会将下一条指令的地址更新到 PC 寄存器中。

同时 CPU 会立即分析该指令,并识别出不同的类型的指令,以及各种获取操作数的方法。

通用寄存器是 CPU 中用来存放数据的设备,不同处理器中寄存器的个数也是不一样的,之所以要通用寄存器,是因为 CPU 访问内存的速度很慢,所以 CPU 就在内部添加了一些存储设备,这些设备就是通用寄存器。通用寄存器容量小,读写速度快,内存容量大,读写速度慢。


通用寄存器通常用来存放数据或者内存中某块数据的地址,还会将某些专用的数据或者指针存储在专用的通用寄存器中 ,比如 rbp 寄存器通常是用来存放栈帧指针的,rsp 寄存器用来存放栈顶指针的,PC 寄存器用来存放下一条要执行的指令等。

几种常用的指令类型:

**加载的指令,**从内存中复制指定长度的内容到通用寄存器中,并覆盖寄存器中原来的内容。

**存储的指令,**将寄存器中的内容复制内存某个位置,并覆盖掉内存中的这个位置上原来的内容。

**更新指令,**复制两个寄存器(或一块寄存器和一块内存)中的内容到 ALU 中,ALU 将两个字相加,并将结果存放在其中的一个寄存器中,并覆盖该寄存器中的内容。

**跳转指令,**从指令本身抽取出一个字,这个字是下一条要执行的指令的地址,并将该字复制到 PC 寄存器中,并覆盖掉 PC 寄存器中原来的值。

IO 读 / 写指令,从一个 IO 设备中复制指定长度的数据到寄存器中,也可以将一个寄存器中的数据复制到指定的 IO 设备。