overall
图1:Node.js overview
本文所做的研究基于Node.js v0.12.4 Linux版本。
熟悉Node.js的同学都知道Node.js由v8 engine,内建的模块以及libuv组成。这是一个宽泛的介绍。本文通过一张图来详细了解以下Node.js运行中涉及到的各路英雄。
对于Node.js而言,可以将v8 engine和libuv看成是一个库。虽然事实上,在编译Node时,二者是以源代码方式直接编译进可执行文件node中去的。v8 engine和libuv源代码放在node-vXXX/deps目录下。 本文先依次介绍v8 engien和libuv,最后结合图1讲述Node.js如何将这二者优雅地结合在一起的。
v8 engine
V8 engine的详细说明可以参考V8的在线文档(https://developers.google.com/v8/intro)。
#include "include/v8.h"
#include "include/libplatform/libplatform.h"
using namespace v8;
int main(int argc, char* argv[]) {
// Initialize V8.
V8::InitializeICU();
Platform* platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
V8::Initialize();
// Create a new Isolate and make it the current one.
Isolate* isolate = Isolate::New();
{
Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
HandleScope handle_scope(isolate);
// Create a new context.
Local<Context> context = Context::New(isolate);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World!'");
// Compile the source code.
Local<Script> script = Script::Compile(source);
// Run the script to get the result.
Local<Value> result = script->Run();
// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(result);
printf("%s\n", *utf8);
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
V8::Dispose();
V8::ShutdownPlatform();
delete platform;
return 0;
}
上面是一个典型的v8使用示例。总体而言,v8里面包含几个重要的概念:isolate, context, handles。
- isolate代表一个v8 engine 实例。各个isolate之间的状态完全独立,位于一个isolate中的object无法在另外一个isolate中被使用。
- context代表一个执行JS代码的虚拟机,在这个虚拟机中已经集成了一些object如Math, JSON以及function如Date(), RegExp(), String(), Object(), and Array()。一个isolate中可以同时存在多个context,且可以在这些context中切换。如图2所示。
- 在v8 engine中通过handle访问存在于heap上的JS object。handle有不少种类,包括Local, Persistent, Eternal等。在v8中有一个handle stack用于管理这些handles,如图3所示。
图2:v8 engine context
图3: v8 engine handle stack
libuv
libuv是一个多平台的,主要用于处理事件驱动的异步I/O模型库。可以参考http://docs.libuv.org/en/v1.x/ 获取更详细的信息。
图4: libuv模块分布图
取决于所运行的操作系统平台,libuv通过epoll, IOCP等方式来实现异步network I/O。而对于File I/O, DNS的操作则依赖于Thread Pool来实现。如图4所示。 http://nikhilm.github.io/uvbook/ 是一个非常好的libuv入门以及API参考文档。 libuv的核心部分是I/O (or event) loop。如图5所示,该loop负责调用callback function、运行idle function、执行事件polling轮询等操作。
图5:libuv I/O(or event) loop
Node.js
介绍完v8 engine和libuv,让我们把目光再次转移到图1上来。除了牵手v8 engine和libuv外,Node.js本身也做了不少工作。提供了包括JS模块,C++模块(包括各种wrap)在内的核心模块供开发者使用。
如果按照Node代码的命名方法,这些JS模块叫“native module”,而C++模块叫“builtin module”。Node.js的代码包括libuv基本上是以C++写成的,将这些用C++写成的模块叫builtin也是合情合理。而native则喻为与Node之生俱来,也与第三方开发的以示区别。
内建的C++模块位于Node源代码的src目录,而JS模块则位于lib目录。通常C++模块是作为内部模块导出给lib/*.js模块使用,如C++所写的tcp_wrap模块是导出给net.js模块使用,而C++所写的crypto则会提供给crypto.js使用。
图6是一个关于native module和builtin module如何编译以及加载到内存的过程。 对于Node自身提供的模块,其实无论是JS模块还是C++模块,最终都在编译生成可执行文件node时,嵌入到了ELF文件里面。而对这两者的提取方式却不一样。对于JS模块,使用process.binding(“natives”),而对于C++模块则直接用get_builtin_module()得到。
图6:native module和buildin module编译和加载过程
当我们执行 node test.js 执行我们所写的JS代码时,node_main.cc首先被调用。这一步主要的工作是初始化v8 engine和libuv执行环境。 uv_run(env->event_loop(), UV_RUN_ONCE) 用于启动libuv event loop。而我们的JS代码 test.js则会作为输入传递给CreateEnvironment(),最终交由v8 engine执行。
查看Node.cc代码,可以看到若干与v8 engine交互的工作,包括context, handle等的创建、通过v8的函数模版创建类的constructor以及类的prototype object等操作。 其中通过函数模版创建类的constructor以及类的prototype object等操作非常有意思。
在JavaScript中,类是基于prototype继承的方式来实现的。一个JS类包含一个constructor和prototype object以及包括其它如__proto__在内的property。通过v8提供的C++ API来完成这些操作其实是生成与JS的类概念相同的代码。我后面会写一篇文章介绍“callback from libuv to JS”,里面会详细介绍对于一个具体的callback function如何通过这种方式来实现从C++世界到JavaScript世界的穿越。
转载本文请注明作者和出处,请勿用于任何商业用途。如需帮助,请QQ联系作者:229848501