前言
之前用過(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é)束吧廷支,大家休息~