前言

上篇 nodejs 启动流程分析中,遗留了几个问题。这一篇,主要讲讲模块加载流程。大家都应该熟悉 timer 模块的相关功能。我们就以 timer 为引子,一步步看下去吧。

C++ init 方法开始

下列函数都在 src/node.cc 中:

void Init(std::vector<std::string>* argv,std::vector<std::string>* exec_argv) {...// 注册内部模块。RegisterBuiltinModules();...
}
复制代码

乍一看,这个 RegisterBuiltinModules 方法应该就是关键所在了。那看看他是什么吧。

void RegisterBuiltinModules() {
#define V(modname) _register_##modname();NODE_BUILTIN_MODULES(V)
#undef V
}
复制代码

原来不是方法,而是一个宏定义。那我们再看看 NODE_BUILTIN_MODULES 这个是什么吧。下列文件位于src/node_internals.h 中:

#define NODE_BUILTIN_MODULES(V)                                               \NODE_BUILTIN_STANDARD_MODULES(V)                                            \NODE_BUILTIN_OPENSSL_MODULES(V)                                             \NODE_BUILTIN_ICU_MODULES(V)
复制代码

乖乖,又是一个宏。简直就是宏的地狱。让我们耐着性子,再往下看看。

#define NODE_BUILTIN_STANDARD_MODULES(V)                                      \V(async_wrap)                                                             \...V(timer)                                                                 \
复制代码

其他两个宏类似,就不放上来了。在这个宏里面,貌似看到了想要查找的 timer 。那我们把这个三个宏合成一下,看看是什么吧。

_register_timer();
复制代码

其他类似,不再赘述。原来这个宏就是依次调用了 _register_xx 方法。插一句,在 C++ 宏里面,## 代表字符串连接。相当于用 ## 之前的字符串 拼接上 ## 之后的字符串。现在看来,找到这个 _register_timer() 方法就是关键了。全文搜索一下 _register_timer() 方法,发现无法找到。再尝试搜索一下 register 这个关键字,可以找到一个宏如下,在文件src/node_internals.h中:

#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags)          \static node::node_module _module = {                                        \NODE_MODULE_VERSION,   /*版本号*/                                          \flags,                 /*模块类型,builtin,internal,linked*/                \nullptr,               /*nm_dso_handle. 未知*/                             \__FILE__,              /*文件名*/                                          \nullptr,               /*注册方法*/                                         \(node::addon_context_register_func) (regfunc),    /*注册上下文*/            \NODE_STRINGIFY(modname),          /*模块名*/                               \priv,                             /*私有*/                                 \nullptr                           /*下一个 node_module 模块节点*/            \};                                                                          \void _register_ ## modname() {                                              \node_module_register(&_module);                                           \}
复制代码

可以看到,这个宏定义了一个结构体,node_module 是在 src/node.h里面定义的。这个宏把模块名,注册方法,私有指针,模块类型传递进来了。然后还定义了一个方法,就是我们要找的 _register_##modname方法。其中又调用了 mode_module_register 方法。此处我们先不着急看实现,而是先去看看是谁调用了这个宏。搜索下来发现,在文件src/node_internals.h中有这样一个宏:

#define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc)                  \NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL)
复制代码

NODE_MODULE_CONTEXT_AWARE_INTERNAL 这个宏调用了上面的宏,并且将模块名,注册方法传递进来,私有变量传null,模块类型传内置模块类型。接着再看看谁调用了NODE_MODULE_CONTEXT_AWARE_INTERNAL。搜索可以看到很多处调用,我们找到我们关心的 src/timers.cc文件查看:

void Initialize(Local<Object> target,Local<Value> unused,Local<Context> context) {...env->SetMethod(target, "getLibuvNow", GetLibuvNow);...target->Set(env->context(),FIXED_ONE_BYTE_STRING(env->isolate(), "immediateInfo"),env->immediate_info()->fields().GetJSArray()).FromJust();
}
NODE_MODULE_CONTEXT_AWARE_INTERNAL(timers, node::Initialize)
复制代码

可以看到,模块名参数就是 timers, 初始化方法参数传递的是 Initialize 方法。到这,算是找到调用根源了。那我们接着看看 node_module_register 方法干了些什么。此方法在 src/node.cc中:

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

你会惊奇的发现,这里仅仅是把所有的模块依次链接起来,形成一个链表。没有调用呀?没有初始化啊?此处注意一下变量modlist_internal,后面会用到。这里有一个 GetInternalBinding 。他被绑定到 global 对象上,会被 js 调用。此方法是必须结合js源码才能明白出处。此方法在src/node.cc中:

static void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {...//获取参数名。Local<String> module = args[0].As<String>();...//获取内部模块。node_module* mod = get_internal_module(*module_v);if (mod != nullptr) {// 初始化。exports = InitModule(env, mod, module);}...
}
复制代码

让我们看看 get_builtin_module 方法,此方法在src/node.cc中:

node_module* get_internal_module(const char* name) {return FindModule(modlist_internal, name, NM_F_INTERNAL);
}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;}...return mp;
}
复制代码

