标题:Node.js源码分析-从main函数开始日期:2018-11-2721:30:15tags:-Node.js-Node.js源码分析-源码分析分类:-Node.js源码分析本文四年前发表于我的个人网站,现转载于此,原文链接:https://laogen.site/nodejs/no...《Node.js 源码分析》系列目录页:https://laogen.site/nodejs/no...小目标知道程序的大致执行逻辑,以及关键点的执行顺序。我们通常在终端中输入nodeapp.js后会发生什么。具体来说,知道何时何地加载node.js原生(C++)模块;知道我们的js代码在哪里加载和执行;知道进程的主循环(事件循环)何时开始;有了这个小目标的基础上,在下一篇文章中,我们将进一步探讨node.js原生模块的注册是如何实现的,如何获取&初始化,如何暴露给js环境调用;然后详细阐述node.js.js的模块机制,我们平时的app.js是如何执行的;帖子代码说明限于篇幅,本文只先勾勒出大概的执行流程,后面会逐篇展开。原代码太长,先去掉不影响我们分析的无关代码,贴上整体执行逻辑相关的代码。代码中的//...注释表示该处有省略代码。每段代码第一行的注释会指出源文件的位置,代码部分的注释会进行一些代码解释;本文不再介绍V8和Libuv的知识,将V8和Libuv写在专门的分类里,参考{%post_linknodejs/nodejs-src/indexNode.js源码分析-前言%}开篇:来自main处理主循环的函数mainfunction/*src/node_main.cc:93*/intmain(intargc,char*argv[]){//...returnnode::Start(argc,argv);}Themainfunction在文件src/node_main.cc中,主要存放main函数。很简单,调用node::Start()即可,这个函数在文件src/node.cc中,下面的核心代码都在这个文件中。初始化V8引擎/*src/node.cc:3011*/intStart(intargc,char**argv){//...std::vectorargs(argv,argv+argc);std::vector执行参数;//这需要运行*before*V8::Initialize().Init(&args,&exec_args);//...v8_platform.Initialize(per_process_opts->v8_thread_pool_size);V8::初始化();//...constintexit_code=Start(uv_default_loop(),args,exec_args);v8_platform.StopTracingAgent();v8_initialized=false;V8::处置();v8_platform.Dispose();returnexit_code;}这段代码中,首先初始化V8,然后调用另一个Start(uv_loop_t*,...)函数,最后释放资源,流程结束;值得注意的是,在初始化V8之前,调用了一个Init()函数被创建。该函数主要完成Node.js原生(C++)模块的注册,即fshttp等模块的C++实现模块。/*src/node.cc:2559*/voidInit(std::vector*argv,std::vector*exec_argv){//...//注册建立-输入模块RegisterBuiltinModules();//...}Init()调用RegisterBuiltinModules(),注册所有Node.js原生模块。关于原生模块的注册,本文不再继续跟进。下篇文章会单独展开这块,先了解一下这里的流程。记住这个RegisterBuiltinModules(),下一篇文章将从这里开始。创建Isolate实例/*src/node.cc:2964*/inlineintStart(uv_loop_t*event_loop,conststd::vector&args,conststd::vector&exec_args){std::unique_ptr分配器(CreateArrayBufferAllocator(),&FreeArrayBufferAllocator);//创建一个Isolate实例Isolate*constisolate=NewIsolate(allocator.get());//...int退出代码;{储物柜储物柜(隔离);隔离::作用域isolate_scope(isolate);HandleScope句柄范围(隔离);//...exit_code=Start(isolate,isolate_data.get(),args,exec_args);}//...隔离->Dispose();returnexit_code;}这个Start()什么都不做,主要的工作是创建一个Isolate实例,然后调用另一个Start(Isolate*...)。处理主循环/*src/node.cc:2868*/inlineintStart(Isolate*isolate,IsolateData*isolate_data,conststd::vector&args,conststd::vector&exec_args){HandleScopehandle_scope(isolate);//创建一个V8上下文对象Localcontext=NewContext(isolate);上下文::范围context_scope(context);//创建一个Environment对象,它是一个Node.js类Environmentenv(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);}//下面是流程的主循环{//...boolmore;//...执行{uv_run(env.event_loop(),UV_RUN_DEFAULT);//...更多=uv_loop_alive(env.event_loop());如果(更多)继续;//...}而(莫重新==真);}//...returnexit_code;}这段代码创建并使用了js执行所需要的上下文,然后创建了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*/voidLoadEnvironment(Environment*env){HandleScopehandle_scope(env->isolate());//...//引导程序脚本是lib/internal/bootstrap/loaders.js和//lib/internal/bootstrap/node.js,每个都包含为静态C字符串//在node_javascript.h中定义,生成于node_javascript.cc来自//node_js2c。//添加两个重要的js文件:internal/bootstrap/loaders.js//和internal/bootstrap/node.jsLocalloaders_name=FIXED_ONE_BYTE_STRING(env->isolate(),"internal/bootstrap/loaders.js");MaybeLocal<函数>loaders_bootstrapper=GetBootstrapper(env,LoadersBootstrapperSource(env),loaders_name);Localnode_name=FIXED_ONE_BYTE_STRING(env->isolate(),"internal/bootstrap/node.js");MaybeLocal<函数>node_bootstrapper=GetBootstrapper(env,NodeBootstrapperSource(env),node_name);//...//添加对全局对象的引用ectLocal