node.js源码研究—模块组织加载
发布于 13 年前 作者 bang590 17986 次浏览 最后一次编辑是 8 年前

粗略研究了一下node.js源码,它有8000行C++代码,2000行javascript代码,来看看js和C++间是怎么组织连接起来,以及各个模块是怎样互相调用的。 <br/> <br/>本文使用的node.js版本是0.4.8,可以在<a href=“https://github.com/joyent/node/tree/v0.4.8” target="_blank">https://github.com/joyent/node/tree/v0.4.8</a>这里看到源码。<!–more–> <br/><h3><strong>js2c.py</strong></h3> <br/>node.js使用了V8附带的js2c.py工具把所有内置的js代码转换成C++里的数组,生成node_natives.h直接include到程序中,成了C++源码的一部分。这样做能提高内置js模块的编译效率。 <br/> <br/>node.js里内置的javascript包括了主程序<a href=“https://github.com/joyent/node/blob/v0.4.8/src/node.js” target="_blank">src/node.js</a>和模块程序<a href=“https://github.com/joyent/node/tree/v0.4.8/lib” target="_blank">lib/.js</a>,通过js2c.py让每一个js文件都生成一个源码数组,存放在build/src/node_natives.h里,node_natives.h在node.js编译后才会生成(编译的脚本wscript中调用了js2c.py),可以看到大致的结构如下: <br/> <br/><pre><code> <br/>namespace node { <br/>const char node_native[] = {47, 47, 32, 67, 112 …} <br/>const char console_native[] = {47, 47, 32, 67, 112 …} <br/>const char buffer_native[] = {47, 47, 32, 67, 112 …} <br/>… <br/>} <br/>struct _native { const char name; const char* source; size_t source_len;}; <br/>static const struct _native natives[] = { <br/>{ “node”, node_native, sizeof(node_native)-1 }, <br/>{ “dgram”, dgram_native, sizeof(dgram_native)-1 }, <br/>{ “console”, console_native, sizeof(console_native)-1 }, <br/>{ “buffer”, buffer_native, sizeof(buffer_native)-1 }, <br/>… <br/>} <br/></pre></code> <br/>这个文件被包含在<a href=“https://github.com/joyent/node/blob/v0.4.8/src/node_javascript.cc” target="_blank">node_javascript.cc</a>里,node_javascript.cc提供了两个接口: <br/>MainSource() 处理node_native源码返回v8::Handle类型的数据可供编译。 <br/>DefineJavaScript(target) 把其他所有模块源码变成v8::Handle类型后加载到传入的target对象上。 <br/> <br/>所有的js模块都被转换成了C数组,接下来看看它们怎么执行和互相调用。 <br/><!–more–> <br/><h3>执行js主程序/传递process</h3> <br/>先看看node.js的底层C++传递给javascript的一个变量process,在一开始运行node.js时,程序会先配置好process <br/><pre><code> <br/>Handleprocess = SetupProcessObject(argc, argv); <br/></pre></code> <br/>然后把process作为参数去调用js主程序src/node.js返回的函数,这样process就传递到javascript里了。 <br/><pre><code> <br/>//node.cc <br/>//通过MainSource()获取已转化的src/node.js源码,并执行它 <br/>Local f_value = ExecuteString(MainSource(), IMMUTABLE_STRING(“node.js”));</pre></code> <br/> <br/><pre><code>//执行src/node.js后获得的是一个函数,从node.js源码可以看出: <br/>//node.js <br/>//(function(process) { <br/>// global = this; <br/>// … <br/>//}) <br/>Local f = Local::Cast(f_value); <br/> <br/></pre></code> <br/> <br/><pre><code>//创建函数执行环境,调用函数,把process传入 <br/>Localglobal = v8::Context::GetCurrent()->Global(); <br/>Local args[1] = { Local::New(process) }; <br/>f->Call(global, 1, args); <br/></pre></code> <br/><h3>C++模块</h3> <br/>node.js的模块除了lib/.js里用js语言编写的以外,还有一些使用C++编写,像os/stdio/crypto/buffer等。这些模块都通过<a href=“https://github.com/joyent/node/blob/v0.4.8/src/node.h” target="_blank">node.h</a>提供的NODE_MODULE方法存储在变量_module里。<a href=“https://github.com/joyent/node/blob/v0.4.8/src/node_extensions.cc” target="_blank">node_extensions.cc</a>提供了get_builtin_module(name)接口获取这些模块。 <br/><h3>process.binding/C++模块加载</h3> <br/>process提供的一个获取模块的接口是binding,它的实现Binding()函数可以在<a href=“https://github.com/joyent/node/blob/v0.4.8/src/node.cc” target="_blank">node.cc</a>找到。 <br/><pre><code> <br/>Persistent binding_cache; <br/>static Handle Binding(const Arguments& args) { <br/>HandleScope scope; <br/>Local module = args[0]->ToString(); <br/>String::Utf8Value module_v(module); <br/>node_module_struct modp;</pre></code> <br/> <br/><pre><code>if (binding_cache.IsEmpty()) { <br/>binding_cache = Persistent::New(Object::New()); <br/>} <br/>Local exports; <br/>if (binding_cache->Has(module)) { <br/>exports = binding_cache->Get(module)->ToObject(); <br/> <br/>} else if ((modp = get_builtin_module(*module_v)) != NULL) { <br/>exports = Object::New(); <br/>modp->register_func(exports); <br/>binding_cache->Set(module, exports); <br/> <br/>} else if (!strcmp(*module_v, “constants”)) { <br/>exports = Object::New(); <br/>DefineConstants(exports); <br/>binding_cache->Set(module, exports); <br/> <br/>#ifdef POSIX <br/>} else if (!strcmp(*module_v, “io_watcher”)) { <br/>exports = Object::New(); <br/>IOWatcher::Initialize(exports); <br/>binding_cache->Set(module, exports); <br/>#endif <br/> <br/>} else if (!strcmp(module_v, “natives”)) { <br/>exports = Object::New(); <br/>DefineJavaScript(exports); <br/>binding_cache->Set(module, exports); <br/> <br/></pre></code> <br/> <br/><pre><code> } else { <br/>return ThrowException(Exception::Error(String::New(“No such module”))); <br/>} <br/>return scope.Close(exports); <br/>} <br/></pre></code> <br/>从源码可以看到,调用process.binding时,先看缓存里是否已经存在此模块,不存在再调用get_builtin_module查找C++内置模块,找到的话获取后绑定在exports上,在最后返回exports。 <br/> <br/>此外还有针对其他模块的特殊处理,其中natives模块就是调用上文提到的DefineJavaScript(exports)接口获取到所有内置的js模块绑定在exports上。 <br/> <br/>现在在js上需要调用C++提供的模块只需要调用process.binding就行了,例如 <br/><pre><code> <br/>var stdio = process.binding(“stdio”) <br/></pre></code> <br/><h3>js模块加载</h3> <br/><a href=“https://github.com/joyent/node/blob/v0.4.8/src/node.js” target="_blank">src/node.js</a>上实现了一个NativeModule对象用于管理js模块,它通过调用process.binding(“natives”)把所有内置的js模块放在NativeModule._source上,并提供require接口供调用。在require里会给代码加一层包装,把一些变量传给这个模块。 <br/><pre><code> <br/>NativeModule.wrapper = [ <br/>’(function (exports, require, module, __filename, __dirname) { ‘, <br/>’\n});’ <br/>]; <br/></pre></code> <br/>再用process提供的其中一个js编译接口process.runInThisContext执行代码。 <br/><pre><code> <br/>var fn = runInThisContext(source, this.filename, true); <br/>fn(this.exports, NativeModule.require, this, this.filename); <br/></pre></code> <br/>于是在主程序src/node.js上可以调用NativeModule.require(“net”)去加载net模块,在lib/.js的各个js模块里能通过调用传进来的require()去加载其他内置js模块。 <br/><h3>总结流程</h3> <br/>粗略总结一下加载模块的流程: <br/> <br/>加载C++模块(以stdio为例): <br/>process.binding(“stdio”) -> get_builtin_module(“stdio”) -> _module -> NODE_MODULE(node_stdio, node::Stdio::Initialize)(定义) <br/> <br/>加载js模块(以net为例) <br/>require(“net”) -> NativeModule.require(“net”) -> process.binding(“natives”)[“net”] -> DefineJavaScript() -> natives[] -> node_natives.h

回到顶部