关于node.js:Nodejs-源码分析-从-main-函数开始

3次阅读

共计 7017 个字符,预计需要花费 18 分钟才能阅读完成。


title: Node.js 源码剖析 – 从 main 函数开始
date: 2018-11-27 21:30:15
tags:

- Node.js
- Node.js 源码剖析
- 源码剖析

categories:

- Node.js 源码剖析

此文最后于四年前公布在集体站上的,现迁徙至此重发,原链接:https://laogen.site/nodejs/no…
《Node.js 源码剖析》系列目录页:https://laogen.site/nodejs/no…

小指标

晓得程序大略执行逻辑,关键点执行的程序

咱们平时在终端敲下 node app.js 后,产生了什么。

具体点,晓得 node.js 原生 (C++) 模块什么时候加载的,在哪加载的;
晓得咱们的 js 代码是在哪个环节被加载执行的;
晓得过程的主循环(事件循环)什么时候启动的;

有了这个小指标的根底,在接下来的文章中,咱们再进一步的摸索 node.js 原生模块的注册是怎么实现的,怎么获取 & 初始化的,怎么曝露给 js 环境调用的;再细说 node.js 的模块机制,咱们通常的 app.js 怎么被执行的;

<!– more –>

贴代码阐明

限于篇幅,本文只先把大体执行流程捋进去,前面再开文一块块的捋。

原代码太长,先把不影响咱们剖析的无关代码去掉,贴上来无关整体执行逻辑的代码,代码中的 // ... 正文意思是这个中央有被省略的代码。

每段代码第一行的正文都会指出源文件地位,一些代码解说会在代码段中的正文中进行;

本文不再介绍 V8 和 Libuv 的常识,会开专门的分类写 V8 和 Libuv,参考 {% post_link nodejs/nodejs-src/index Node.js 源码剖析 – 前言 %}

开捋:从 main 函数到过程主循环

main 函数

/* src/node_main.cc:93 */
int main(int argc, char* argv[]) {
  // ...
  return node::Start(argc, argv);
}

main 函数src/node_main.cc 这个文件中,这个文件次要就是寄存 main 函数

很简略,只是调用了 node::Start(),这个函数在 src/node.cc 这个文件中,接下来的外围代码都在这个文件中。

初始化 V8 引擎

/* src/node.cc:3011 */
int Start(int argc, char** argv) {
  // ...

  std::vector<std::string> args(argv, argv + argc);
  std::vector<std::string> exec_args;

  // This needs to run *before* V8::Initialize().
  Init(&args, &exec_args);

  // ...
  v8_platform.Initialize(per_process_opts->v8_thread_pool_size);
  V8::Initialize();

  // ...
  const int exit_code = Start(uv_default_loop(), args, exec_args);

  v8_platform.StopTracingAgent();
  v8_initialized = false;
  V8::Dispose();
  v8_platform.Dispose();

  return exit_code;
}

在这段代码,首先进行 V8 的初始化,而后调用了另外一个 Start(uv_loop_t*, ...)函数,最初开释资源,过程完结;

其中值得注意的一点,在初始化 V8 之前,调用了一个 Init() 函数,这个函数次要实现了 Node.js 原生 (C++) 模块的注册,就是 fs http等模块的 C++ 实现模块。

/* src/node.cc:2559 */
void Init(std::vector<std::string>* argv, std::vector<std::string>* exec_argv) {
  // ...
  // Register built-in modules
  RegisterBuiltinModules();
  // ...
}

Init() 中调用了 RegisterBuiltinModules(),它注册了所有 Node.js 原生模块,对于原生模块的注册,本文不再持续跟进去,下一篇会独自开展这一块,这里先晓得这个流程。

记住这个 RegisterBuiltinModules(),下一篇文章就从这里开始开展。

创立 Isolate 实例

/* src/node.cc:2964 */
inline int Start(uv_loop_t* event_loop,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {std::unique_ptr<ArrayBufferAllocator, decltype(&FreeArrayBufferAllocator)>
      allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator);
  // 创立 Isolate 实例
  Isolate* const isolate = NewIsolate(allocator.get());

  // ...
  int exit_code;
  {Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    // ...
    exit_code = Start(isolate, isolate_data.get(), args, exec_args);
  }
  // ...
  isolate->Dispose();
  return exit_code;
}

这个 Start() 倒也没做什么,次要工作是创立了 Isolate 实例,而后调用了另外一个 Start(Isolate*...)

过程主循环

/* src/node.cc:2868 */
inline int Start(Isolate* isolate, IsolateData* isolate_data,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {HandleScope handle_scope(isolate);
  // 创立 V8 Context 对象
  Local<Context> context = NewContext(isolate);
  Context::Scope context_scope(context);
  
  // 创立 Environment 对象,这个是 Node.js 的类
  Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());
  
  // 这外面次要实现 libuv 的初始化,以及创立 process 对象
  // 就是 Node.js 中那个全局的 process 对象,这里不细开展
  env.Start(args, exec_args, v8_is_profiling);

  {
    // ...
    // LoadEnvironment 是本文重要的关键点
    LoadEnvironment(&env);
    env.async_hooks()->pop_async_id(1);
  }

  // 上面就是过程的主循环
  {
    // ...
    bool more;
    // ...
    do {uv_run(env.event_loop(), UV_RUN_DEFAULT);
      // ...
      more = uv_loop_alive(env.event_loop());
      if (more)
        continue;

      // ...
    } while (more == true);
  }
  // ...
  return exit_code;
}

这段代码创立并应用了 js 执行须要的 context,而后创立了 Environment 对象;

