nodejs啟動(dòng)流程分析

前言

之前用過(guò)一段時(shí)間的v8 担巩,也只是會(huì)初始化那個(gè)流程榨馁,最近想深入了解一下森篷,所以想要通過(guò)學(xué)習(xí) nodejs 來(lái)加深理解薛耻。這篇文章主要是講講 nodejs 的初始化流程营罢,如有錯(cuò)誤,煩請(qǐng)指教~饼齿。(本文分析基于 v10.9.0饲漾,本文會(huì)盡量避免大段源碼,但是為了有理有據(jù)缕溉,還是會(huì)放上一些精簡(jiǎn)過(guò)并帶有注釋的代碼上來(lái))考传。

Helloworld 鎮(zhèn)樓:

const http = require('http');
const hostname = '127.0.0.1';
const port = 8888;

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
}).listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

寫(xiě)過(guò) nodejs 的都能看懂如上代碼。寥寥數(shù)行证鸥,就創(chuàng)建了一個(gè) http 服務(wù)僚楞。第一行代碼,就出現(xiàn)了一個(gè) require 關(guān)鍵字枉层,那么 require 是從何而來(lái)呢泉褐?帶著這個(gè)問(wèn)題,我們一起去看下吧鸟蜡。

啟動(dòng)流程

1. node 的目錄結(jié)構(gòu)膜赃,此處就不再分析了。最重要的就是 src 和 lib 了揉忘。 src 路徑下是 node 的 C++ 實(shí)現(xiàn)的主要源碼目錄跳座,而 lib 主要是 JavaScript 實(shí)現(xiàn)所在目錄端铛。稍微有一些 C++ 編程基礎(chǔ)的同學(xué)應(yīng)該知道,C++ 的啟動(dòng)函數(shù)就是 main 函數(shù)躺坟。那么 node 的啟動(dòng)函數(shù)在哪呢沦补。通過(guò)全文搜索乳蓄,可以確定咪橙,啟動(dòng)函數(shù)就在 src/node_main.cc 這個(gè)文件當(dāng)中了。此處截取部分源碼:

// windows 啟動(dòng)方法虚倒。
int wmain(int argc, wchar_t* wargv[]) {
  //...
  // 啟動(dòng)方法美侦。
  return node::Start(argc, argv);
}
//...
// 類linux 啟動(dòng)方法。
int main(int argc, char* argv[]) {
    // ...
    // 啟動(dòng)方法魂奥。
    return node::Start(argc, argv);
}

可以看到菠剩,這個(gè)只是一個(gè)外殼,做了一些邏輯判斷耻煤,最終的核心就是調(diào)用 Start 方法具壮。

2. Start 方法位于 src/node.cc:

int Start(int argc, char** argv) {
    //...
    Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv); // 1.
    // v8 初始化。
    InitializeV8Platform(per_process_opts->v8_thread_pool_size);
    v8_initialized = true;
    // 開(kāi)始事件循環(huán)哈蝇。
    const int exit_code =
        Start(uv_default_loop(), args, exec_args);  // 2.
    //... v8 開(kāi)始銷毀棺妓。
    v8_initialized = false;
    V8::Dispose();
    //...
    return exit_code;
}

可以看到,Start 方法主要是執(zhí)行了一個(gè) Init 方法以及對(duì) v8 進(jìn)行了初始化的操作炮赦,然后開(kāi)啟了整個(gè)事件循環(huán)流程怜跑。

2.1 來(lái)看看 Init 方法做了些什么事情,同樣位于 src/node.cc 中:

void Init(int* argc,
          const char** argv,
          int* exec_argc,
          const char*** exec_argv) {
  //... 注冊(cè)內(nèi)部模塊吠勘。 此處暫時(shí)不細(xì)講性芬。
  RegisterBuiltinModules();
  //...  處理參數(shù),打印 help 等剧防。
  ProcessArgv(argv, exec_argv, false);
  //...
}

2.2 接著讓我們看看里面這個(gè) Start 方法做了什么植锉。同樣位于 src/node.cc 中:

