Node.js源碼研究之模塊組織加載
粗略研究了一下node.js源碼,它有8000行C++代碼,2000行javascript代碼,來看看js和C++間是怎么組織連接起來,以及各個模塊是怎樣互相調用的。
本文使用的node.js版本是0.4.8,可以在https://github.com/joyent/node/tree/v0.4.8這里看到源碼。
js2c.py
node.js使用了V8附帶的js2c.py工具把所有內置的js代碼轉換成C++里的數組,生成node_natives.h直接include到程序中,成了C++源碼的一部分。這樣做能提高內置js模塊的編譯效率。
node.js里內置的javascript包括了主程序src/node.js和模塊程序lib/*.js,通過js2c.py讓每一個js文件都生成一個源碼數組,存放在build/src/node_natives.h里,node_natives.h在node.js編譯后才會生成(編譯的腳本wscript中調用了js2c.py),可以看到大致的結構如下:
- namespace node {
- const char node_native[] = {47, 47, 32, 67, 112 ......}
- const char console_native[] = {47, 47, 32, 67, 112 ......}
- const char buffer_native[] = {47, 47, 32, 67, 112 ......}
- .....
- }
- struct _native { const char* name; const char* source; size_t source_len;};
- static const struct _native natives[] = {
- { "node", node_native, sizeof(node_native)-1 },
- { "dgram", dgram_native, sizeof(dgram_native)-1 },
- { "console", console_native, sizeof(console_native)-1 },
- { "buffer", buffer_native, sizeof(buffer_native)-1 },
- ....
- }
這個文件被包含在node_javascript.cc里,node_javascript.cc提供了兩個接口:
MainSource() 處理node_native源碼返回v8::Handle類型的數據可供編譯。
DefineJavaScript(target) 把其他所有模塊源碼變成v8::Handle類型后加載到傳入的target對象上。
所有的js模塊都被轉換成了C數組,接下來看看它們怎么執行和互相調用。
執行js主程序/傳遞process
先看看node.js的底層C++傳遞給javascript的一個變量process,在一開始運行node.js時,程序會先配置好process
- Handleprocess = SetupProcessObject(argc, argv);
然后把process作為參數去調用js主程序src/node.js返回的函數,這樣process就傳遞到javascript里了。
- //node.cc
- //通過MainSource()獲取已轉化的src/node.js源碼,并執行它
- Local f_value = ExecuteString(MainSource(), IMMUTABLE_STRING("node.js"));
- //執行src/node.js后獲得的是一個函數,從node.js源碼可以看出:
- //node.js
- //(function(process) {
- // global = this;
- // ....
- //})
- Local f = Local::Cast(f_value);
- //創建函數執行環境,調用函數,把process傳入
- Localglobal = v8::Context::GetCurrent()->Global();
- Local args[1] = { Local::New(process) };
- f->Call(global, 1, args);
C++模塊
node.js的模塊除了lib/*.js里用js語言編寫的以外,還有一些使用C++編寫,像os/stdio/crypto/buffer等。這些模塊都通過node.h提供的NODE_MODULE方法存儲在變量_module里。node_extensions.cc提供了get_builtin_module(name)接口獲取這些模塊。
process.binding/C++模塊加載
process提供的一個獲取模塊的接口是binding,它的實現Binding()函數可以在node.cc找到。
- Persistent binding_cache;
- static Handle Binding(const Arguments& args) {
- HandleScope scope;
- Local module = args[0]->ToString();
- String::Utf8Value module_v(module);
- node_module_struct* modp;
- if (binding_cache.IsEmpty()) {
- binding_cache = Persistent::New(Object::New());
- }
- Local exports;
- if (binding_cache->Has(module)) {
- exports = binding_cache->Get(module)->ToObject();
- } else if ((modp = get_builtin_module(*module_v)) != NULL) {
- exports = Object::New();
- modp->register_func(exports);
- binding_cache->Set(module, exports);
- } else if (!strcmp(*module_v, "constants")) {
- exports = Object::New();
- DefineConstants(exports);
- binding_cache->Set(module, exports);
- #ifdef __POSIX__
- } else if (!strcmp(*module_v, "io_watcher")) {
- exports = Object::New();
- IOWatcher::Initialize(exports);
- binding_cache->Set(module, exports);
- #endif
- } else if (!strcmp(*module_v, "natives")) {
- exports = Object::New();
- DefineJavaScript(exports);
- binding_cache->Set(module, exports);
- } else {
- return ThrowException(Exception::Error(String::New("No such module")));
- }
- return scope.Close(exports);
- }
從源碼可以看到,調用process.binding時,先看緩存里是否已經存在此模塊,不存在再調用get_builtin_module查找C++內置模塊,找到的話獲取后綁定在exports上,在最后返回exports。
此外還有針對其他模塊的特殊處理,其中natives模塊就是調用上文提到的DefineJavaScript(exports)接口獲取到所有內置的js模塊綁定在exports上。
現在在js上需要調用C++提供的模塊只需要調用process.binding就行了,例如
- var stdio = process.binding("stdio")
js模塊加載
src/node.js上實現了一個NativeModule對象用于管理js模塊,它通過調用process.binding(“natives”)把所有內置的js模塊放在NativeModule._source上,并提供require接口供調用。在require里會給代碼加一層包裝,把一些變量傳給這個模塊。
- NativeModule.wrapper = [
- '(function (exports, require, module, __filename, __dirname) { ',
- '\n});'
- ];
再用process提供的其中一個js編譯接口process.runInThisContext執行代碼。
- var fn = runInThisContext(source, this.filename, true);
- fn(this.exports, NativeModule.require, this, this.filename);
于是在主程序src/node.js上可以調用NativeModule.require(“net”)去加載net模塊,在lib/*.js的各個js模塊里能通過調用傳進來的require()去加載其他內置js模塊。
總結流程
粗略總結一下加載模塊的流程:
加載C++模塊(以stdio為例):
process.binding(“stdio”) -> get_builtin_module(“stdio”) -> _module -> NODE_MODULE(node_stdio, node::Stdio::Initialize)(定義)
加載js模塊(以net為例)
require(“net”) -> NativeModule.require(“net”) -> process.binding(“natives”)["net"] -> DefineJavaScript() -> natives[] -> node_natives.h
原文:http://cnodejs.org/blog/?p=1280
【編輯推薦】