Node.js 源碼分析 - 原生模塊(C++模塊)的注冊


title: Node.js 源碼分析 - 原生模塊(C++模塊)的注冊
date: 2018-11-28 21:04:49
tags:
- Node.js
- Node.js 源碼分析
- 源碼分析
categories:
- Node.js 源碼分析


此文最初于四年前發(fā)布在個人站上的,現(xiàn)遷移至此重發(fā),原鏈接:https://laogen.site/nodejs/nodejs-src/register-builtin-modules/
《Node.js 源碼分析》 系列目錄頁:https://laogen.site/nodejs/nodejs-src/index/

上一篇提到 RegisterBuiltinModules() 注冊了原生 C++ 模塊沒有詳細(xì)展開丧荐,這里就從這個函數(shù)展開。

將 RegisterBuiltinModules() 層層展開

/* src/node.cc:3066 */
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
  NODE_BUILTIN_MODULES(V)
#undef V
}

首先定義了一個宏 V_register_##modname(), 可以看出 V 展開后是一個函數(shù)調(diào)用類似這樣: _register_xx();

隨后埠巨,RegisterBuiltinModules() 實(shí)際是宏 NODE_BUILTIN_MODULES(V) 來實(shí)現(xiàn)的宏悦,我們看看它的定義:

/* src/node_internals.h:147 */
#define NODE_BUILTIN_MODULES(V)    \
  NODE_BUILTIN_STANDARD_MODULES(V)
// ...

進(jìn)一步查看 NODE_BUILTIN_STANDARD_MODULES(V) 的定義:

/* src/node_internals.h:106 */
#define NODE_BUILTIN_STANDARD_MODULES(V)  \
    V(async_wrap)       \
    V(buffer)           \
    V(cares_wrap)       \
    V(config)           \
    V(contextify)       \
    V(domain)           \
    V(fs)               \
    V(fs_event_wrap)    \
    V(heap_utils)       \
    V(http2)            \
    V(http_parser)      \
    V(inspector)        \
    V(js_stream)        \
    V(messaging)        \
    V(module_wrap)      \
    V(options)          \
    V(os)               \
    V(performance)      \
    V(pipe_wrap)        \
    V(process_wrap)     \
    V(serdes)           \
    V(signal_wrap)      \
    V(spawn_sync)       \
    V(stream_pipe)      \
    V(stream_wrap)      \
    V(string_decoder)   \
    V(symbols)          \
    V(tcp_wrap)         \
    V(timer_wrap)       \
    V(trace_events)     \
    V(tty_wrap)         \
    V(types)            \
    V(udp_wrap)         \
    V(url)              \
    V(util)             \
    V(uv)               \
    V(v8)               \
    V(worker)           \
    V(zlib)

這個宏定義中多次調(diào)用宏 V,還記得這個宏嗎沛厨,在上面定義的:#define V(modname) _register_##modname();刚照,那我們把它展開后就是:

/* src/node_internals.h:106 */
#define NODE_BUILTIN_STANDARD_MODULES(V)  \
    _register_async_wrap();
    _register_buffer();
    _register_cares_wrap();
    _register_config();
    _register_contextify();
    _register_domain();
    _register_fs();
    _register_fs_event_wrap();
    _register_heap_utils();
    _register_http2();
    _register_http_parser();
    _register_inspector();
    _register_js_stream();
    _register_messaging();
    _register_module_wrap();
    _register_options();
    _register_os();
    _register_performance();
    _register_pipe_wrap();
    _register_process_wrap();
    _register_serdes();
    _register_signal_wrap();
    _register_spawn_sync();
    _register_stream_pipe();
    _register_stream_wrap();
    _register_string_decoder();
    _register_symbols();
    _register_tcp_wrap();
    _register_timer_wrap();
    _register_trace_events();
    _register_tty_wrap();
    _register_types();
    _register_udp_wrap();
    _register_url();
    _register_util();
    _register_uv();
    _register_v8();
    _register_worker();
    _register_zlib();

最終刑巧,RegisterBuiltinModules() 展開后大概是這樣的:

void RegisterBuiltinModules() {
  _register_async_wrap();
  _register_buffer();
  // ...
  _register_os();
  // ...
}

經(jīng)過層層的宏展開,我們看到 RegisterBuiltinModules() 的原貌无畔,就是調(diào)用了一些全局注冊函數(shù)啊楚,這樣就能理解了。

