V8这个概念大家都不陌生了,那么你动手编译过V8源码吗?编译后有尝试去了解V8背后的一些概念吗?如果没有,那么也不用心慌,下文将跟大家一一解释这些东西。在编译V8之前我们先要了解一个东西-构建系统

1、构建系统

1.1、构建系统是啥?

写惯前端的童鞋可能不是很明白这个东西是干啥用的?但是其实平时你都会接触到,只是概念不同而已。前端我们一般称其为打包构建,类似工具诸如webpack、parcel做的事情。其实最后的目标都是想得到一些目标性的文件。这里可以简单地提及一下软件工程中的构建系统的历史。

构建系统的需求是随着软件规模的增大而提出的。如果只是做简单的demo,通常代码量比较小,编写的源代码只有几个文件。比如你编写了一段代码放入helloworld.cpp文件中,要编译这段代码,只需要执行以下命令:

g++ helloworld.c -o helloworld

当软件规模逐渐增加,这时可能有几十个源代码文件,而且有了模块划分,有的要编译成静态库,有的要编译成动态库,最后链接成可执行代码,这时命令行方式就捉襟见肘,需要一个构建系统。常见的构建系统有GNU Make。需要注意的是,构建系统并不是取代gcc这样的工具链,而是定义编译规则,最终还是会调用工具链编译代码。

当软件规模进一步扩大,特别是有多平台支持需求的时候,编写GNU Makefile将是一件繁琐和乏味的事情,而且极容易出错。这时就出现了生成Makefile的工具,比如CmakeAutoMake等等,这种构建系统称作元构建系统(meta build system)。在Linux上软件仓库的概念还没有普及的时候,通常我们安装软件的步骤是:

./configure
make
make install

第一步就是调用一些自动化工具,根据系统环境(系统的版本众多,软件安装情况也不一样),生成GNU Makefile。然后第二步才使用gcc或者g++命令去编译所有文件,最后一步便是将所有文件链接起来成可执行命令并安装到系统的某个指定目录。

一般后两个步骤都是比较固化的,能提高工作效率的也就是在第一步了。于是V8团队针对自己的项目特点,撸了一个叫做GYP(Generate Your Projects)的构建系统,后面你要是看到node-gyp其实就是基于这个做的js版本。不过后面GYP被v8团队废弃掉,改用GN(Generate Ninja)构建系统。二者的区别不是本文重点,有兴趣的童鞋可以查看这篇文章: chromium中的GN构建系统。

有意思的是尽管v8彻底废弃掉了GYP,但是nodejs仍然在使用GYP,这个R大在创建deno项目的时候有提及到:Design Mistakes in Node。

1.1.1、GN构建系统简介

GN(Generate Ninja)是chromium project用来取代GYP的新工具,由于GN是用C++编写,比起用 python写的GYP快了很多,GN新的DSL的语法也被认为是比较好写以及维护的。

在v8项目的根目录下有个.gn文件,内容如下(去掉所有注释了):

import("//build/dotfile_settings.gni")
buildconfig = "//build/config/BUILDCONFIG.gn"
check_targets = []
exec_script_whitelist = build_dotfile_settings.exec_script_whitelist + []

我们关注buildconfig这个配置。.gn所在的目录会被GN工具认定是项目的根目录,.gn的内容基本就是用buildconfig来指定build config的位置,其中//build//config/BUILDCONFIG.gn是相对于项目根目录下路径的配置文件。

但是你会发现现在v8源码目录下并没有叫做build的目录,这个目录要咋生成呢?这些知识我们会在稍后的编译v8代码中提及。

假设现在你有build目录了,我们找到BUILDCONFIG.gn文件,文件里面会根据系统和平台设置对应的编译工具链:

... ...if (custom_toolchain != "") {set_default_toolchain(custom_toolchain)
} else if (_default_toolchain != "") {set_default_toolchain(_default_toolchain)
}... ...