inline int Start(uv_loop_t* event_loop,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {
  //... 開(kāi)始創(chuàng)建 Isolate 實(shí)例。 
  Isolate* const isolate = NewIsolate(allocator.get(), event_loop);
  //...
  {
    //... 又是一個(gè) Start 峭拘。
    exit_code = Start(isolate, isolate_data.get(), args, exec_args);
  }
  // isolate 銷毀俊庇。
  isolate->Dispose();
  //...
  return exit_code;
}

參數(shù)檢查什么的就略過(guò)了,上來(lái)先創(chuàng)建了一個(gè) Isolate 實(shí)例棚唆,這個(gè)實(shí)例相當(dāng)于是一個(gè) js 獨(dú)立環(huán)境暇赤,更粗略一點(diǎn),比作一個(gè)頁(yè)面宵凌。 中間又調(diào)用了一個(gè) Start 方法鞋囊,最終處理一下 isolate 的銷毀。

3. 那接著來(lái)看這個(gè) Start 方法(麻木了瞎惫,都叫 Start 方法溜腐。)同樣位于 src/node.cc 中:

inline int Start(Isolate* isolate, IsolateData* isolate_data,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {
  //... 創(chuàng)建一個(gè) Context
  Local<Context> context = NewContext(isolate); // 1.
  //... 創(chuàng)建一個(gè) Environment 實(shí)例译株,并開(kāi)啟 Start 方法。
  Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());
  env.Start(args, exec_args, v8_is_profiling); // 2.
  {
    //... 環(huán)境加載
    LoadEnvironment(&env);  // 3.
    //...
  }

  {
    //...
    do {
      // 事件循環(huán)啟動(dòng)挺益。libuv 相關(guān)歉糜。 4.
      uv_run(env.event_loop(), UV_RUN_DEFAULT);
      //...
    } while (more == true);
    //...
  }
  //...
  const int exit_code = EmitExit(&env);
  //... 善后工作,資源回收等等望众。
  return exit_code;
}

Context 又是 v8 的一個(gè)概念匪补,相當(dāng)于執(zhí)行上下文,js 的執(zhí)行上下文烂翰,可以實(shí)現(xiàn)互不影響夯缺。比如一個(gè)頁(yè)面上嵌套了某個(gè)頁(yè)面,那么他們之間的 js 上下文環(huán)境就不一樣甘耿。此處需要關(guān)注 1 , 2踊兜,3,4 四個(gè)方法佳恬。

3.1 先來(lái)看看 1 ,如何創(chuàng)建的 Context捏境。NewContext 同樣位于 src/node.cc 中:

Local<Context> NewContext(Isolate* isolate,
                          Local<ObjectTemplate> object_template) {
  // 使用 v8 的 api 創(chuàng)建 Context。 
  auto context = Context::New(isolate, nullptr, object_template);
  // ...
  {
    // ... Run lib/internal/per_context.js
    // 獲取 per_context.js 文件的字符串毁葱。
    Local<String> per_context = NodePerContextSource(isolate);
    // 編譯運(yùn)行垫言,v8的模板代碼。
    ScriptCompiler::Source per_context_src(per_context, nullptr);
    Local<Script> s = ScriptCompiler::Compile(
        context,
        &per_context_src).ToLocalChecked();
    s->Run(context).ToLocalChecked();
  }
  return context;
}

此方法不僅僅創(chuàng)建了一個(gè) Context头谜,而且還預(yù)加載執(zhí)行了一段js骏掀。注意這個(gè) NodePerContextSource 方法只有編譯過(guò)才會(huì)有這個(gè)文件。

3.1.1 看一下這個(gè)方法.文件位于node_javascript.cc 中:

v8::Local<v8::String> NodePerContextSource(v8::Isolate* isolate) {
    return internal_per_context_value.ToStringChecked(isolate);
}
static const uint8_t raw_internal_per_context_value[] = { 39,...}
static struct : public v8::String::ExternalOneByteStringResource {
    const char* data() const override {
        return reinterpret_cast<const char*>(raw_internal_per_context_value);
    }
    //...
    v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
        return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
    }
} internal_per_context_value;

