Node.js 源码分析 - 从 main 函数开始

小目标

知道程序大概执行逻辑,关键点执行的顺序

我们平时在终端敲下 node app.js 后,发生了什么。

具体点,知道 node.js 原生(C++)模块什么时候加载的,在哪加载的;
知道我们的 js 代码是在哪个环节被加载执行的;
知道进程的主循环(事件循环)什么时候启动的;

有了这个小目标的基础,在接下来的文章中,我们再进一步的探索 node.js 原生模块的注册是怎么实现的,怎么获取 & 初始化的,怎么曝露给 js 环境调用的;再细说 node.js 的模块机制,我们通常的 app.js 怎么被执行的;

贴代码说明

限于篇幅,本文只先把大体执行流程捋出来,后面再开文一块块的捋。

原代码太长,先把不影响我们分析的无关代码去掉,贴上来有关整体执行逻辑的代码,代码中的 // ... 注释意思是这个地方有被省略的代码。

每段代码第一行的注释都会指出源文件位置,一些代码讲解会在代码段中的注释中进行;

本文不再介绍 V8 和 Libuv 的知识,会开专门的分类写 V8 和 Libuv,参考 Node.js 源码分析 - 前言

开捋:从 main 函数到进程主循环

main 函数

1
2
3
4
5
/* src/node_main.cc:93 */
int main(int argc, char* argv[]) {
// ...
return node::Start(argc, argv);
}

main函数src/node_main.cc 这个文件中,这个文件主要就是存放 main函数

很简单,只是调用了 node::Start(),这个函数在 src/node.cc 这个文件中,接下来的核心代码都在这个文件中。

初始化 V8 引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* src/node.cc:3011 */
int Start(int argc, char** argv) {
// ...

std::vector<std::string> args(argv, argv + argc);
std::vector<std::string> exec_args;

// This needs to run *before* V8::Initialize().
Init(&args, &exec_args);

// ...
v8_platform.Initialize(per_process_opts->v8_thread_pool_size);
V8::Initialize();

// ...
const int exit_code = Start(uv_default_loop(), args, exec_args);

v8_platform.StopTracingAgent();
v8_initialized = false;
V8::Dispose();
v8_platform.Dispose();

return exit_code;
}

在这段代码,首先进行 V8 的初始化,然后调用了另外一个 Start(uv_loop_t*, ...)函数,最后释放资源,进程结束;

其中值得注意的一点,在初始化 V8 之前,调用了一个 Init() 函数,这个函数主要完成了 Node.js 原生(C++)模块的注册,就是 fs http等模块的 C++ 实现模块。

1
2
3
4
5
6
7
/* src/node.cc:2559 */
void Init(std::vector<std::string>* argv, std::vector<std::string>* exec_argv) {
// ...
// Register built-in modules
RegisterBuiltinModules();
// ...
}

Init() 中调用了 RegisterBuiltinModules(),它注册了所有 Node.js 原生模块,关于原生模块的注册,本文不再继续跟进去,下一篇会单独展开这一块,这里先知道这个流程。

记住这个 RegisterBuiltinModules(),下一篇文章就从这里开始展开。

创建 Isolate 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* src/node.cc:2964 */
inline int Start(uv_loop_t* event_loop,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
std::unique_ptr<ArrayBufferAllocator, decltype(&FreeArrayBufferAllocator)>
allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator);
// 创建 Isolate 实例
Isolate* const isolate = NewIsolate(allocator.get());

// ...
int exit_code;
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
// ...
exit_code = Start(isolate, isolate_data.get(), args, exec_args);
}
// ...
isolate->Dispose();
return exit_code;
}

这个 Start() 倒也没做什么,主要工作是创建了 Isolate 实例,然后调用了另外一个 Start(Isolate*...)

进程主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* src/node.cc:2868 */
inline int Start(Isolate* isolate, IsolateData* isolate_data,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
HandleScope handle_scope(isolate);
// 创建 V8 Context 对象
Local<Context> context = NewContext(isolate);
Context::Scope context_scope(context);

// 创建 Environment 对象,这个是 Node.js 的类
Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());

// 这里面主要完成 libuv 的初始化,以及创建 process 对象
// 就是 Node.js 中那个全局的 process 对象,这里不细展开
env.Start(args, exec_args, v8_is_profiling);

{
// ...
// LoadEnvironment 是本文重要的关键点
LoadEnvironment(&env);
env.async_hooks()->pop_async_id(1);
}

// 下面就是进程的主循环
{
// ...
bool more;
// ...
do {
uv_run(env.event_loop(), UV_RUN_DEFAULT);
// ...
more = uv_loop_alive(env.event_loop());
if (more)
continue;

// ...
} while (more == true);
}
// ...
return exit_code;
}

这段代码创建并使用了 js 执行需要的 context,然后创建了 Environment 对象;

这个 Environment 对象是 Node.js 源码中重要的一个对象,它是一个全局单例,定义和存储了一些重要的全局对象和函数,比如刚开始创建的 Isolate 对象、刚刚创建的 Context 对象等,注意它不是 V8 的,是 Node.js 定义的,对它的使用贯穿整个 Node.js 执行的生命周期。

