Node.js 原始碼分析 - 從 main 函式開始
title: Node.js 原始碼分析 - 從 main 函式開始
date: 2018-11-27 21:30:15
tags:
- Node.js
- Node.js 原始碼分析
- 原始碼分析
categories:
- Node.js 原始碼分析
此文最初於四年前釋出在個人站上的,現遷移至此重發,原連結: https://laogen.site/nodejs/nodejs-src/the-main/
《Node.js 原始碼分析》 系列目錄頁:https://laogen.site/nodejs/nodejs-src/index/
小目標
知道程式大概執行邏輯,關鍵點執行的順序
我們平時在終端敲下 node app.js
具體點,知道 node.js 原生(C++)模組什麼時候載入的,在哪載入的;
知道我們的 js 程式碼是在哪個環節被載入執行的;
知道程序的主迴圈(事件迴圈)什麼時候啟動的;
有了這個小目標的基礎,在接下來的文章中,我們再進一步的探索 node.js 原生模組的註冊是怎麼實現的,怎麼獲取 & 初始化的,怎麼曝露給 js 環境呼叫的;再細說 node.js 的模組機制,我們通常的 app.js
怎麼被執行的;
貼程式碼說明
限於篇幅,本文只先把大體執行流程捋出來,後面再開文一塊塊的捋。
原始碼太長,先把不影響我們分析的無關程式碼去掉,貼上來有關整體執行邏輯的程式碼,程式碼中的 // ...
每段程式碼第一行的註釋都會指出原始檔位置,一些程式碼講解會在程式碼段中的註釋中進行;
本文不再介紹 V8 和 Libuv 的知識,會開專門的分類寫 V8 和 Libuv,參考 {% post_link nodejs/nodejs-src/index Node.js 原始碼分析 - 前言 %}
開捋:從 main 函式到程序主迴圈
main 函式
/* 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 引擎
/* 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++ 實現模組。
/* 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 例項
/* 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*...)
。
程序主迴圈
/* 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
。
主迴圈之前
/* 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.js
和 lib/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 原始碼,再花時間捋一捋,光靠貼的這點程式碼,可能還是會迷糊的。
接下來的文章,就是對這個執行邏輯中的關鍵點分別展開。
作者水平有限,寫的也倉促,有誤之處還請指出。
Maslow ([email protected]), laf.js 作者。
lafyun.com 開源雲開發平臺,前端變全棧,無需服務端。