本文,我们从一个简单的例子来探知一下 Node.js 的运行机制。事先做如下准备:
# test.js var fs = require('fs'); fs.readFile('./test.js', {encoding: 'utf-8'}, function(e, content) { console.log(content); });
每一份优秀的开源代码都是一个珍贵的宝藏。要想挖掘这份宝藏,首先要会“寻龙点穴”,找到“入口”。其中一种方式是开启 debug 模式,在关键位置打上断点,看一下运行的堆栈,以此作为入口深入下去。
我们选在 src/node_file.cc
中的 Read
方法上“下铲”,打下第一个断点。 Read
函数代码如下:
static void Read(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); if (args.Length() < 2) return TYPE_ERROR("fd and buffer are required"); if (!args[0]->IsInt32()) return TYPE_ERROR("fd must be a file descriptor"); if (!Buffer::HasInstance(args[1])) return TYPE_ERROR("Second argument needs to be a buffer"); // code if (req->IsObject()) { ASYNC_CALL(read, req, fd, &uvbuf, 1, pos); // 注意!这里会调用底层的 read 接口,还用了 ASYNC_CALL 宏。 } else { SYNC_CALL(read, 0, fd, &uvbuf, 1, pos) args.GetReturnValue().Set(SYNC_RESULT); } }
开始 debug,程序中断的时候,看下调用栈:
可以看到,程序的入口在 node_main.cc
文件中,中间会调用 v8 引擎执行 javascript 代码。当 js 代码读取文件的时候,会调用 node_file.cc
当中的 Read
接口。
你会在 Read
方法末尾看到这段代码:
if (req->IsObject()) { ASYNC_CALL(read, req, fd, &uvbuf, 1, pos); // 注意!这里会调用底层的 read 接口,还用了 ASYNC_CALL 宏。 } else { SYNC_CALL(read, 0, fd, &uvbuf, 1, pos) args.GetReturnValue().Set(SYNC_RESULT); }
搜索一下 ASYNC_CALL
,你会找到它的定义:
#define ASYNC_DEST_CALL(func, req, dest, ...) / Environment* env = Environment::GetCurrent(args); / CHECK(req->IsObject()); / FSReqWrap* req_wrap = FSReqWrap::New(env, req.As<Object>(), #func, dest); / int err = uv_fs_ ## func(env->event_loop(), / &req_wrap->req_, / __VA_ARGS__, / After); / req_wrap->Dispatched(); / if (err < 0) { / uv_fs_t* uv_req = &req_wrap->req_; / uv_req->result = err; / uv_req->path = nullptr; / After(uv_req); / } / args.GetReturnValue().Set(req_wrap->persistent()); #define ASYNC_CALL(func, req, ...) / ASYNC_DEST_CALL(func, req, nullptr, __VA_ARGS__)
读到这儿,你会发现 Read
方法像是一个代理,继续往下跟,你会发现它调用的就是经常听说的 libuv
的底层接口。仔细看一遍代码,你会发现它调用的是 uv_fs_read
方法。
到这儿,你基本能够了解一个简单的读取文件脚本的运行机制了。各种大神博客里面介绍的 v8 引擎,libuv 等在这儿都找到了。
注意: 程序第一次中断的时候,是在加载 fs
模块。因为你继续运行下去,你会发现代码执行的是 SYNC_CALL(read...)
,而不是 ASYNC_CALL(read...)
。等第二次程序中断的时候,再往下 debug。
首先,下载最新的 Node.js 代码。
然后,进入代码文件,编译代码。
$ ./configure $ make -j 4
编译好之后,选择 File -> import project...
导入 Node.js 代码。
由于 clion 采用 cmake 构建项目,所以需要修改 CMakeLists.txt 添加自定义任务,修改后的代码如下:
cmake_minimum_required(VERSION 3.3) project(node_master) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") add_custom_target(node COMMAND make -C $(node_SOURCE_DIR) node_g CLION_EXE_DIR='./out/Debug')
最后,配置 debug 选项,要配置的内容还挺多:
Current Directory
test.js
作为命令行参数 配好之后就可以 debug 了。