看到這里應(yīng)該知道了柱告,就是把 raw_internal_per_context_value 這個(gè)數(shù)組轉(zhuǎn)成 v8 的字符串返回出去截驮。那么問(wèn)題來(lái)了,這個(gè)數(shù)組里面到底是什么東西呢际度。

3.1.2 猜也沒(méi)法猜葵袭,那就打印一下唄。打印數(shù)組相關(guān)代碼如下:

#include <string>
#include <iostream>
static const unsigned char raw_internal_per_context_value[] = {39,...}
int main() {
    std::cout << (char *)raw_internal_bootstrap_loaders_value << std::endl;
}

g++ -o test test.cc & ./test 就可以看到內(nèi)容了乖菱。你會(huì)驚奇的發(fā)現(xiàn)坡锡,這不就是 lib/internal/per_context.js 文件的內(nèi)容嗎?是的窒所,的確是這樣鹉勒,他就是把這段文本直接在編譯期間就編成C++字符數(shù)組,為了在啟動(dòng)的時(shí)候加快啟動(dòng)速度吵取,不至于現(xiàn)場(chǎng)去讀文件從而引發(fā)文件加載速度的等等一系列問(wèn)題禽额。至于此 js 文件內(nèi)容,在此先不做講解。接著讓我回到 4~5步的方法2當(dāng)中脯倒。

**3.2 ** env.Start 方法位于 src/env.cc 中:

void Environment::Start(const std::vector<std::string>& args,
                        const std::vector<std::string>& exec_args,
                        bool start_profiler_idle_notifier) {
    //... 一大堆的 uv 操作等等实辑。
    // 設(shè)置了 process。
    auto process_template = FunctionTemplate::New(isolate()); 
    process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));
    // ...
}

可以看到其中設(shè)置了 process 是什么藻丢,此處設(shè)置了之后剪撬,在js里面就可以直接拿到 process 變量了。

3.3 LoadEnvironment 方法在 src/node.cc 中:

void LoadEnvironment(Environment* env) {
  //...
  // 加載 lib/internal/bootstrap/loaders.js 和 node.js 進(jìn)來(lái)悠反。
  // FIXED_ONE_BYTE_STRING 就是一個(gè)轉(zhuǎn)換字符串的宏残黑。
  Local<String> loaders_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
  // LoadersBootstrapperSource 是獲取 loaders.js 的文件內(nèi)容。 GetBootstrapper 方法是用來(lái)
  // 執(zhí)行 js 的问慎。
  MaybeLocal<Function> loaders_bootstrapper =
      GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
  //...
  // 獲取 global 對(duì)象
  Local<Object> global = env->context()->Global();
  //...
  // 暴露 global 出去萍摊,在 js 中可以訪問(wèn)。
  global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global);

  // 創(chuàng)建bind,linked_binding,internal_binding
  Local<Function> get_binding_fn =
      env->NewFunctionTemplate(GetBinding)->GetFunction(env->context())
          .ToLocalChecked();
  //...

  // 執(zhí)行 internal/loaders.js,node.js 里面的方法如叼。
  if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(),
                           arraysize(loaders_bootstrapper_args),
                           loaders_bootstrapper_args,
                           &bootstrapped_loaders)) {
    return;
  }
  //...
}

static void GetBinding(const FunctionCallbackInfo<Value>& args) {
  // ... 通過(guò)參數(shù)獲取模塊名。
  Local<String> module = args[0].As<String>();
  //... 獲取內(nèi)部模塊穷劈。此處就是通過(guò)2.1步驟中的 RegisterBuiltinModules 宏處理之后的東西來(lái)獲取的笼恰。
  node_module* mod = get_builtin_module(*module_v);
  Local<Object> exports;
  if (mod != nullptr) {
    // 調(diào)用模塊初始化方法。
    exports = InitModule(env, mod, module);
  }
  // ... 設(shè)置返回值歇终。
  args.GetReturnValue().Set(exports);
}

