图解 Google V8 # 10:机器代码:二进制机器码究竟是如何被CPU执行的?

简介: 图解 Google V8 # 10:机器代码:二进制机器码究竟是如何被CPU执行的?

说明

图解 Google V8 学习笔记



在编译流水线中的位置


在执行代码时,V8 需要先将 JavaScript 编译成字节码,然后再解释执行字节码,或者将需要优化的字节码编译成二进制,并直接执行二进制代码。


CPU执行二进制代码:

33ff45ebf5644c228e3fd46636527412.png


将源码编译成机器码


下面是一段 C 代码

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


先通过 GCC 编译器将这段 C 代码编译成二进制文件,编译出来的机器码


217a34a9df4b43ebbfaa103e18297150.png


每一行都是一个指令,该指令可以让 CPU 执行指定的任务。

  • 左边就是编译生成的机器码,用十六进制来展示,便于阅读
  • 中间的部分是汇编代码,汇编代码采用助记符(memonic)来编写程序


助记符(mnemonic)


   助记符(mnemonic)是便于人们记忆、并能描述指令功能和指令操作数的符号,助记符是表明指令功能的英语单词或其缩写。汇编语言由于采用了助记符号来编写程序,比用机器语言的二进制代码编程要方便些,在一定程度上简化了编程过程。汇编语言的特点是用符号代替了机器指令代码,而且助记符与指令代码一一对应,基本保留了机器语言的灵活性。使用汇编语言能面向机器并较好地发挥机器的特性,得到质量较高的程序。按指令作用对象来分,可分为伪指令和真指令(硬指令)。伪指令也就是作用于汇编程序的命令;真指令就是作用于真正处理器的命令。


   汇编:将汇编语言编写的程序转换为机器语言的过程


   反汇编:将机器语言转化为汇编语言的过程



CPU 是怎么执行程序的?


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


a4a37f9330ab42c993ca2a3678c790c3.png


首先,在程序执行之前,需要被装进内存。



什么是内存?


   内存(Memory)是计算机的重要部件,也称内存储器和主存储器,它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。


内存还是一个临时存储数据的设备,之所以是临时的存储器,是因为断电之后,内存中的数据都会消失。


内存中的每个存储空间都有其对应的独一无二的地址。


8b4bfb1d57404a34a51bc0e2951f8f9b.png


当二进制代码被加载进了内存后,同一条指令,使用相同的颜色来标记:


6e006381a4c946b6b650bae7fa6a80b1.png


二进制数据反汇编成一条条指令的形式:

310a4795d45d4c95a2419820fd41a7e6.png

CPU 便可以通过指定内存地址,从内存中取出一条指令,然后分析该指令,最后执行该指令,这三个过程称为一个 CPU 时钟周期



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


将混乱的二进制代码转换为有序的指令形式:


804599d0c1d24c70b800b22ee24d3701.png


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


   PC (program counter)是程序计数器的简称。PC 是一种特殊的寄存器,用于保存下一条待执行指令的存储地址。


PC 寄存器中的指令取出来之后,CPU 会做两件事:



1、将下一条指令的地址更新到 PC 寄存器中

93d212b881f24f1f8db2446210cf6fb6.png


2、分析该指令,并识别出不同的类型的指令,以及各种获取操作数的方法。



什么是通用寄存器?

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


通用寄存器跟内存的区别:


   通用寄存器容量小,读写速度快

   内存容量大,读写速度慢。


通用寄存器通常用来存放数据或者内存中某块数据的地址,把这个地址又称为指针。


某些专用的数据或者指针存储在专用的通用寄存器中:比如


   rbp 寄存器通常是用来存放栈帧指针的

   rsp 寄存器用来存放栈顶指针的

   PC 寄存器用来存放下一条要执行的指令



常用的指令类型

算术逻辑单元(arithmetic and logic unit) 是能实现多组算术运算和逻辑运算的组合逻辑电路,简称ALU。


1.加载的指令:其作用是从内存中复制指定长度的内容到通用寄存器中,并覆盖寄存器中原来的内容。


9463639bc2e44b75979533cac6b8e48c.png

  • 上图使用了 movl 指令,指令后面跟着的第一个参数是要拷贝数据的内存的位置,第二个参数是要拷贝到 ecx 这个寄存器。


  • 存储的指令:和加载类型的指令相反,其作用是将寄存器中的内容复制内存某个位置,并覆盖掉内存中的这个位置上原来的内容。

fcaf48627c22497a9b0e31d033ee5914.png


上图也是使用 movl 指令,movl 指令后面的 %ecx 就是寄存器地址,-8(%rbp) 是内存中的地址,这条指令的作用是将寄存器中的值拷贝到内存中。


