• node 模块载入原理【1】


    简单介绍

    我们会从简单的模块载入原理来开始,尝试阅读下 Node.js 源代码。首先我们知道 Node.js 的源代码主要是由 C++ 和 JavaScript 编写的,JS 部分主要在 lib 目录下,而 C++ 部分主要在 src 目录下。

    模块加载主要是分四种类型的模块:

    1. C++ 核心模块:主要在 src 目录下,比如 node_file.cc
    2. Node.js 内部模块:和 C++ 核心模块不同,在源码的 lib 目录下,以同名的 JS 源码来实现,实际上 Node.js 内置模块是对 C++ 核心模块的更高层次的封装,比如 fs、http
    3. 用户源码模块
    4. C++ 扩展模块:插件是用 C++ 编写的动态链接共享对象。 require() 函数可以将插件加载为普通的 Node.js 模块。 插件提供了 JavaScript 和 C/C++ 库之间的接口。

    这次介绍的是 C++ 核心模块加载的源码分析

    C++ 核心模块加载过程

    一句话概括:每个 C++ 核心模块源码末尾都有一个宏调用,将模块注册到 C++ 核心模块的链表当中去,以供 internalBinding 获取它。
    从这句话可以得到两个步骤:注册和获取。

    总的流程

    1. 入口文件执行
      我们知道,Node.js 的执行入口在 node.cc 的 Start 函数
    int Start(int argc, char** argv) {}
    
    1. 分支一:C++ 模块注册到链表中
      我们来看看 Start 函数都做了什么,首先调用了 InitializeOncePerProcess,对 Node.js 和 V8 做初始化处理。从这个函数找下去,可以找到 C++ 模块注册到链表
    InitializationResult result = InitializeOncePerProcess(argc, argv);
    
    1. 分支二:获取 C++ 模块
      在 Start 函数继续找下去,可以看到实例化了 NodeMainInstance,调用了 Run 函数。从这个函数找下去,可以找到获取 C++ 模块的过程
    NodeMainInstance main_instance(...);
    result.exit_code = main_instance.Run();
    

    分支一:C++ 模块注册到链表中

    1、前置链路

    从上面的 InitializeOncePerProcess 开始往下找,可以看到调用了初始化 node 的函数 InitializeNodeWithArgs 函数

    result.exit_code = InitializeNodeWithArgs(&(result.args), &(result.exec_args), &errors);
    

    继续找,看到它调用了 node_binding.cc 中的 RegisterBuiltinModules,作用是注册 C++ 模块

    binding::RegisterBuiltinModules();
    

    2、C++ 模块注册函数的调用

    那我们就来看一下,RegisterBuiltinModules 这个函数都做了什么,主要做了两件事:一是定义了 V 这个宏,它接收 modname 这个参数;二是调用 NODE_BUILTIN_MODULES

    void RegisterBuiltinModules() {
    #define V(modname) _register_##modname();
    NODE_BUILTIN_MODULES(V)  // module name 可以在这个宏找到,这里不多写了
    #undef V
    }
    

    然后我们可以看到 NODE_BUILTIN_MODULES 其实也是一个宏定义:

    #define NODE_BUILTIN_MODULES(V)                                             NODE_BUILTIN_STANDARD_MODULES(V)                                            
    ...
    

    其中 NODE_BUILTIN_STANDARD_MODULES 的定义是这样的:

    #define NODE_BUILTIN_STANDARD_MODULES(V)                                 
      V(async_wrap)                                                          
      V(buffer)                                                              
      ...
    

    也就是说,c++的预处理后,会变成下面的函数体

    void RegisterBuiltinModules() {  
      _register_async_wrap();  
      _register_buffer();  
    .... }
    

    也就是说,最终去调用的是 _register_async_wrap_register_buffer ...这些函数,好了,那么这些函数是在哪定义的呢?

    3、C++ 模块注册函数的定义

    从上面宏定义后面,可以找到这样的注释:
    The definitions are in each module's implementation when calling the NODE_MODULE_CONTEXT_AWARE_INTERNAL.
    也就是说,定义是在每个模块调用 NODE_MODULE_CONTEXT_AWARE_INTERNAL 时进行的~

    好的于是我们看看 fs 模块对应的 C++ 文件 node_file.cc ,看到最后一行调用了 NODE_MODULE_CONTEXT_AWARE_INTERNAL

    NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs, node::fs::Initialize)
    

    继续,在 node_binding.h 看到它调用了 NODE_MODULE_CONTEXT_AWARE_CPP

    #define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc)             
      NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL)
    

    继续,在 node_binding.h 中,定义了 _register_##modname 执行时调用了 node_module_register

    #define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags)     
      void _register_##modname() { node_module_register(&_module); }
    

    好的最后一步,在 node_binding.cc 中找到了它的定义,将传入的 node module 插到了链表 modlist_internal 中,后面查找时就会在这里找了~

    extern "C" void node_module_register(void* m) {
      struct node_module* mp = reinterpret_cast<struct node_module*>(m);
      if (mp->nm_flags & NM_F_INTERNAL) {
        mp->nm_link = modlist_internal;
        modlist_internal = mp;
      }
    }
    

    好了~到这里我们就知道,C++ 模块的插入链表的过程了,简单来说就是:每个 C++ 内置模块源码末尾都有一个宏调用,编译模块代码的时候,将模块注册到 C++ 核心模块的链表 modlist_internal 当中去,记住链表的名字哦。

    分支二:获取 C++ 模块

    1、前置链路

    好,这部分从 main_instance.Run() 开始,可以看到他的定义在 node_main_instance.cc 里面
    执行了 RunBootstrapping

    if (env->RunBootstrapping().IsEmpty()) {
      *exit_code = 1;
    }
    

    RunBootstrapping 执行了 node.cc 中的 BootstrapInternalLoaders

    if (BootstrapInternalLoaders().IsEmpty()) {
      return MaybeLocal<Value>();
    }
    

    接着它执行了 internal/bootstrap/loaders 这个文件

    if (!ExecuteBootstrapper(this, "internal/bootstrap/loaders", &loaders_params, &loaders_args).ToLocal(&loader_exports))
    

    2、从链表获取 C++ 模块

    在这个文件的注释中,解释了它的作用:
    This file is compiled as if it's wrapped in a function with arguments passed by node::RunBootstrapping()
    global process, getLinkedBinding, getInternalBinding, primordials
    可以看出这个 js 文件被包裹在一个函数中,这个函数接收四个参数,这四个参数哪里来的呢
    再去 C++ 文件里找

    // Create binding loaders
      std::vector<Local<String>> loaders_params = {
          process_string(),
          FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"),
          FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"),
          primordials_string()};
      std::vector<Local<Value>> loaders_args = {
          process_object(),
          NewFunctionTemplate(binding::GetLinkedBinding)
          NewFunctionTemplate(binding::GetInternalBinding)
          primordials()};
    

    嗯,其中 getLinkedBindinggetInternalBinding 两个函数,它们会负责 JS 和 C++ 的交互,getInternalBinding 是获取核心模块的函数,另外的一个应该是 C++ 扩展相关的,那我们来看下 GetInternalBinding
    在 node_binding.cc 中可以看到,它内部执行了 get_internal_module

    node_module* mod = get_internal_module(*module_v);
    

    来~node_binding.cc 里面的 get_internal_module 去执行了 FindModule 传入了刚才我们注册了的链表 modlist_internal,模块标识,和标识内部模块的一个常量

    node_module* get_internal_module(const char* name) {
      return FindModule(modlist_internal, name, NM_F_INTERNAL);
    }
    

    最后我们看看 FindModule

    inline struct node_module* FindModule(struct node_module* list,
                                          const char* name,
                                          int flag) {
      struct node_module* mp;
      for (mp = list; mp != nullptr; mp = mp->nm_link) {
        if (strcmp(mp->nm_modname, name) == 0) break;
      }
      CHECK(mp == nullptr || (mp->nm_flags & flag) != 0);
      return mp;
    }
    

    嗯,从 modlist_internal 链表中找到模块并返回了,耶

    整体的流程图

    后续

    最后说到 loader.js 接收到 getInternalBinding 就停止了,那在继续看一下,他定义了 process.bindingprocess._linkedBinding 分别对应刚才的 getInternalBindinggetLinkedBinding,另外还定义了 internalBinding

    internalBinding = function internalBinding(module) {
        let mod = bindingObj[module];
        if (typeof mod !== 'object') {
          mod = bindingObj[module] = getInternalBinding(module);
          moduleLoadList.push(`Internal Binding ${module}`);
        }
        return mod;
      };
    

    这个函数用到的地方很多,比如在 fs.js 中,或是通过 CommonJS 或 ESModule 的方式加载 JS 模块时,也会用到这个函数,后面还有很多可以看~

    参考文档

  • 相关阅读:
    shell脚本day06-sed
    shell脚本day05-交互式输入与for语句
    shell脚本day04-if语句
    shell脚本day04-grep与正则表达式
    shell脚本day03-编程原理
    shell脚本day02-重定向与管道符
    编程原理大致介绍
    进程管理
    Linux网络
    shell脚本--grep以及正则表达式
  • 原文地址:https://www.cnblogs.com/ubuntugx/p/12592584.html
Copyright © 2020-2023  润新知