接下來浑彰,我們打算看看這些注冊函數(shù)是在哪里定義的恭理。 我全局搜索了整個代碼目錄,也沒找到這些函數(shù)中的任何一個郭变,看來又是通過宏定義的颜价。

那我們就挑一個原生模塊的源碼,來看看里面有沒有上面注冊函數(shù)的定義诉濒,我挑了模塊名為 os 的模塊周伦,它的源碼位于 src/node_os.cc

查看一個原生模塊的源碼

/* src/node_os.cc */
namespace node {
namespace os {
// ...
static void GetHostname(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetOSType(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetOSRelease(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetCPUInfo(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetFreeMemory(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetTotalMemory(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetUptime(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetLoadAvg(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetInterfaceAddresses(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetHomeDirectory(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void SetPriority(const FunctionCallbackInfo<Value>& args) {
  // ...
}
static void GetPriority(const FunctionCallbackInfo<Value>& args) {
  // ...
}

// 這個初始化函數(shù)是每個原生模塊都會定義的,它的參數(shù)也是一致的
void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context) {
  Environment* env = Environment::GetCurrent(context);
  env->SetMethod(target, "getHostname", GetHostname);
  env->SetMethod(target, "getLoadAvg", GetLoadAvg);
  env->SetMethod(target, "getUptime", GetUptime);
  env->SetMethod(target, "getTotalMem", GetTotalMemory);
  env->SetMethod(target, "getFreeMem", GetFreeMemory);
  env->SetMethod(target, "getCPUs", GetCPUInfo);
  env->SetMethod(target, "getOSType", GetOSType);
  env->SetMethod(target, "getOSRelease", GetOSRelease);
  env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses);
  env->SetMethod(target, "getHomeDirectory", GetHomeDirectory);
  env->SetMethod(target, "getUserInfo", GetUserInfo);
  env->SetMethod(target, "setPriority", SetPriority);
  env->SetMethod(target, "getPriority", GetPriority);
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"),
              Boolean::New(env->isolate(), IsBigEndian()));
}

}  // namespace os
}  // namespace node

NODE_BUILTIN_MODULE_CONTEXT_AWARE(os, node::os::Initialize)

這個 os 模塊先是定義了一些函數(shù)未荒,代碼最后一行是個宏調(diào)用专挪,這個宏把模塊名 osInitialize 函數(shù)做為其參數(shù),我們找到它的定義如下:

/* src/node_internals.h:169 */
#define NODE_BUILTIN_MODULE_CONTEXT_AWARE(modname, regfunc)   \
  NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_BUILTIN)

又是一個宏定義片排,繼續(xù)跟下去:

/* src/node_internals.h:152*/
#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \
  static node::node_module _module = { \
    NODE_MODULE_VERSION,    \
    flags,      \
    nullptr,    \
    __FILE__,   \
    nullptr,    \
    (node::addon_context_register_func) (regfunc),  \
    NODE_STRINGIFY(modname),    \
    priv,   \
    nullptr \
  };    \
  void _register_ ## modname() {    \
    node_module_register(&_module); \
  }

這個宏的定義里好像看到了我們要找的代碼寨腔,我們在這里就可以把 NODE_BUILTIN_MODULE_CONTEXT_AWARE(os, node::os::Initialize) 完全展開了:

// 創(chuàng)建一個 node_module 對象 _module
static node::node_module _module = {
    NODE_MODULE_VERSION,
    NM_F_BUILTIN,
    nullptr,
    __FILE__,
    nullptr,
    (node::addon_context_register_func) (node::os::Initialize),  
    NODE_STRINGIFY(os),
    nullptr,
    nullptr
};

// 定義我們要找的 _register_os() 函數(shù)
void _register_os() {    
  node_module_register(&_module); 
}

到此,我們就明白了 RegisterBuiltinModules() 函數(shù)中調(diào)用的 _register_os() 是在哪里定義的了率寡,隨后查看了所有原生模塊的代碼迫卢,最后一行都是以同樣的方式定義相應(yīng)的 _register_xx()

其中 node::node_module 類型就代表一個模塊的信息冶共。

所謂注冊 os 模塊實(shí)際是調(diào)用了 node_module_register(node_module *) 函數(shù)完成的乾蛤,我們繼續(xù)來看看 node_module_register() 函數(shù)和 node::node_module

模塊注冊實(shí)現(xiàn)

/* src/node.h:518*/
struct node_module {
  int nm_version;
  unsigned int nm_flags;
  void* nm_dso_handle;
  const char* nm_filename;
  // 上例中 Initialize 函數(shù)被賦到 nm_register_func 里
  node::addon_register_func nm_register_func;  
  node::addon_context_register_func nm_context_register_func;
  const char* nm_modname; // 模塊的名字
  void* nm_priv;
  struct node_module* nm_link;  
};
/* src/node.cc:1094 */
extern "C" void node_module_register(void* m) {
  struct node_module* mp = reinterpret_cast<struct node_module*>(m);

  if (mp->nm_flags & NM_F_BUILTIN) {
    // 鏈表操作
    mp->nm_link = modlist_builtin;
    modlist_builtin = mp;
  } else if (mp->nm_flags & NM_F_INTERNAL) {
    // 鏈表操作
    mp->nm_link = modlist_internal;
    modlist_internal = mp;
  } else if (!node_is_initialized) {
    // "Linked" modules are included as part of the node project.
    // Like builtins they are registered *before* node::Init runs.
    mp->nm_flags = NM_F_LINKED;
    mp->nm_link = modlist_linked;
    modlist_linked = mp;
  } else {
    uv_key_set(&thread_local_modpending, mp);
  }
}

到這里就清晰了每界, 所謂原生模塊的注冊,實(shí)際上就是將一個類型為 node::node_module 的模塊對象幻捏,添加到不同類別的全局鏈表中盆犁。

上述代碼中用3個全局鏈表:modlist_builtin modlist_internal modlist_linked 分別保存不同類型的模塊命咐,本文我們說的是 BUILTIN 類型的篡九,也就是第一個。

我把這幾個鏈表的定義位置發(fā)出來:

/* src/node.cc:175 */
static node_module* modlist_builtin;   // 我們現(xiàn)在只關(guān)注 builtin 模塊
static node_module* modlist_internal;
static node_module* modlist_linked;
static node_module* modlist_addon;

小結(jié)

這個原生模塊的注冊過程就寫到這里醋奠,邏輯還是比較簡單的榛臼,只是連續(xù)的宏定義讓代碼不那么直觀。

原生模塊加載寫完后窜司,接下來沛善,會繼續(xù)寫原生模塊的加載篇。

Maslow (wangfugen@126.com)塞祈, laf.js 作者金刁。

lafyun.com 開源云開發(fā)平臺,前端變?nèi)珬R樾剑瑹o需服務(wù)端尤蛮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斯议,隨后出現(xiàn)的幾起案子产捞,更是在濱河造成了極大的恐慌,老刑警劉巖哼御,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯临,死亡現(xiàn)場離奇詭異,居然都是意外死亡恋昼,警方通過查閱死者的電腦和手機(jī)看靠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來液肌,“玉大人挟炬,你說我怎么就攤上這事【仄ǎ” “怎么了辟宗?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吝秕。 經(jīng)常有香客問我泊脐,道長,這世上最難降的妖魔是什么烁峭? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任容客,我火速辦了婚禮秕铛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缩挑。我一直安慰自己但两,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布供置。 她就那樣靜靜地躺著谨湘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芥丧。 梳的紋絲不亂的頭發(fā)上紧阔,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音续担,去河邊找鬼擅耽。 笑死,一個胖子當(dāng)著我的面吹牛物遇,可吹牛的內(nèi)容都是我干的乖仇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼询兴,長吁一口氣:“原來是場噩夢啊……” “哼乃沙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蕉朵,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤崔涂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后始衅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冷蚂,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年汛闸,在試婚紗的時候發(fā)現(xiàn)自己被綠了蝙茶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡诸老,死狀恐怖隆夯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情别伏,我是刑警寧澤蹄衷,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站厘肮,受9級特大地震影響愧口,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜类茂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一耍属、第九天 我趴在偏房一處隱蔽的房頂上張望托嚣。 院中可真熱鬧,春花似錦厚骗、人聲如沸示启。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夫嗓。三九已至,卻和暖如春提揍,著一層夾襖步出監(jiān)牢的瞬間啤月,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工劳跃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浙垫。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓刨仑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夹姥。 傳聞我的和親對象是個殘疾皇子杉武,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容