转载

Node.js 运行机制探知

本文,我们从一个简单的例子来探知一下 Node.js 的运行机制。事先做如下准备:

  • 下载最新的 Node.js 代码
  • 搭建 debug 环境,我用的是 clion ,参考下面的 clion debug 环境搭建步骤。
  • 写一个简单的读取文件脚本,用来 debug,代码如下:
# 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.js 运行机制探知

可以看到,程序的入口在 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。

clion 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 选项,要配置的内容还挺多:

  • 选择执行 out/Debug/node 作为执行文件
  • 注意配置 Current Directory
  • 添加 test.js 作为命令行参数
  • 在 Before launch 栏中把 build 去掉

配好之后就可以 debug 了。

正文到此结束
Loading...