node 模組載入原理【1】
阿新 • • 發佈:2020-03-29
[TOC]
## 簡單介紹
我們會從簡單的模組載入原理來開始,嘗試閱讀下 Node.js 原始碼。首先我們知道 Node.js 的原始碼主要是由 C++ 和 JavaScript 編寫的,JS 部分主要在 lib 目錄下,而 C++ 部分主要在 src 目錄下。
![](https://img2020.cnblogs.com/blog/1004427/202003/1004427-20200329151921980-987119585.png)
模組載入主要是分四種類型的模組:
1. C++ 核心模組:主要在 src 目錄下,比如 node_file.cc
2. Node.js 內部模組:和 C++ 核心模組不同,在原始碼的 lib 目錄下,以同名的 JS 原始碼來實現,實際上 Node.js 內建模組是對 C++ 核心模組的更高層次的封裝,比如 fs、http
3. 使用者原始碼模組
4. C++ 擴充套件模組:外掛是用 C++ 編寫的動態連結共享物件。 require() 函式可以將外掛載入為普通的 Node.js 模組。 外掛提供了 JavaScript 和 C/C++ 庫之間的介面。
這次介紹的是 C++ 核心模組載入的原始碼分析
## C++ 核心模組載入過程
一句話概括:每個 C++ 核心模組原始碼末尾都有一個巨集呼叫,將模組註冊到 C++ 核心模組的連結串列當中去,以供 `internalBinding` 獲取它。
從這句話可以得到兩個步驟:註冊和獲取。
### 總的流程
![](https://img2020.cnblogs.com/blog/1004427/202003/1004427-20200329152207598-817500759.png)
1. 入口檔案執行
我們知道,Node.js 的執行入口在 node.cc 的 Start 函式
```c++
int Start(int argc, char** argv) {}
```
2. 分支一:C++ 模組註冊到連結串列中
我們來看看 Start 函式都做了什麼,首先呼叫了 InitializeOncePerProcess,對 Node.js 和 V8 做初始化處理。從這個函式找下去,可以找到 C++ 模組註冊到連結串列
```c++
InitializationResult result = InitializeOncePerProcess(argc, argv);
```
3. 分支二:獲取 C++ 模組
在 Start 函式繼續找下去,可以看到例項化了 NodeMainInstance,呼叫了 Run 函式。從這個函式找下去,可以找到獲取 C++ 模組的過程
```c++
NodeMainInstance main_instance(...);
result.exit_code = main_instance.Run();
```
### 分支一:C++ 模組註冊到連結串列中
![](https://img2020.cnblogs.com/blog/1004427/202003/1004427-20200329152336141-625291399.png)
#### 1、前置鏈路
從上面的 `InitializeOncePerProcess` 開始往下找,可以看到呼叫了初始化 node 的函式 `InitializeNodeWithArgs` 函式
```c++
result.exit_code = InitializeNodeWithArgs(&(result.args), &(result.exec_args), &errors);
```
繼續找,看到它呼叫了 node_binding.cc 中的 `RegisterBuiltinModules`,作用是註冊 C++ 模組
```c++
binding::RegisterBuiltinModules();
```
#### 2、C++ 模組註冊函式的呼叫
那我們就來看一下,`RegisterBuiltinModules` 這個函式都做了什麼,主要做了兩件事:一是定義了 `V` 這個巨集,它接收 `modname` 這個引數;二是呼叫 `NODE_BUILTIN_MODULES`
```c++
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
NODE_BUILTIN_MODULES(V) // module name 可以在這個巨集找到,這裡不多寫了
#undef V
}
```
然後我們可以看到 `NODE_BUILTIN_MODULES` 其實也是一個巨集定義:
```c++
#define NODE_BUILTIN_MODULES(V) \ NODE_BUILTIN_STANDARD_MODULES(V) \
...
```
其中 `NODE_BUILTIN_STANDARD_MODULES` 的定義是這樣的:
```c++
#define NODE_BUILTIN_STANDARD_MODULES(V) \
V(async_wrap) \
V(buffer) \
...
```
也就是說,c++的預處理後,會變成下面的函式體
```c++
void RegisterBuiltinModules() {
_register_async_wrap();
_register_buffer();
.... }
```
也就是說,最終去呼叫的是 `_register_async_wrap` 和 `_register_buffer` ...這些函式,好了,那麼這些函式是在哪定義的呢?
#### 3、C++ 模組註冊函式的定義
從上面巨集定義後面,可以找到這樣的註釋:
The definitions are in each module's implementation when calling the NODE_MODULE_CONTEXT_AWARE_INTERNAL.
也就是說,定義是在每個模組呼叫 `NODE_MODULE_CONTEXT_AWARE_INTERNAL` 時進行的~
好的於是我們看看 fs 模組對應的 C++ 檔案 node_file.cc ,看到最後一行呼叫了 `NODE_MODULE_CONTEXT_AWARE_INTERNAL`
```c++
NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs, node::fs::Initialize)
```
繼續,在 node_binding.h 看到它呼叫了 `NODE_MODULE_CONTEXT_AWARE_CPP`
```c++
#define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc) \
NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL)
```
繼續,在 node_binding.h 中,定義了` _register_##modname` 執行時呼叫了 `node_module_register`
```c++
#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \
void _register_##modname() { node_module_register(&_module); }
```
好的最後一步,在 node_binding.cc 中找到了它的定義,將傳入的 node module 插到了連結串列 `modlist_internal` 中,後面查詢時就會在這裡找了~
```c++
extern "C" void node_module_register(void* m) {
struct node_module* mp = reinterp