3.更新指令:其作用是复制两个寄存器中的内容到 ALU 中,也可以是一块寄存器和一块内存中的内容到 ALU 中,ALU 将两个字相加,并将结果存放在其中的一个寄存器中,并覆盖该寄存器中的内容。


e84d23c3f3c440e4bb88f54fc603ade8.png

  • 上图里的 addl 指令,将寄存器 eax 和 ecx 中的值传给 ALUALU 对它们进行相加操纵,并将计算的结果写回 ecx。


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


0d979e49c8af4cb691a8cc4e36d58245.png



上图是通过 jmp 来实现的,jmp 后面跟着要跳转的内存中的指令地址。


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



CPU 是如何执行指令?


分析一段汇编代码的执行流程,以上面的 C 代码为例


1、在 C 程序中,CPU 会首先执行调用 main 函数,在调用 main 函数时,CPU 会保存上个栈帧上下文信息和创建当前栈帧的上下文信息,主要是通过下面这两条指令实现的:

pushq   %rbp
movq    %rsp, %rbp


  • 第一条指令:将 rbp 寄存器中的值写到内存中的栈区域
  • 第二条指令:将 rsp 寄存器中的值写到 rbp 寄存器中。


2、然后将 0 写到栈帧的第一个位置:

movl  $0, -4(%rbp)


3、接下来给 x 和 y 赋值:

movl  $1, -8(%rbp)
movl  $2, -12(%rbp)


  • 第一条指令:将常数值 1 压入到栈中
  • 第二条指令:将常数值 2 压入到栈中


4、x 的值从栈中复制到 eax 寄存器中

movl  -8(%rbp), %eax


5、再将内存中的 y 和 eax 中的 x 相加,相加的结果再保存在 eax 中

addl  -12(%rbp), %eax


6、CPU 会将结果保存到内存中

movl  %eax, -16(%rbp)


7、将结果 z 加载到 eax 寄存器中,会被默认作为返回值

movl  -16(%rbp), %eax


8、最后执行一些恢复现场的操作

popq  %rbp 
retq



总结


下面是来自王楚然同学的总结:


二进制代码装载进内存,系统会将第一条指令的地址写入到 PC 寄存器中。


读取指令:根据pc寄存器中地址,读取到第一条指令,并将pc寄存器中内容更新成下一条指令地址。


分析指令:并识别出不同的类型的指令,以及各种获取操作数的方法。


执行指令:由于cpu访问内存花费时间较长,因此cpu内部提供了通用寄存器,用来保存关键变量,临时数据等。指令包括加载指令,存储指令,更新指令,跳转指令。如果涉及加减运算,会额外让ALU进行运算。


指令完成后,通过pc寄存器取出下一条指令地址,并更新pc寄存器中内容,再重复以上步骤。









目录
相关文章
|
6月前
|
Linux
如何在linux中查看cpu信息、机器硬件型号
如何在linux中查看cpu信息、机器硬件型号
110 0
|
6月前
|
人工智能 前端开发 API
【代码吸猫】使用 Google MLKit 进行图像识别
【代码吸猫】使用 Google MLKit 进行图像识别
195 0
|
6月前
|
传感器 编解码 数据处理
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
131 0
|
2月前
|
调度
CPU调度器实现提示:针对特定体系结构代码【ChatGPT】
CPU调度器实现提示:针对特定体系结构代码【ChatGPT】
|
4月前
|
NoSQL Redis 开发工具
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
|
5月前
汇编语言(第四版) 实验一 查看CPU和内存,用机器指令和汇编指令编程
汇编语言(第四版) 实验一 查看CPU和内存,用机器指令和汇编指令编程
|
4月前
|
机器学习/深度学习 TensorFlow API
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
|
5月前
|
并行计算 异构计算 Python
python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
【6月更文挑战第3天】python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
574 4
|
6月前
|
Java Linux
Linux下如何定位最耗CPU的JAVA代码
Linux下如何定位最耗CPU的JAVA代码
78 0
|
6月前
|
Web App开发 人工智能 JavaScript
从零写一个基于油猴脚本的 Google 辅助插件(文末附完整代码)
这是一个关于如何使用JavaScript和油猴脚本为Google搜索结果添加快捷键的功能介绍。作者首先阐述了想通过快捷键选择搜索结果的需求,然后选择了油猴插件作为开发平台。实现步骤包括:获取搜索结果列表、在结果前添加序号以及监听键盘事件触发点击。最后,作者还扩展了通过快捷键平滑滚动页面的功能,并分享了完整代码的GitHub链接。
97 0
从零写一个基于油猴脚本的 Google 辅助插件(文末附完整代码)
下一篇
无影云桌面