小目标
知道程序大概执行逻辑,关键点执行的顺序
我们平时在终端敲下 node app.js
后,发生了什么。
具体点,知道 node.js 原生(C++)模块什么时候加载的,在哪加载的;
知道我们的 js 代码是在哪个环节被加载执行的;
知道进程的主循环(事件循环)什么时候启动的;
有了这个小目标的基础,在接下来的文章中,我们再进一步的探索 node.js 原生模块的注册是怎么实现的,怎么获取 & 初始化的,怎么曝露给 js 环境调用的;再细说 node.js 的模块机制,我们通常的 app.js
怎么被执行的;
贴代码说明
限于篇幅,本文只先把大体执行流程捋出来,后面再开文一块块的捋。
原代码太长,先把不影响我们分析的无关代码去掉,贴上来有关整体执行逻辑的代码,代码中的 // ...
注释意思是这个地方有被省略的代码。
每段代码第一行的注释都会指出源文件位置,一些代码讲解会在代码段中的注释中进行;
本文不再介绍 V8 和 Libuv 的知识,会开专门的分类写 V8 和 Libuv,参考 Node.js 源码分析 - 前言
开捋:从 main 函数到进程主循环
main 函数
1 | /* src/node_main.cc:93 */ |
main函数
在 src/node_main.cc
这个文件中,这个文件主要就是存放 main函数
。
很简单,只是调用了 node::Start()
,这个函数在 src/node.cc
这个文件中,接下来的核心代码都在这个文件中。
初始化 V8 引擎
1 | /* src/node.cc:3011 */ |
在这段代码,首先进行 V8 的初始化,然后调用了另外一个 Start(uv_loop_t*, ...)
函数,最后释放资源,进程结束;
其中值得注意的一点,在初始化 V8 之前,调用了一个 Init()
函数,这个函数主要完成了 Node.js 原生(C++)模块的注册,就是 fs
http
等模块的 C++ 实现模块。
1 | /* src/node.cc:2559 */ |
Init()
中调用了 RegisterBuiltinModules()
,它注册了所有 Node.js 原生模块,关于原生模块的注册,本文不再继续跟进去,下一篇会单独展开这一块,这里先知道这个流程。
记住这个
RegisterBuiltinModules()
,下一篇文章就从这里开始展开。
创建 Isolate 实例
1 | /* src/node.cc:2964 */ |
这个 Start()
倒也没做什么,主要工作是创建了 Isolate 实例,然后调用了另外一个 Start(Isolate*...)
。
进程主循环
1 | /* src/node.cc:2868 */ |
这段代码创建并使用了 js 执行需要的 context,然后创建了 Environment
对象;
这个 Environment
对象是 Node.js 源码中重要的一个对象,它是一个全局单例,定义和存储了一些重要的全局对象和函数,比如刚开始创建的 Isolate 对象、刚刚创建的 Context 对象等,注意它不是 V8 的,是 Node.js 定义的,对它的使用贯穿整个 Node.js 执行的生命周期。
再下面是进程的主循环,uv_run()
启动了 Libuv
的事件循环, 它也是 Node.js 进程的主循环,Libuv
会单独写文介绍。
最后说一下,中间的 LoadEnvironment()
调用,它是在程序进入主循环之前最关键的一环;
LoadEnvironment()
完成了一些 js 文件的加载和执行,其中就包括加载执行通常编写的 app.js
。
主循环之前
1 | /* src/node.cc:2115 */ |
LoadEnvironment()
首先加载了两个 js 文件,这两个 js 文件的位置分别在:lib/internal/bootstrap/loaders.js
和 lib/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 源码,再花时间捋一捋,光靠贴的这点代码,可能还是会迷糊的。
接下来的文章,就是对这个执行逻辑中的关键点分别展开。
作者水平有限,写的也仓促,有误之处还请指出。