摘要:注很多以前的源碼分析文章中,所寫的第一個執(zhí)行的文件代碼為,但這個文件在中已被移除,并被拆解為了等其他下的文件,為正文作為第一段被執(zhí)行的代碼,它的歷史使命免不了就是進行一些環(huán)境和全局變量的初始化工作。
大家可能會好奇,在 Node.js 啟動后,第一個執(zhí)行的 JavaScript 文件會是哪個?它具體又會干些什么事?
一步步來看,翻開 Node.js 的源碼,不難看出,入口文件在 src/node_main.cc 中,主要任務(wù)為將參數(shù)傳入 node::Start 函數(shù):
// src/node_main.cc // ... int main(int argc, char *argv[]) { setvbuf(stderr, NULL, _IOLBF, 1024); return node::Start(argc, argv); }
node::Start 函數(shù)定義于 src/node.cc 中,它進行了必要的初始化工作后,會調(diào)用 StartNodeInstance :
// src/node.cc // ... int Start(int argc, char** argv) { // ... NodeInstanceData instance_data(NodeInstanceType::MAIN, uv_default_loop(), argc, const_cast(argv), exec_argc, exec_argv, use_debug_agent); StartNodeInstance(&instance_data); }
而在 StartNodeInstance 函數(shù)中,又調(diào)用了 LoadEnvironment 函數(shù),其中的 ExecuteString(env, MainSource(env), script_name); 步驟,便執(zhí)行了第一個 JavaScript 文件代碼:
// src/node.cc // ... void LoadEnvironment(Environment* env) { // ... Localf_value = ExecuteString(env, MainSource(env), script_name); // ... } static void StartNodeInstance(void* arg) { // ... { Environment::AsyncCallbackScope callback_scope(env); LoadEnvironment(env); } // ... } // src/node_javascript.cc // ... Local MainSource(Environment* env) { return String::NewFromUtf8( env->isolate(), reinterpret_cast (internal_bootstrap_node_native), NewStringType::kNormal, sizeof(internal_bootstrap_node_native)).ToLocalChecked(); }
其中的 internal_bootstrap_node_native ,即為 lib/internal/bootstrap_node.js 中的代碼。(注:很多以前的 Node.js 源碼分析文章中,所寫的第一個執(zhí)行的 JavaScript 文件代碼為 src/node.js ,但這個文件在 Node.js v5.10 中已被移除,并被拆解為了 lib/internal/bootstrap_node.js 等其他 lib/internal 下的文件,PR 為: https://github.com/nodejs/node/pull/5103 )
正文作為第一段被執(zhí)行的 JavaScript 代碼,它的歷史使命免不了就是進行一些環(huán)境和全局變量的初始化工作。代碼的整體結(jié)構(gòu)很簡單,所有的初始化邏輯都被封裝在了 startup 函數(shù)中:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... } // ... startup(); });
而在 startup 函數(shù)中,邏輯可以分為四塊:
初始化全局 process 對象上的部分屬性 / 行為
初始化全局的一些 timer 方法
初始化全局 console 等對象
開始執(zhí)行用戶執(zhí)行指定的 JavaScript 代碼
讓我們一個個來解析。
初始化全局 process 對象上的部分屬性 / 行為 添加 process 上 uncaughtException 事件的默認行為在 Node.js 中,如果沒有為 process 上的 uncaughtException 事件注冊監(jiān)聽器,那么該事件觸發(fā)時,將會導致進程退出,這個行為便是在 startup 函數(shù)里添加的:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { setupProcessFatal(); } // ... function setupProcessFatal() { process._fatalException = function(er) { var caught; // ... if (!caught) caught = process.emit("uncaughtException", er); if (!caught) { try { if (!process._exiting) { process._exiting = true; process.emit("exit", 1); } } catch (er) { } } // ... return caught; }; } });
邏輯十分直白,使用到了 EventEmitter#emit 的返回值來判斷該事件上是否有注冊過的監(jiān)聽器,并最終調(diào)用 c++ 的 exit() 函數(shù)退出進程:
// src/node.cc // ... void FatalException(Isolate* isolate, Local根據(jù) Node.js 在啟動時所帶的某些參數(shù),來調(diào)整 process 上 warning 事件觸發(fā)時的行為error, Local message) { // ... Local caught = fatal_exception_function->Call(process_object, 1, &error); // ... if (false == caught->BooleanValue()) { ReportException(env, error, message); exit(1); } }
具體來說,這些參數(shù)是:--no-warnings,--no-deprecation,--trace-deprecation 和 --throw-deprecation。這些參數(shù)的有無信息,會先被掛載在 process 對象上:
// src/node.cc // ... if (no_deprecation) { READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); } if (no_process_warnings) { READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate())); } if (trace_warnings) { READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate())); } if (throw_deprecation) { READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); }
然后根據(jù)這些信息,控制行為:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... NativeModule.require("internal/process/warning").setup(); } // ... startup(); });
// lib/internal/process/warning.js "use strict"; const traceWarnings = process.traceProcessWarnings; const noDeprecation = process.noDeprecation; const traceDeprecation = process.traceDeprecation; const throwDeprecation = process.throwDeprecation; const prefix = `(${process.release.name}:${process.pid}) `; exports.setup = setupProcessWarnings; function setupProcessWarnings() { if (!process.noProcessWarnings) { process.on("warning", (warning) => { if (!(warning instanceof Error)) return; const isDeprecation = warning.name === "DeprecationWarning"; if (isDeprecation && noDeprecation) return; const trace = traceWarnings || (isDeprecation && traceDeprecation); if (trace && warning.stack) { console.error(`${prefix}${warning.stack}`); } else { var toString = warning.toString; if (typeof toString !== "function") toString = Error.prototype.toString; console.error(`${prefix}${toString.apply(warning)}`); } }); } // ... }
具體行為的話,文檔中已經(jīng)有詳細說明,邏輯總結(jié)來說,就是按需將警告打印到控制臺,或者按需拋出特定的異常。其中 NativeModule 對象為 Node.js 在當前的函數(shù)體的局部作用域內(nèi),實現(xiàn)的一個最小可用的模塊加載器,具有緩存等基本功能。
為 process 添加上 stdin, stdout 和 stderr 屬性通常為 tty.ReadStream 類和 tty.WriteStream 類的實例:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... NativeModule.require("internal/process/stdio").setup(); } // ... startup(); });
// lib/internal/process/stdio.js // ... function setupStdio() { var stdin, stdout, stderr; process.__defineGetter__("stdout", function() { if (stdout) return stdout; stdout = createWritableStdioStream(1); // ... return stdout } process.__defineGetter__("stderr", function() { if (stderr) return stderr; stderr = createWritableStdioStream(2); // ... return stderr; }); process.__defineGetter__("stdin", function() { if (stdin) return stdin; var tty_wrap = process.binding("tty_wrap"); var fd = 0; switch (tty_wrap.guessHandleType(fd)) { case "TTY": var tty = require("tty"); stdin = new tty.ReadStream(fd, { highWaterMark: 0, readable: true, writable: false }); break; // ... } return stdin; } } function createWritableStdioStream(fd) { var stream; var tty_wrap = process.binding("tty_wrap"); // Note stream._type is used for test-module-load-list.js switch (tty_wrap.guessHandleType(fd)) { case "TTY": var tty = require("tty"); stream = new tty.WriteStream(fd); stream._type = "tty"; break; // ... } // ... }為 process 添加上 nextTick 方法
具體的做法便是將注冊的回調(diào)推進隊列中,等待事件循環(huán)的下一次 Tick ,一個個取出執(zhí)行:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... NativeModule.require("internal/process/next_tick").setup(); } // ... startup(); });
// lib/internal/process/next_tick.js "use strict"; exports.setup = setupNextTick; function setupNextTick() { var nextTickQueue = []; // ... var kIndex = 0; var kLength = 1; process.nextTick = nextTick; process._tickCallback = _tickCallback; function _tickCallback() { var callback, args, tock; do { while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; args = tock.args; _combinedTickCallback(args, callback); if (1e4 < tickInfo[kIndex]) tickDone(); } tickDone(); } while (tickInfo[kLength] !== 0); } function nextTick(callback) { if (typeof callback !== "function") throw new TypeError("callback is not a function"); if (process._exiting) return; var args; if (arguments.length > 1) { args = new Array(arguments.length - 1); for (var i = 1; i < arguments.length; i++) args[i - 1] = arguments[i]; } nextTickQueue.push(new TickObject(callback, args)); tickInfo[kLength]++; } } // ...為 process 添加上 hrtime, kill, exit 方法
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... _process.setup_hrtime(); _process.setupKillAndExit(); } // ... startup(); });
這些功能的核心實現(xiàn)也重度依賴于 c++ 函數(shù):
hrtime 方法依賴于 libuv 提供的 uv_hrtime() 函數(shù)
kill 方法依賴于 libuv 提供的 uv_kill(pid, sig) 函數(shù)
exit 方法依賴于 c++ 提供的 exit(code) 函數(shù)
初始化全局的一些 timer 方法和 console 等對象這些初始化都干的十分簡單,直接賦值:
// lib/internal/bootstrap_node.js "use strict"; (function(process) { function startup() { // ... setupGlobalVariables(); if (!process._noBrowserGlobals) { setupGlobalTimeouts(); setupGlobalConsole(); } function setupGlobalVariables() { global.process = process; // ... global.Buffer = NativeModule.require("buffer").Buffer; process.domain = null; process._exiting = false; } function setupGlobalTimeouts() { const timers = NativeModule.require("timers"); global.clearImmediate = timers.clearImmediate; global.clearInterval = timers.clearInterval; global.clearTimeout = timers.clearTimeout; global.setImmediate = timers.setImmediate; global.setInterval = timers.setInterval; global.setTimeout = timers.setTimeout; } function setupGlobalConsole() { global.__defineGetter__("console", function() { return NativeModule.require("console"); }); } } // ... startup(); });
值得注意的一點是,由于 console 是通過 __defineGetter__ 賦值給 global 對象的,所以在嚴格模式下給它賦值將會拋出異常,而非嚴格模式下,賦值將被忽略。
開始執(zhí)行用戶執(zhí)行指定的 JavaScript 代碼這一部分的邏輯已經(jīng)在之前的文章中有所闡述,這邊就不再重復說明啦。
最后還是再次總結(jié)下:
lib/internal/bootstrap_node.js 中的代碼 為 Node.js 執(zhí)行后第一段被執(zhí)行的 JavaScript 代碼,從 src/node.cc 中的 node::LoadEnvironment 被調(diào)用
lib/internal/bootstrap_node.js 主要進行了一些初始化工作:
初始化全局 process 對象上的部分屬性 / 行為
添加接收到 uncaughtException 事件時的默認行為
根據(jù) Node.js 啟動時參數(shù),調(diào)整 warning 事件的行為
添加上 stdin,stdout 和 stderr 屬性
添加上 nextTick,hrtime,exit 方法
初始化全局的一些 timer 方法
初始化全局 console 等對象
開始執(zhí)行用戶執(zhí)行指定的 JavaScript 代碼
參考https://github.com/nodejs/node/blob/master/src/node.cc
https://github.com/nodejs/node/blob/master/src/node_javascript.cc
https://github.com/nodejs/node/blob/master/lib/internal/process.js
https://github.com/nodejs/node/blob/master/lib/internal/process/next_tick.js
https://github.com/nodejs/node/blob/master/lib/internal/process/stdio.js
https://github.com/nodejs/node/blob/master/lib/internal/process/warning.js
https://github.com/nodejs/node/blob/master/lib/internal/bootstrap_node.js
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79329.html
摘要:具體調(diào)用鏈路如圖函數(shù)主要是解析啟動參數(shù),并過濾選項傳給引擎。查閱文檔之后發(fā)現(xiàn),通過指定參數(shù)可以設(shè)置線程池大小。原來的字節(jié)碼編譯優(yōu)化還有都是通過多線程完成又繼續(xù)深入調(diào)查,發(fā)現(xiàn)環(huán)境變量會影響的線程池大小。執(zhí)行過程如下調(diào)用執(zhí)行。 作者:正龍 (滬江Web前端開發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。 隨著Node.js的普及,越來越多的開發(fā)者使用Node.js來搭建環(huán)境,也有很多公司開始把...
摘要:具體調(diào)用鏈路如圖函數(shù)主要是解析啟動參數(shù),并過濾選項傳給引擎。查閱文檔之后發(fā)現(xiàn),通過指定參數(shù)可以設(shè)置線程池大小。原來的字節(jié)碼編譯優(yōu)化還有都是通過多線程完成又繼續(xù)深入調(diào)查,發(fā)現(xiàn)環(huán)境變量會影響的線程池大小。執(zhí)行過程如下調(diào)用執(zhí)行。 作者:正龍 (滬江Web前端開發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。 隨著Node.js的普及,越來越多的開發(fā)者使用Node.js來搭建環(huán)境,也有很多公司開始把...
摘要:提起中的模塊,就會想到用去加在引用那個模塊。看了不少博客,加載機制明白了,腦子里總是稀里糊涂的知道會每個文件會被文件的源碼包裹,自然也就有文件中的命令了。于是想寫寫記錄自己的整個過程。這就是幾個的關(guān)系。 提起nodejs中的模塊,就會想到用require去加在引用那個模塊。看了不少博客,加載機制明白了,腦子里總是稀里糊涂的知道會每個文件會被(function (exports, req...
摘要:第三次第四次設(shè)想,如果傳入的參數(shù)值特別大,那么這個調(diào)用棧將會非常之大,最終可能超出調(diào)用棧的緩存大小而崩潰導致程序執(zhí)行失敗。注意尾遞歸不一定會將你的代碼執(zhí)行速度提高相反,可能會變慢。 譯者按: 程序員應(yīng)該知道遞歸,但是你真的知道是怎么回事么? 原文: All About Recursion, PTC, TCO and STC in JavaScript 譯者: Fundebug ...
摘要:例如,在方法中,如果需要主從進程之間建立管道,則通過環(huán)境變量來告知從進程應(yīng)該綁定的相關(guān)的文件描述符,這個特殊的環(huán)境變量后面會被再次涉及到。 文:正龍(滬江網(wǎng)校Web前端工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處 之前的文章走進Node.js之HTTP實現(xiàn)分析中,大家已經(jīng)了解 Node.js 是如何處理 HTTP 請求的,在整個處理過程,它僅僅用到單進程模型。那么如何讓 Web 應(yīng)用擴展到...
閱讀 3137·2021-11-08 13:18
閱讀 2282·2019-08-30 15:55
閱讀 3607·2019-08-30 15:44
閱讀 3067·2019-08-30 13:07
閱讀 2782·2019-08-29 17:20
閱讀 1949·2019-08-29 13:03
閱讀 3410·2019-08-26 10:32
閱讀 3225·2019-08-26 10:15