代碼很長(zhǎng)社证,但是條理還是挺清晰的。這里進(jìn)行了一些綁定操作和一些初始化方法的調(diào)用邏輯评凝。此處也可以知道追葡,GetBinding 類似的東西是什么。調(diào)用的 js 如何執(zhí)行需要和 js 一起看才能明白奕短。此處先不講解了宜肉。

3.4 uv_run 這個(gè)方法此處也不細(xì)講了。 libuv 這個(gè)庫(kù)還沒(méi)有詳細(xì)了解翎碑。等待了解之后谬返,補(bǔ)上 libuv 的相關(guān)調(diào)用分析,此處我們知道日杈,在這里開(kāi)始執(zhí)行事件循環(huán)了遣铝。

結(jié)語(yǔ)

講了這么多,大家應(yīng)該對(duì) nodejs 的啟動(dòng)流程有了一個(gè)大致的了解了吧莉擒。雖然開(kāi)頭說(shuō)少點(diǎn)源碼酿炸,可是后來(lái)還是夾雜了很多的源碼,哈哈涨冀,有一種上當(dāng)?shù)母杏X(jué)填硕。后面再講講模塊加載,libuv加載的相關(guān)東西蝇裤。這次分析就到此結(jié)束吧廷支,大家休息~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末频鉴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恋拍,更是在濱河造成了極大的恐慌垛孔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件施敢,死亡現(xiàn)場(chǎng)離奇詭異周荐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)僵娃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門概作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人默怨,你說(shuō)我怎么就攤上這事讯榕。” “怎么了匙睹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵愚屁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痕檬,道長(zhǎng)霎槐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任梦谜,我火速辦了婚禮丘跌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唁桩。我一直安慰自己闭树,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布朵夏。 她就那樣靜靜地躺著蔼啦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仰猖。 梳的紋絲不亂的頭發(fā)上捏肢,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音饥侵,去河邊找鬼鸵赫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躏升,可吹牛的內(nèi)容都是我干的辩棒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼一睁!你這毒婦竟也來(lái)了钻弄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤者吁,失蹤者是張志新(化名)和其女友劉穎窘俺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體复凳,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘤泪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了育八。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片对途。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖髓棋,靈堂內(nèi)的尸體忽然破棺而出实檀,到底是詐尸還是另有隱情,我是刑警寧澤仲锄,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布劲妙,位于F島的核電站,受9級(jí)特大地震影響儒喊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜币呵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一怀愧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧余赢,春花似錦芯义、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至举塔,卻和暖如春绑警,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背央渣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工计盒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芽丹。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓北启,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咕村,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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

  • 新項(xiàng)目預(yù)研在KittenBlock上添加我們接口的Block场钉,里面用到了NWJS,跟以往的Electron框架類似...
    JomarWu閱讀 6,070評(píng)論 2 1
  • 前言 js是從網(wǎng)頁(yè)小腳本演變過(guò)來(lái)的懈涛,至今逛万,前端的js庫(kù),也不像一個(gè)真正的模塊肩钠。前端js經(jīng)歷了工具類庫(kù)泣港、組件庫(kù)、前端...
    白昔月閱讀 3,278評(píng)論 2 11
  • 「年輕人价匠,你的問(wèn)題主要在于讀書(shū)不多而想得太多当纱。」 --- 楊絳 2015 年讀了不少書(shū)踩窖,收獲也不少坡氯,到了年底寫(xiě)個(gè)小...
    yyqian閱讀 534評(píng)論 0 0
  • 據(jù)調(diào)查箫柳,腦癱已成為兒童疾病的高發(fā)病,而導(dǎo)致腦癱居高不下的原因也是各種各樣啥供,由于腦癱是因?yàn)槟撤N病因而損害了大腦部份悯恍,...
    孟青閱讀 251評(píng)論 0 1
  • 虞美人 千里相思人寂寥。惆悵明月橋伙狐。北地南疆萬(wàn)里遙涮毫。鴻雁難寄,空守錦書(shū)老贷屎。...
    昭兮未央閱讀 333評(píng)論 0 0