方法内部调用了 FindModule 方法。第一个参数熟悉吗?**modlist_internal!**就是通过上面的 RegisterBuiltinModules 方法来生成的模块。具体的查找过程也比较简单,就是遍历这个链表,一个个比较模块名,是否相同,然后拿到模块。再继续看一下 InitModule 方法,同样位于src/node.cc:

static Local<Object> InitModule(Environment* env,node_module* mod,Local<String> module) {...//直接调用node_module模块的注册上下文的方法,并传入对应的一些参数。mod->nm_context_register_func(exports,unused,env->context(),mod->nm_priv);return exports;
}
复制代码

回想下上面的 timer.cc 中的 Initialize 方法,此处就是调用的地点了。至此,模块就加载到程序当中了。

尾言

分析过程中,会遇到很多的宏处理,此时可以将宏一个个展开,然后写到纸上进行分析。否则一眼很难看的清全貌。

通过本文,应该对 RegisterModules,和如何初始化模块应该有了一个大致的了解。但是你可能还是会有一些迷惑,比如 GetInternalBinding 这个方法谁调用的呢,下次来进行分析~

nodejs模块加载分析(1).md相关推荐

  1. nodejs模块加载的猜想

    nodejs的模块化就相当于其他语言的类文件的导入和使用. require方法与页面js的导入外部js文件不同.你在页面从外部导入一个js文件,比如: server.js: var http=requ ...

  2. nodejs学习巩固笔记-nodejs基础,Node.js 高级编程(核心模块、模块加载机制)

    目录 Nodejs 基础 大前端开发过程中的必备技能 nodejs 的架构 为什么是 Nodejs Nodejs 异步 IO Nodejs 事件驱动架构 全局对象 全局变量之 process 核心模块 ...

  3. 大前端 - nodejs 基础(核心模块、模块加载机制)

    node基础 一 nodejs 核心模块.模块加载机制 nodejs异步io和事件循环 nodejs单线程 nodejs实现api服务 nodejs核心模块和api使用 提供应用程序可直接调用库,例如 ...

  4. 模块加载过程代码分析1

    一.概述 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中.对于每个模块,系统都要分配一个包含以下数据结构的内存区. 一个module对象,表示模块名的一个以null ...

  5. res_config_mysql和chan_sip模块的加载分析

    1. res_config_mysql的模块加载早于chan_sip,他们的加载函数均为load_module 先分析res_config_mysql的load_module函数 parse_conf ...

  6. FreeSwitch 的初始化及其模块加载过程

    FS 主函数main() Freeswitch的主函数是在文件switch.c中定义的,该文件的260行是整个程序的入口,主函数主要完成的功能是包括,命令行解析,初始化apr库,构建全局内存池,模块加 ...

  7. 未能加载文件或程序集rsy3_abp vnext2.0之核心组件模块加载系统源码解析

    abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...

  8. 制作镜像包时遇到的模块加载错误的问题

    最近遇到一个问题,定制的一个镜像包,在启动后,发现有些内核模块加载是错误的,有些是正确的.使用modinfo去查询,可以看到加载的为内核版本中的模块(非自己定制的模块),而有些自己新增的模块没有加载成 ...

  9. abp vnext2.0之核心组件模块加载系统源码解析

    abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...

最新文章

  1. HDU1402(FFT入门)
  2. [BZOJ2527]Meteors
  3. 电脑显示服务器地址无法ping通,网关无法Ping通故障及解决方法
  4. 一、Linux Shell基础
  5. Linux日常之允许或禁止指定用户或IP进行SSH登录
  6. 目标检测 nms非极大抑制算法
  7. 解决cookie写入问题
  8. 如何实现Android端获取RTSP|RTMP流转推RTMP
  9. With(ReadPast)就不会被阻塞吗?
  10. 号外!德国惊现大罢工--要求每周上班28小时
  11. 面向对象类设计的五大原则(一)单一职责原则Single Responsibility Principle
  12. 利用原生node.js连接sql数据库
  13. 项目经理应对需求变更的策略
  14. WordPress怎么关闭评论审核啊?
  15. 我在南大的七年(刘未鹏先生)
  16. 【STM32】实战2—用STM32产生PWM信号驱动舵机MG996R(一)
  17. 计算机和人脑在线阅读,人脑与电脑(原文)
  18. 自制3DSMAX 自动减面脚本
  19. R语言ggplot2包之坐标轴
  20. centos发现网络连不上了,重启网络服务报错“systemctl status network.service” and “journalctl -xe” for details. [失败]

热门文章

  1. 世界各国钱币(ZT)
  2. PowerToys,微软开源的超实用小工具合集
  3. 结合不同的模型进行集成学习
  4. 合并多个excel——贼快
  5. String字符串类的获取功能
  6. Django Bakend--后台管理插件开发-01
  7. 从零开始の后缀自动机
  8. Python中变量名与变量值的关系
  9. iOS开发CocoaPods使用
  10. 在 64 位版本的 Windows 上,如何在 32 位版本的 ASP.NET 1.1 和 64 位版本的 ASP.NET 2.0 之间切换...