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_fnget_linked_binding_fnget_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 開源云開發平臺,前端變全堆疊,無需服務端,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/428536.html
標籤:其他
