当前位置: 首页 > 后端技术 > Node.js

Node.js源码分析-从main函数开始

时间:2023-04-04 01:09:29 Node.js

标题: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);//...//添加对全局对象的引用ectLocalglobal=env->context()->Global();env->SetMethod(env->process_object(),"_rawDebug",RawDebug);//将全局对象公开为自身的属性//(允许您从JavaScript中的任何位置设置`global`的内容。)global->Set(FIXED_ONE_BYTE_STRING(env->isolate(),"global"),global);//准备绑定函数,下面调用js会作用为参数传给js环境//创建绑定加载器Localget_binding_fn=env->NewFunctionTemplate(GetBinding)->GetFunction(env->context()).ToLocalChecked();Localget_linked_binding_fn=env->NewFunctionTemplate(GetLinkedBinding)->GetFunction(env->context()).ToLocalChecked();Localget_internal_binding_fn=env->NewFunctionTemplate(GetInternalBinding)->GetFunction(env->context()).ToLocalChecked();//准备执行internal/bootstrap/loaders.js文件的参数Local;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)};//执行内部/bootstrap/loaders.js//Bootstrap内部加载器//这个对象是使用接收执行结果的,记住是bootstrapped_loaders,下面会用到Localbootstrapped_loaders;如果(!ExecuteBootstrapper(env,loaders_bootstrapper.ToLocalChecked(),arraysize(loaders_bootstrapper_args),loaders_bootstrapper_args,&bootstrapped_loaders)){返回;}//准备执行internal/bootstrap/node.js的参数//BootstrapNode.jsLocalbootstrapper=Object::New(env->isolate());SetupBootstrapObject(环境,引导程序);本地<值>bootstrapped_node;Localnode_bootstrapper_args[]={env->process_object(),bootstrapper,//注意,这里是上面执行loaders.js返回的结果对象,//作为执行参数传递给internal/bootstrap/node.jsbootstrapped_loaders};//执行internal/bootstrap/node.js}}LoadEnvironment()首先加载两个js文件,这两个js文件的位置分别是:lib/internal/bootstrap/loaders.js和lib/internal/bootstrap/node.js我们Node.js写的app.jsdevelopers其实就是在这两个js文件中加载执行的。这是最重要的逻辑之一,内容也很多,后面的文章会详细展开。LoadEnvironment()接下来创建三个绑定函数:get_binding_fnget_linked_binding_fnget_internal_binding_fn这三个绑定函数用于获取和加载Node.js原生模块,这些模块会被传入js执行环境,也就是你可以在js代码中调用它们了,比如process.binding('fs'),我们用C++开发Node.js扩展模块时也会用到,后面会详细展开。LoadEnvironment()接下来会执行lib/internal/bootstrap/loaders.js。在这个js文件中,主要定义了内部的模块加载器(loaders)。然后将lib/internal/bootstrap/loaders.js定义的模块加载器(loaders)作为执行参数传入lib/internal/bootstrap/node.js,在lib/internal/bootstrap/node中使用。js这些加载器用于加载内部模块。lib/internal/bootstrap/node.js做了很多工作,只要知道它最终加载并执行我们的Node.js程序员编写的app.js即可。到目前为止,我们知道当我们在命令行中输入nodeapp.js时发生了什么!总结这只是一般逻辑。可以结合Node.js的源码,然后花时间过一遍。仅通过发布这段代码可能仍然令人困惑。下一篇文章就是针对这个执行逻辑中的关键点展开。作者水平有限,写的仓促,有错误请指出。作者Maslow(wangfugen@126.com),laf.js的作者。lafyun.com是一个开源的云开发平台,前端变成全栈,不需要服务器。