学习 v8在我看来有两种比较好的方式,一种是通过分析 js 对象或者函数,比如分析 Promise 的实现,另一种方式就是通过分析 d8的源码来进行学习. d8实际上就是一个 v8的开发者工具,已通过 d8来对 js 进行调试和分析.这篇文章主要是通过分析 d8的实现来学习 v8.
在 c 和 c++ 中,大部分程序的入口都是main 函数,因此我们从 main 函数开始分析.
int main(int argc, char* argv[]) { return v8::Shell::Main(argc, argv); }
从 main 函数中我们可以看到, main 函数很简单只调用了 Shell 类的 Main 函数,所有的逻辑都是在 Shell 类里面实现的.Shell 类实际上就是 d8的主要部分.
...
// static
ShellOptions Shell::options;
...
int Shell::Main(int argc, char* argv[]) {
...
if (!SetOptions(argc, argv)) return 1;
...
v8::V8::InitializeICUDefaultLocation(argv[0], options.icu_data_file);
...
}
首先,Shell::Main 通过调用 SetOptions 函数解析命令行里面的参数并保存到Shell 类的静态成员变量 options 里面.然后通过相应的参数进行一系列的初始化工作.
InitializeICUDefaultLocation 函数主要是用来初始化 ICU 库的,默认会第一个参数是可执行文件路径,第二个参数是 icu 文件,如果为空,默认是 icudtl.dat 文件.
std::unique_ptr<platform::tracing::TracingController> tracing;
std::ofstream trace_file;
if (options.trace_enabled && !i::FLAG_verify_predictable) {
tracing_file.open(options.trace_path ? options.trace_path : "v8_trace.json");
...
platform::tracing::TraceBuffer* trace_buffer =
platform::tracing::TraceBuffer::CreateTraceBufferRingBuffer(
platform::tracing::TraceBuffer::kRingBufferChunks,
platform::tracing::TraceWriter::CreateJSONTraceWriter(trace_file));
tracing->Initialize(trace_buffer);
}
接下来会分析是否需要保存追踪日志,如果与指定日志路径就会将追踪日志保存到 指定的路径,如果没有就会保存在当前的目录下的 v8_tracing.json 文件中.创建好 tracing之后接下来就是创建 v8 platform 实例了.
g_platform = v8::platform::NewDefaultPlatform(
options.thread_pool_size, v8::platform::IdleTaskSupport::kEnabled,
in_process_stack_dumping, std::move(tracing));
g_default_platform = g_platform.get();
if (i::FLAG_verify_predictable) {
g_platform = MakePredictablePlatform(std::move(g_platform));
}
if (options.stress_delay_tasks) {
int64_t random_seed = i::FLAG_fuzzer_random_seed;
if (!random_seed) random_seed = i::FLAG_random_seed;
// If random_seed is still 0 here, the {DelayedTasksPlatform} will choose a
// random seed.
g_platform = MakeDelayedTasksPlatform(std::move(g_platform), random_seed);
}
从上面在这段代码可以看到参数不一样会创建不相同的 platform 实例,g_default_platform 是通过 NewDefaultPlatform() 创建的 DefaultPlatform, 通过 MakePredictablePlatform() 创建的是 PredictablePlatform, 通过 MakeDelayedTasksPlatform() 创建的是一个 DelayedTasksPlatform.这里只分析 NewDefaultPlatform() 函数.
std::unique_ptr<v8::Platform> NewDefaultPlatform(
int thread_pool_size, IdleTaskSupport idle_task_support,
InProcessStackDumping in_process_stack_dumping,
std::unique_ptr<v8::TracingController> tracing_controller) {
if (in_process_stack_dumping == InProcessStackDumping::kEnabled) {
v8::base::debug::EnableInProcessStackDumping();
}
std::unique_ptr<DefaultPlatform> platform(
new DefaultPlatform(idle_task_support, std::move(tracing_controller)));
platform->SetThreadPoolSize(thread_pool_size);
platform->EnsureBackgroundTaskRunnerInitialized();
return std::move(platform);
}
从 NewDefaultPlatform() 函数以及 Platform 类的注释,我们可以了解什么是 platform是干什么的, 以及platform 有什么用?
从注释来看是一个是对整个嵌入v8的程序的一个抽象概念,看看他的私有变量我们可以大概知道他是干嘛的,Platform用来管理isolate,确定他是在后台线程还是前台线程运行,管理线程池等。
创建完 platform 之后就正式开始进行 v8的初始化工作了.
v8::V8::InitializePlatform(g_platform.get());
v8::V8::Initialize();
if (options.snapshot_blob) {
v8::V8::InitializeExternalStartupDataFromFile(options.snapshot_blob);
} else {
v8::V8::InitializeExternalStartupData(argv[0]);
}
如果options 里面指定了 snapshot_blob 则从指定的 snapshot_blob 文件加载外部的数据,否则从当前执行的文件里面加载外部数据,这里涉及到了 v8的 snapshot 技术,snapshot 就是把之前运行且编译过的代码进行快照,然后保存起来,之后直接从保存的文件中加载,这样可以加快 v8的启动速度.
初始化完成之后,接下来就进入 isolate 创建阶段了.
Isolate::CreateParams create_params;
...
create_params.array_buffer_allocator = Shell::array_buffer_allocator;
create_params.constraints.ConfigureDefaults(
base::SysInfo::AmountOfPhysicalMemory(),
base::SysInfo::AmountOfVirtualMemory());
...
Isolate* isolate = Isolate::New(create_params);
isolate->SetHostCleanupFinalizationGroupCallback(
Shell::HostCleanupFinalizationGroup);
isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
创建 isolate 之前首先需要设置一些信息,比如缓冲区大小,物理内存和虚拟内存的大小等等,这些信息都通过 CreateParams 来保存,v8和 chromium 的代码有很多相似的地方,大部分的内容的初始化都会有一个 CreateParams 类来保存创建信息,比如 Browser::CreateParams, content::CreateParams等等.
在 v8里面创建对象都是通过类的静态成员函数 New 来创建的,而不是直接通过关键字 new 来创建的.
isolate 创建完成之后设置了三个回调函数.
SetHostCleanupFinalizationGroupCallback 指定了在准备清除finalization group并要求在将来的任务中调用FinalizationGroup :: Cleanup() 的回调函数。
SetHostImportModuleDynamicallyCallback 指定通过使用动态import 来加载模块的回调函数。
SetHostInitializeImportMetaObjectCallback 指定通过的 importa.meta 功能检索模块的host-defined元数据时需要调用的回调函数。