比如得到的_default_toolchain值为:_default_toolchain = "//build/toolchain/linux:clang_x86,那么你在build/toolchain/linux目录下的BUILD.gn可以找到这么一个配置:

clang_toolchain("clang_x86") {# Output linker map files for binary size analysis.enable_linker_map = truetoolchain_args = {current_cpu = "x86"current_os = "linux"}
}

因为GN没有内建的toolchain规则,toolchain里的各种tool例如 cc,cxx,link等必须自己指定,指定的文件是build/toolchain/gcc_toolchain.gni文件,在文件中我们可以看到GN给定义的一些动作:

tool("cc") {depfile = "{{output}}.d"precompiled_header_type = "gcc"command = "$cc -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}${extra_cppflags}${extra_cflags} -c {{source}} -o {{output}}"depsformat = "gcc"description = "CC {{output}}"outputs = ["$object_subdir/{{source_name_part}}.o",]
}

最后项目根目录下会有一个BUILD.gn的文件,指定生成可执行文件的指令,比如:

v8_executable("v8_hello_world") {sources = ["samples/hello-world.cc",]configs = [# Note: don't use :internal_config here because this target will get# the :external_config applied to it by virtue of depending on :v8, and# you can't have both applied to the same target.":internal_config_base",]deps = [":v8",":v8_libbase",":v8_libplatform","//build/win:default_exe_manifest",]
}

这样一套完整的GN构建系统便完成了。

1.1.2、Ninja构建系统

有了GN,为啥还要Ninja呢?刚才我们知道GN的英文意思是Generator Ninja,可见GN生成的东西并不是我们最终GNU Makefile形式。而Ninja才是最后生成Makefile的终极法器。Ninja 作为一个新型的编译工具,小巧而又高效,据谷歌官方的说法是速度有了好几倍的提升。

这个时候我们还没有生成任何的Ninja文件,需要我们使用GN命令去生成:

gn args out/foo

这下子你在out/foo下就可以看到好多ninja文件:

Ninja使用build.ninja文件来定义构建规则,和Makefile里的元编程不同,build.ninja几乎是完全静态的,动态生成依赖其他工具,如gn或者CMake。

build.ninja

build.niinja相当于ninja的makefile,一个简单的build.ninja文件如下,分为rule和dependency两部分。

phony: 可以创建其他target的别名。

default: 如果没有在命令行中指定target,可以使用default来指定默认的target。

pools: 为了支持并发作业,Ninja还支持pool的机制,和用-j并行模式一样。

Make vs Ninja Performance Comparison将Ninja和Make进行了测试对比。

2、编译并测试V8代码

接下来我们开始进行v8代码的编译操作。官网的文档给的已经很齐全了,这里只是再简单说一下,并提及一些官网没有给出的基本知识。

2.1、下载v8代码

这一步注意了,不要直接从v8仓库使用git clone命令下载代码,这样下载下来的代码是无效的,会缺失很多东西,要使用官方提供的工具depot_tools

整个步骤汇总如下:

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=$PATH:/path/to/depot_tools
gclient config https://chromium.googlesource.com/v8/v8
gclient sync
mkdir ~/v8
cd ~/v8
fetch v8
cd v8

2.2、编译v8代码

编译v8代码官网同样给的很详细:传送门,这里总结一下而已,有两种编译方式

2.2.1、超便捷方式

使用gm这个集成所有为一体的python脚本可以几个命令就搞定:

alias gm=/path/to/v8/tools/dev/gm.py
gm x64.release
gm x64.release.check

2.2.2、手动编译方式

按照我们之前说的流程,我们需要使用GN去生成ninja文件,再生成makefile,最后才是编译,因此:

可以使用gn args out/foo或者gn gen out/foo --args='is_debug=false target_cpu="x64" v8_target_cpu="arm64" use_goma=true'来生成ninja文件。

这一行命令官网没有详细解释,我在这里解释一下:

gn args out/foo => 通过参数形式指定输出目录,这个命令会弹出文本让你配置参数
gn gen out/foo => 指定GN构建输出的目录, 可以指定参数: --args='is_debug=false target_cpu="x64" v8_target_cpu="arm64" use_goma=true',这个命令不会弹出文本窗让你配置
gn args out/foo --list => 查看这个构建输出目录当时配置的参数

如果嫌上面的方式麻烦,那么v8还提供了另外一个脚本来集成这些步骤:v8gen,命令如下:

alias v8gen=/path/to/v8/tools/dev/v8gen.py
v8gen -b 'V8 Linux64 - debug builder' -m client.v8 foo

v8gen的原理是借助mb_config.pyl文件。根据master配置(-m)和builder配置(-b)来生成编译文件,我们在mb_config.pyl找到对应的配置:

最后一个参数foo是指定生成的二级目录,默认一级目录是out.gn,如下:

你也可以使用默认配置,直接v8gen foo

接下去使用ninja来编译:

ninja -C out/foo

如果想要指定生成指定目标则:

ninja -C out/foo d8

上述编译正常会报错:goma/gomacc: No such file or directory。因为我们本地没有安装goma,所以想要正常编译下去,还需要安装一下goma,goma是什么东西呢?从官网上看,它是一个辅助编译加速的工具,详细可以参考:goma

3、编译单个引用到v8库的C++文件

除了上述整体v8工程编译,如果你想利用v8编译单个文件的话,比如在官网提到的编译Hello.cc中使用到了g++命令,对于g++命令有些参数是你必须了解的,这里整理了一份,请参考:

g++ -I. -Iinclude samples/hello-world.cc -o hello_world -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++0x

G++命令解释如下:

-std=决定使用的语言标准,当编译C和C++的时候该选择支持配置。上述命令中的`c++0x`表示:语言标准使用即将发布的ISO c++ 0x标准的工作草案。此选项支持可能包含在c++ 0x中的实验性特性。工作草案在不断地变化,如果GCC的未来版本不属于c++ 0x标准,那么由这个标志启用的任何特性都可能被删除。更多标准请参考:[g++](https://linux.die.net/man/1/g++)-pthread使用POSIX线程库添加对多线程的支持。此选项为预处理器和链接器设置标志。它不影响编译器生成的目标代码的线程安全性,也不影响与其提供的库的线程安全性。这些是特定于HP-UX的标志。-I dir将目录dir添加到要搜索头文件的目录列表中。在系统标准包含目录之前,搜索由**-I**指定的目录。如果目录*dir*是标准的系统包含目录,则忽略该选项,以确保不会破坏系统目录的默认搜索顺序和对系统头文件的特殊处理。如果*dir*以"="开头,则"="将被sysroot前缀替换。-o file
指定输出文件。这与将file指定为cpp的第二个非选项参数相同。gcc 对第二个非选项参数的有另一种解释,因此必须使用-o指定输出文件-llibrary
-l library链接时搜索名为library的库。(第二种指定库文件的方式仅适用于POSIX遵从性,不建议使用。)在命令中编写这个选项的位置会有所不同;链接器按照指定的顺序搜索和处理库和目标文件。因此,`foo.o -lz bar.o`是在文件foo.o之后搜索库z。但在bar.o之前。如果bar.o是引用到了z库中的函数,这些函数是不能被加载。链接器搜索库的标准目录列表,实际上是一个名为`liblibrary.a`的文件。然后链接器使用这个文件,就好像它是通过名称精确指定的一样。搜索的目录包括几个标准系统目录,以及您使用-L指定的任何目录。通常以这种方式找到的文件是库文件——其成员是目标文件的归档文件。链接器通过扫描成员来处理存档文件,这些成员定义了到目前为止已经引用但尚未定义的符号。但是,如果找到的文件是一个普通的对象文件,则以通常的方式链接它。
-Ldir添加`dir`目录到搜索目录列表中去供`-l`使用

这样上述命令想必一目了然了吧

4、v8引擎基本概念简述

在[译文]V8学习的高级进阶完整详细地介绍了很多概念,这里只是再把这些概念简化掉,让大家的记忆更加深刻。

4.1、isolate

这个概念在[译文]V8学习的高级进阶没有提及到,它表示的一个独立的V8虚拟机,拥有自己的堆栈。所以才取名isolate,意为“隔离”。在v8中使用以下语法进行初始化:

Isolate* isolate = Isolate::New(create_params);

4.2、handle

handle是指向对象的指针,在V8中,所有的对象都通过handle来引用,handle主要用于V8的垃圾回收机制。在 V8 中,handle 分为两种:持久化 (Persistent)handle 和本地 (Local)handle,持久化 handle 存放在堆上,而本地 handle 存放在栈上。比如我要使用本地句柄,句柄指向的内容是一个string,那么你要这么定义:

Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World'", NewStringType::kNormal).ToLocalChecked();

鉴于一个个释放Handle比较麻烦,v8又提供了HandleScope来批量处理,你可以在handle之前声明好:

HandleScope handle_scope(isolate);

4.3、context

context 是一个执行器环境,使用 context 可以将相互分离的 JavaScript 脚本在同一个 V8 实例中运行,而互不干涉。在运行 JavaScript 脚本是,需要显式的指定 context 对象。创建上下文,需要这样:

// 创建一个上下文
Local<Context> context = Context::New(isolate);// 进入上下文编译和运行脚本
Context::Scope context_scope(context);

4.4、V8的数据类型

由于 C++ 原生数据类型与 JavaScript 中数据类型有很大差异,因此 V8 提供了 Data 类,从 JavaScript 到 C++,从 C++ 到 JavaScrpt 都会用到这个类及其子类,比如:

String::NewFromUtf8(info.GetIsolate(), "version").ToLocalChecked()

这里的String便是V8的数据类型。再比如:

v8::Integer::New(info.GetIsolate(), 10);

4.5、对象模板和函数模板

这两个模板类用以定义 JavaScript 对象和 JavaScript 函数。我们在后续的小节部分将会接触到模板类的实例。通过使用 ObjectTemplate,可以将 C++ 中的对象暴露给脚本环境,类似的,FunctionTemplate 用以将 C++ 函数暴露给脚本环境,以供脚本使用。

最后

就此,对于v8的了解应该有了一定的雏形了,v8里面有很多重要的概念,想要继续深入的可以参考另外一篇v8的实际应用文章了:如何正确地使用v8嵌入到我们的C++应用中

参考

  1. chromium中的GN构建系统
  2. GYP,GN和Ninja
  3. depot_tools_tutorial(7) Manual Page
  4. GN Reference

nodejs missing script: dev_nodejs深入学习系列之v8基础篇相关推荐

  1. OpenCV学习系列教程第五篇:测试和提高代码的效率

    Opencv-Python学习系列教程第五篇 来自opencv-python官方学习文档,本人谨做翻译和注释,以及一些自己的理解 本文由作者翻译并进行代码验证,转载请注明出处~ 官方文档请参阅:htt ...

  2. 手摸手,带你用vue撸后台 系列一(基础篇) - 掘金

    完整项目地址:vue-element-admin 系列文章: 手摸手,带你用 vue 撸后台 系列一(基础篇) 手摸手,带你用 vue 撸后台 系列二(登录权限篇) 手摸手,带你用 vue 撸后台 系 ...

  3. QT/C++从新手到老手系列之QT基础篇-李浩林-专题视频课程

    QT/C++从新手到老手系列之QT基础篇-1620人已学习 课程介绍         本系列课程励志于带领你学习QT5/C++,从开发环境(QTCreator和VS2013两种)搭建到实际项目实战,从 ...

  4. 视频教程-Linux系列课程(基础篇)-Linux

    Linux系列课程(基础篇) 2年JavaEE开发 ,5年资深大数据开发大牛,曾就职于蓝点科技,擅长精准广告系统开发,精通Linux操作系统! 具备丰富的大数据研发和培训经验,熟练运用Hadoop和S ...

  5. 菜鸟学习笔记:Java基础篇7(包装类、时间相关类、文件类、异常处理类)

    菜鸟学习笔记:Java其他常用类 基本数据类型包装类 时间处理和文件处理相关类 Date时间类 SimpleDateFormat Calendar日历类 文件类 异常机制 异常的概念 Java异常处理 ...

  6. 菜鸟学习笔记:Java基础篇6(数组、字符串)

    菜鸟学习笔记:Java常用类(数组.字符串) 数组 概述 数组的定义 二维数组 数组查找和排序 查找 排序 数组运用--字符串 不可变字符序列(String) 可变字符序列(StringBuilder ...

  7. 菜鸟学习笔记:Java基础篇5(抽象类与接口、回调函数、内部类)

    菜鸟学习笔记:Java面向对象篇下 抽象类 接口 回调函数 内部类 成员内部类 匿名内部类 抽象类 通过前面知识的学习,抽象类这个概念应该不难理解,但比较容易和后面要说的接口混淆,而且在面试中也比较爱 ...

  8. 菜鸟学习笔记:Java基础篇4(面向对象三大特征)

    菜鸟学习笔记:Java面向对象篇中 继承 概念 方法重写(override) Object类 Super关键字 组合 final关键字补充 封装 访问控制符 多态 继承 概念 继续上一篇的例子: #m ...

  9. 菜鸟学习笔记:Java基础篇3(面向对象思想、程序执行过程内存分析、面向对象重要概念)

    菜鸟学习笔记:Java面向对象篇上 Java面向对象的思想 Java程序执行过程内存分析 Java垃圾回收机制 构造方法 方法重载(overload) static关键字 this关键字 Java面向 ...

最新文章

  1. 怎么判断网络回路_PLC控制回路故障的判断和检修方法与技巧!
  2. 在Android中取得当前进程名
  3. 【thymeleaf】th:text、[[]]、th:utext、[()]输出变量
  4. android CTS test
  5. onvif开发踩坑【二】鉴权失败
  6. 基于matlab的音频处理
  7. 班主任工作总结中职计算机网络,中职班主任工作总结(优秀篇).doc
  8. 移动终端基带芯片的基本架构介绍之一(arm框架的软硬件组合)
  9. 邮箱如何发邮件?邮箱怎么发邮件,掌握这几点,轻松搞定
  10. Java面试必问的HashMap,基础mysql笔试题及答案
  11. 网站seo诊断,网站seo诊断方法
  12. 基于AI的计算机视觉识别在Java项目中的使用(三) —— 搭建基于Docker的深度学习训练环境
  13. Web导出Excel总结
  14. 从自己的角度比较 天书夜读 和 寒江独钓
  15. 学生家乡网页设计作品静态HTML网页模板源码 广西旅游景点网页设计 大学生家乡主题网站制作 简单家乡介绍网页设计成品
  16. 深入理解Java虚拟机 - 字节码指令集
  17. 修改element-ui中时间选择器的样式
  18. 数据产品经理=数据+产品经理?
  19. 新改版代shua网源码 已对接艾K支付 附25套模板
  20. Android GPS学习笔记(三)定位数据如何从GPS芯片到应用层

热门文章

  1. Vue + Spring Boot 项目实战(七):前端路由与登录拦截器
  2. linux 创建用户和修改新增用户默认的家目录
  3. node环境搭建流程
  4. python 打开pdf文件_Python3检验pdf文件是否有效
  5. 开机一直转圈_电脑开机后网络一直转圈,程序也打不开?
  6. construct2 ajax,Construct2/3
  7. php 支付宝小程序授权登陆验签_星巴克“啡快”登陆支付宝小程序,让你“飞快”取到咖啡...
  8. python能做数据库开发吗_5分钟快速入门,用Python做SQLite数据库开发,附代码适合初学...
  9. bufferedreader读取中文乱码_python之pandas模块关于csv文件乱码问题解决
  10. oracle 存储过程打印语句,oracle学习之第一个存储过程:打印Hello World