机器代码:二进制机器码究竟是如何被CPU执行的?
V8运行时环境:准备好运行时环境,就可以执行JavaScript代码了。在执行代码时,V8需要先将JavaScript编译为字节码,然后再解释执行字节码,或者将字节码需要优化的字节码编译成二进制,并直接执行二进制代码。
将源码编译成机器码
我们以一段C代码为例,来看一下代码被编译成二进制可执行程序之后,是如何被CPU执行的。
在这段代码中,只是做了非常简单的加法操作,将x和y两个数字相加得到z,并返回结果z。
int main()
{
int x = 1;
int y = 2;
int z = x + y;
return z;
}
CPU并不能直接执行这段C代码,而需要对其进行编译,转换为二进制的机器码。
先通过GCC编译器将这段C代码编译成二进制文件:gcc -O0 -o code_prog code.c
再将编译出来的code_prog程序进行反汇编,这样我们就可以看到二进制代码和对应的汇编代码。你可以使用objdump的完成该任务,命令如下所示:objdump -d code_prog
最后编译完成的机器码如下图:
左边就是编译生成的机器码,在这里它是使用十六进制来展示的,因为十六进制方便阅读。
中间的部分是汇编代码,汇编代码采用助记符(memonic)来编写程序。例如原本是二进制表示的指令,在汇编代码中可以使用单词来表示,比如mov、add就分别表示数据的存储和相加。汇编语言和机器语言是一一对应的。
通常我们将汇编语言编写的程序转换为机器语言的过程称为“汇编”;反之,机器语言转化为汇编语言的过程称为“反汇编”,比如上图就是对code_prog进程进行了反汇编操作。
CPU是怎么执行程序的?
不过为了分析程序的执行过程,我们还需要理解典型的计算机系统的硬件组织结构,具体你可以参看下图:
由图可以看出,它主要是由CPU、主存储器、各种IO总线,还有一些外部设备,诸如硬盘、显示器、USB等设备组成的。
首先,在程序执行之前,我们的程序需要被装进内存。
什么是内存?
你可以把内存看成是一个快递柜,比如当你需要寄件的时候,你可以打开快递柜中的第100号单元格,并存放你的物品,有时候你会收到快递,提示你在快递柜的105号单元格中,你就可以打开105号单元格取出的你的快递。
这里有三个重要的内容,分别是快递柜、快递柜中的每个单元格的编号、操作快递柜的人,你可以把它们对比成计算机中的内存、内存地址和CPU。
CPU可以通过指定的内存地址,从内存读取数据或写入数据。
另外,内存是一个临时储存数据的设置,断电之后,数据都会被消失。
内存中的每个存储空间都有其对应的独一无二的地址,内存和地址的关系如下图:
文章开头的那段c代码,编译为可执行文件,包含的二进制机器码被加载进了内存,内存中的二进制代码便都有了自己的对应地址,如图所示:
有时候一条指令只需要一个字节就可以了,但是有时候一条指令却需要多个字节。
三个过程称为一个CPU时钟周期。CPU是永不停歇的,当它执行完成一条指令之后,会立即从内存中取出下一条指令,接着分析该指令,执行该指令,CPU一直重复执行该过程,直至所有的指令执行完成。
CPU是如何知道要取出内存中的哪条指令呢?
CPU中有一个PC寄存器。它保存了将要执行的指令地址。当代码被装载到内存中,系统会将第一条指令写入PC寄存器中。
PC寄存器的指令取出来之后,系统会做两件事:
第一件事是将下一条指令的地址更新到PC寄存器中
更新了PC寄存器之后,CPU就会立即做第二件事,那就是分析该指令
通用寄存器
通用寄存器是CPU中用来存放数据的设备,不同处理器中寄存器的个数也是不一样的,之所以要通用寄存器,是因为CPU访问内存的速度很慢,所以CPU就在内部添加了一些存储设备,这些设备就是通用寄存器。
你可以把通用寄存器比喻成是你身上的口袋,内存就是你的背包,而硬盘则是你的行李箱,要从背包里面拿物品会比较不方便,所以你会将常用的物品放进口袋。你身上口袋的个数通常不会太多,容量也不会太大,而背包就不同了,它的容量会非常大。
通用寄存器容量小,读写速度快,内存容量大,读写速度慢。
几种常用的执行类型:
第一种是加载的指令:其作用是从内存中复制指定长度的内容到通用寄存器中,并覆盖寄存器中原来的内容。你可以参看下图:
第二种存储的指令:和加载类型的指令相反,其作用是将寄存器中的内容复制内存某个位置,并覆盖掉内存中的这个位置上原来的内容。你可以参看下图:
第三种是更新指令:其作用是复制两个寄存器中的内容到ALU中,也可以是一块寄存器和一块内存中的内容到ALU中,ALU将两个字相加,并将结果存放在其中的一个寄存器中,并覆盖该寄存器中的内容。具体流程如下图所示:
还有跳转指令:从指令本身抽取出一个字,这个字是下一条要执行的指令的地址,并将该字复制到PC寄存器中,并覆盖掉PC寄存器中原来的值。那么当执行下一条指令时,便会跳转到对应的指令了。
分析一段汇编代码的执行流程
在C程序中,CPU会首先执行调用main函数,在调用main函数时,CPU会保存上个栈帧上下文信息和创建当前栈帧的上下文信息,主要是通过下面这两条指令实现的:
`pushq %rbp
movq %rsp, %rbp`
第一条指令pushq %rbp,是将rbp寄存器中的值写到内存中的栈区域。第二条指令是将rsp寄存器中的值写到rbp寄存器中。
...
此文章为5月Day16学习笔记,内容来源于极客时间《图解 Google V8》,日拱一卒,每天进步一点点💪💪
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。