再下面是进程的主循环,uv_run() 启动了 Libuv 的事件循环, 它也是 Node.js 进程的主循环,Libuv 会单独写文介绍。

最后说一下,中间的 LoadEnvironment() 调用,它是在程序进入主循环之前最关键的一环;

LoadEnvironment() 完成了一些 js 文件的加载和执行,其中就包括加载执行通常编写的 app.js

主循环之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/* src/node.cc:2115 */

void LoadEnvironment(Environment* env) {
HandleScope handle_scope(env->isolate());
// ...

// The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
// lib/internal/bootstrap/node.js, each included as a static C string
// defined in node_javascript.h, generated in node_javascript.cc by
// node_js2c.
// 加载两个重要的 js 文件:internal/bootstrap/loaders.js
// 和 internal/bootstrap/node.js
Local<String> loaders_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
MaybeLocal<Function> loaders_bootstrapper =
GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
Local<String> node_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
MaybeLocal<Function> node_bootstrapper =
GetBootstrapper(env, NodeBootstrapperSource(env), node_name);
// ...
// Add a reference to the global object
Local<Object> global = env->context()->Global();

env->SetMethod(env->process_object(), "_rawDebug", RawDebug);

// Expose the global object as a property on itself
// (Allows you to set stuff on `global` from anywhere in JavaScript.)
global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global);

// 准备 binding 函数,下面调用 js 会作为参数传给 js 环境
// Create binding loaders
Local<Function> get_binding_fn =
env->NewFunctionTemplate(GetBinding)->GetFunction(env->context())
.ToLocalChecked();

Local<Function> get_linked_binding_fn =
env->NewFunctionTemplate(GetLinkedBinding)->GetFunction(env->context())
.ToLocalChecked();

Local<Function> get_internal_binding_fn =
env->NewFunctionTemplate(GetInternalBinding)->GetFunction(env->context())
.ToLocalChecked();

// 准备执行 internal/bootstrap/loaders.js 文件的参数
Local<Value> loaders_bootstrapper_args[] = {
env->process_object(),
get_binding_fn,
get_linked_binding_fn,
get_internal_binding_fn,
Boolean::New(env->isolate(),
env->options()->debug_options->break_node_first_line)
};

// 执行 internal/bootstrap/loaders.js
// Bootstrap internal loaders
// 这个对象是用来接收执行结果的,记住是 bootstrapped_loaders,下面会用到
Local<Value> bootstrapped_loaders;
if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(),
arraysize(loaders_bootstrapper_args),
loaders_bootstrapper_args,
&bootstrapped_loaders)) {
return;
}

// 准备执行 internal/bootstrap/node.js 的参数
// Bootstrap Node.js
Local<Object> bootstrapper = Object::New(env->isolate());
SetupBootstrapObject(env, bootstrapper);
Local<Value> bootstrapped_node;
Local<Value> node_bootstrapper_args[] = {
env->process_object(),
bootstrapper,
// 注意,这里是上面执行 loaders.js 返回的结果对象,
// 作为执行参数传给 internal/bootstrap/node.js
bootstrapped_loaders
};

// 执行 internal/bootstrap/node.js
if (!ExecuteBootstrapper(env, node_bootstrapper.ToLocalChecked(),
arraysize(node_bootstrapper_args),
node_bootstrapper_args,
&bootstrapped_node)) {
return;
}
}

LoadEnvironment() 首先加载了两个 js 文件,这两个 js 文件的位置分别在:
lib/internal/bootstrap/loaders.jslib/internal/bootstrap/node.js

我们 Node.js 开发者写的 app.js 其实就是在这两个 js 文件中加载并执行的,这块是最重要的逻辑之一,内容也很多,后面的文章会详细展开。

LoadEnvironment() 接下来创建了三个 binding 函数:

  • get_binding_fn
  • get_linked_binding_fn
  • get_internal_binding_fn

这3个 binding 函数是用来获取和加载 Node.js 原生模块的,会传入到 js 执行环境中,也就是你在 js 代码中是可以调用的,比如 process.binding('fs'),在我们用 C++ 开发 Node.js 扩展模块的时候,也会用到,以后会详细展开。

LoadEnvironment() 接下来要执行 lib/internal/bootstrap/loaders.js,在这个 js 文件中主要定义了内部(internal)模块加载器(loaders)。

lib/internal/bootstrap/loaders.js 定义的模块加载器(loaders) 接下来做为执行参数,传入了 lib/internal/bootstrap/node.js,在 lib/internal/bootstrap/node.js 中会使用这些 loaders 来加载 internal 模块。

lib/internal/bootstrap/node.js 做了很多工作,这里只需要知道,它最终加载并执行了我们 Node.js 程序员编写的 app.js 就可以了。

到此为止,我们就知道了在命令行敲下 node app.js 大概发生了哪些事!

小结

这只是个大概逻辑,可以配合 Node.js 源码,再花时间捋一捋,光靠贴的这点代码,可能还是会迷糊的。

接下来的文章,就是对这个执行逻辑中的关键点分别展开。

作者水平有限,写的也仓促,有误之处还请指出。