这个 Environment 对象是 Node.js 源码中重要的一个对象,它是一个全局单例,定义和存储了一些重要的全局对象和函数,比方刚开始创立的 Isolate 对象、刚刚创立的 Context 对象等,留神它不是 V8 的,是 Node.js 定义的,对它的应用贯通整个 Node.js 执行的生命周期。

再上面是过程的主循环,uv_run() 启动了 Libuv 的事件循环,它也是 Node.js 过程的主循环,Libuv 会独自写文介绍。

最初说一下,两头的 LoadEnvironment() 调用,它是在程序进入主循环之前最要害的一环;

LoadEnvironment() 实现了一些 js 文件的加载和执行,其中就包含加载执行通常编写的 app.js

主循环之前

/* src/node.cc:2115 */

void LoadEnvironment(Environment* env) {HandleScope handle_scope(env->isolate());
  // ...

  // The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
  // lib/internal/bootstrap/node.js, each included as a static C string
  // defined in node_javascript.h, generated in node_javascript.cc by
  // node_js2c.
  // 加载两个重要的 js 文件:internal/bootstrap/loaders.js 
  // 和 internal/bootstrap/node.js
  Local<String> loaders_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
  MaybeLocal<Function> loaders_bootstrapper =
      GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
  Local<String> node_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
  MaybeLocal<Function> node_bootstrapper =
      GetBootstrapper(env, NodeBootstrapperSource(env), node_name);
  // ...
  // Add a reference to the global object
  Local<Object> global = env->context()->Global();

  env->SetMethod(env->process_object(), "_rawDebug", RawDebug);

  // Expose the global object as a property on itself
  // (Allows you to set stuff on `global` from anywhere in JavaScript.)
  global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global);

  // 筹备 binding 函数,上面调用 js 会作为参数传给 js 环境
  // Create binding loaders
  Local<Function> get_binding_fn =
      env->NewFunctionTemplate(GetBinding)->GetFunction(env->context())
          .ToLocalChecked();

  Local<Function> get_linked_binding_fn =
      env->NewFunctionTemplate(GetLinkedBinding)->GetFunction(env->context())
          .ToLocalChecked();

  Local<Function> get_internal_binding_fn =
      env->NewFunctionTemplate(GetInternalBinding)->GetFunction(env->context())
          .ToLocalChecked();

  // 筹备执行 internal/bootstrap/loaders.js 文件的参数
  Local<Value> loaders_bootstrapper_args[] = {env->process_object(),
    get_binding_fn,
    get_linked_binding_fn,
    get_internal_binding_fn,
    Boolean::New(env->isolate(),
                 env->options()->debug_options->break_node_first_line)
  };

  // 执行 internal/bootstrap/loaders.js 
  // Bootstrap internal loaders
  // 这个对象是用来接管执行后果的,记住是 bootstrapped_loaders,上面会用到
  Local<Value> bootstrapped_loaders;
  if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(),
                           arraysize(loaders_bootstrapper_args),
                           loaders_bootstrapper_args,
                           &bootstrapped_loaders)) {return;}

  // 筹备执行 internal/bootstrap/node.js 的参数
  // Bootstrap Node.js
  Local<Object> bootstrapper = Object::New(env->isolate());
  SetupBootstrapObject(env, bootstrapper);
  Local<Value> bootstrapped_node;
  Local<Value> node_bootstrapper_args[] = {env->process_object(),
    bootstrapper,
    // 留神,这里是下面执行 loaders.js 返回的后果对象,// 作为执行参数传给 internal/bootstrap/node.js
    bootstrapped_loaders  
  };

  // 执行 internal/bootstrap/node.js
  if (!ExecuteBootstrapper(env, node_bootstrapper.ToLocalChecked(),
                           arraysize(node_bootstrapper_args),
                           node_bootstrapper_args,
                           &bootstrapped_node)) {return;}
}

LoadEnvironment() 首先加载了两个 js 文件,这两个 js 文件的地位别离在:
lib/internal/bootstrap/loaders.jslib/internal/bootstrap/node.js

咱们 Node.js 开发者写的 app.js 其实就是在这两个 js 文件中加载并执行的,这块是最重要的逻辑之一,内容也很多,前面的文章会具体开展。

LoadEnvironment() 接下来创立了三个 binding 函数:

  • get_binding_fn
  • get_linked_binding_fn
  • get_internal_binding_fn

这 3 个 binding 函数是用来获取和加载 Node.js 原生模块的,会传入到 js 执行环境中,也就是你在 js 代码中是能够调用的,比方 process.binding('fs'),在咱们用 C++ 开发 Node.js 扩大模块的时候,也会用到,当前会具体开展。

LoadEnvironment() 接下来要执行 lib/internal/bootstrap/loaders.js,在这个 js 文件中次要定义了外部 (internal) 模块加载器(loaders)。

lib/internal/bootstrap/loaders.js 定义的模块加载器(loaders) 接下来做为执行参数,传入了 lib/internal/bootstrap/node.js,在 lib/internal/bootstrap/node.js 中会应用这些 loaders 来加载 internal 模块。

lib/internal/bootstrap/node.js 做了很多工作,这里只须要晓得,它最终加载并执行了咱们 Node.js 程序员编写的 app.js 就能够了。

到此为止,咱们就晓得了在命令行敲下 node app.js 大略产生了哪些事!

小结

这只是个大略逻辑,能够配合 Node.js 源码,再花工夫捋一捋,光靠贴的这点代码,可能还是会迷糊的。

接下来的文章,就是对这个执行逻辑中的关键点别离开展。

作者程度无限,写的也仓促,有误之处还请指出。


By Maslow (wangfugen@126.com),laf.js 作者。

lafyun.com 开源云开发平台,前端变全栈,无需服务端。

正文完
 0