Node.js source code analysis - start with the main function

title: Node.js source code analysis - start with the main function
date: 2018-11-27 21:30:15
tags:

- Node.js
- Node.js Source code analysis
- Source code analysis

categories:

- Node.js Source code analysis

This article was first published on the personal website four years ago, and is now migrated to this site for re posting. The original link is: https://laogen.site/nodejs/no...
Directory page of Node.js source code analysis series: https://laogen.site/nodejs/no...

Small target

Know the general execution logic of the program and the order of key points

We usually knock down node app at the terminal JS, what happened.

Be specific, know node When and where the JS native (C + +) module was loaded;
Know where our js code is loaded and executed;
Know when the main loop (event loop) of the process starts;

With the foundation of this small goal, in the next article, we will further explore node How to realize the registration of js native module, how to obtain & initialize, and how to expose it to the js environment; More about node js module mechanism, our usual app How js is executed;

<!-- more -->

Paste code description

Limited to space, this paper only draws out the general implementation process first, and then opens the text one by one.

The original code is too long. First remove the irrelevant code that does not affect our analysis and paste the code related to the overall execution logic, the / / in the code Comment means that there is omitted code in this place.

The comments on the first line of each code segment will indicate the location of the source file, and some code explanations will be carried out in the comments in the code segment;

This article will no longer introduce the knowledge of V8 and Libuv, but will hold a special classification to write V8 and Libuv. Refer to {% post_link nodejs / nodejs Src / index node.js source code analysis - Preface%}

Open stroke: from main function to process main loop

main function

/* src/node_main.cc:93 */
int main(int argc, char* argv[]) {
  // ...
  return node::Start(argc, argv);
}

Main function in src/node_main.cc file, which mainly stores the main function.

It's very simple. It just calls node::Start(), which is in Src / node CC in this file, the next core code is in this file.

Initialize V8 engine

/* src/node.cc:3011 */
int Start(int argc, char** argv) {
  // ...

  std::vector<std::string> args(argv, argv + argc);
  std::vector<std::string> exec_args;

  // This needs to run *before* V8::Initialize().
  Init(&args, &exec_args);

  // ...
  v8_platform.Initialize(per_process_opts->v8_thread_pool_size);
  V8::Initialize();

  // ...
  const int exit_code = Start(uv_default_loop(), args, exec_args);

  v8_platform.StopTracingAgent();
  v8_initialized = false;
  V8::Dispose();
  v8_platform.Dispose();

  return exit_code;
}

In this code, first initialize V8, then call another Start(uv_loop_t*,...). Function, finally release resources and the process ends;

It is worth noting that before initializing V8, we call a Init() function, which mainly completes Node.. The registration of JS native (c + +) module is the C + + implementation module of fs http and other modules.

/* src/node.cc:2559 */
void Init(std::vector<std::string>* argv, std::vector<std::string>* exec_argv) {
  // ...
  // Register built-in modules
  RegisterBuiltinModules();
  // ...
}

Init() calls RegisterBuiltinModules(), which registers all Node.. JS native module. For the registration of native modules, this article will not continue to follow. The next article will expand this part separately. Here we know the process first.

Remember this RegisterBuiltinModules(), and the next article will start here.

Create Isolate instance

/* src/node.cc:2964 */
inline int Start(uv_loop_t* event_loop,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {
  std::unique_ptr<ArrayBufferAllocator, decltype(&FreeArrayBufferAllocator)>
      allocator(CreateArrayBufferAllocator(), &FreeArrayBufferAllocator);
  // Create Isolate instance
  Isolate* const isolate = NewIsolate(allocator.get());

  // ...
  int exit_code;
  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    // ...
    exit_code = Start(isolate, isolate_data.get(), args, exec_args);
  }
  // ...
  isolate->Dispose();
  return exit_code;
}

What Start() didn't do, the main job was to create the Isolate instance, and then call another Start(Isolate*...).

Process main loop

/* src/node.cc:2868 */
inline int Start(Isolate* isolate, IsolateData* isolate_data,
                 const std::vector<std::string>& args,
                 const std::vector<std::string>& exec_args) {
  HandleScope handle_scope(isolate);
  // Create V8 Context object
  Local<Context> context = NewContext(isolate);
  Context::Scope context_scope(context);
  
  // Create an Environment object, which is node JS class
  Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());
  
  // This mainly completes the initialization of libuv and the creation of process objects
  // Node The global process object in JS is not expanded here
  env.Start(args, exec_args, v8_is_profiling);

  {
    // ...
    // LoadEnvironment is the key point of this article
    LoadEnvironment(&env);
    env.async_hooks()->pop_async_id(1);
  }

  // The following is the main loop of the process
  {
    // ...
    bool more;
    // ...
    do {
      uv_run(env.event_loop(), UV_RUN_DEFAULT);
      // ...
      more = uv_loop_alive(env.event_loop());
      if (more)
        continue;

      // ...
    } while (more == true);
  }
  // ...
  return exit_code;
}

This code creates and uses the context required for js execution, and then creates the Environment object;

The Environment object is node JS is an important object in the source code. It is a global singleton. It defines and stores some important global objects and functions, such as the Isolate object just created and the Context object just created. Note that it is not V8, but node JS, and its use runs through the whole node JS execution lifecycle.

Next is the main loop of the process, uv_run() starts the event loop of libuv, which is also a node Libuv will write a separate introduction to the main loop of JS process.

Finally, the middle LoadEnvironment() call is the most critical link before the program enters the main loop;

LoadEnvironment() completes the loading and execution of some JS files, including loading and executing the commonly written app js.

Before main cycle

/* src/node.cc:2115 */

void LoadEnvironment(Environment* env) {
  HandleScope handle_scope(env->isolate());
  // ...

  // The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
  // lib/internal/bootstrap/node.js, each included as a static C string
  // defined in node_javascript.h, generated in node_javascript.cc by
  // node_js2c.
  // Load two important JS files: internal / bootstrap / loaders js 
  // And internal / bootstrap / node js
  Local<String> loaders_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
  MaybeLocal<Function> loaders_bootstrapper =
      GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
  Local<String> node_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
  MaybeLocal<Function> node_bootstrapper =
      GetBootstrapper(env, NodeBootstrapperSource(env), node_name);
  // ...
  // Add a reference to the global object
  Local<Object> global = env->context()->Global();

  env->SetMethod(env->process_object(), "_rawDebug", RawDebug);

  // Expose the global object as a property on itself
  // (Allows you to set stuff on `global` from anywhere in JavaScript.)
  global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global);

  // Prepare the binding function. Calling js below will be passed to the js environment as a parameter
  // Create binding loaders
  Local<Function> get_binding_fn =
      env->NewFunctionTemplate(GetBinding)->GetFunction(env->context())
          .ToLocalChecked();

  Local<Function> get_linked_binding_fn =
      env->NewFunctionTemplate(GetLinkedBinding)->GetFunction(env->context())
          .ToLocalChecked();

  Local<Function> get_internal_binding_fn =
      env->NewFunctionTemplate(GetInternalBinding)->GetFunction(env->context())
          .ToLocalChecked();

  // Prepare to execute internal / bootstrap / loaders Parameters of JS file
  Local<Value> 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)
  };

  // Execute internal / bootstrap / loaders js 
  // Bootstrap internal loaders
  // This object is used to receive the execution result. Remember that it is bootstrapped_loaders, which will be used below
  Local<Value> bootstrapped_loaders;
  if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(),
                           arraysize(loaders_bootstrapper_args),
                           loaders_bootstrapper_args,
                           &bootstrapped_loaders)) {
    return;
  }

  // Prepare to execute internal / bootstrap / node JS parameters
  // Bootstrap Node.js
  Local<Object> bootstrapper = Object::New(env->isolate());
  SetupBootstrapObject(env, bootstrapper);
  Local<Value> bootstrapped_node;
  Local<Value> node_bootstrapper_args[] = {
    env->process_object(),
    bootstrapper,
    // Note that this is the above implementation loaders JS returned result object,
    // Pass it as an execution parameter to internal / bootstrap / node js
    bootstrapped_loaders  
  };

  // Execute internal / bootstrap / node js
  if (!ExecuteBootstrapper(env, node_bootstrapper.ToLocalChecked(),
                           arraysize(node_bootstrapper_args),
                           node_bootstrapper_args,
                           &bootstrapped_node)) {
    return;
  }
}

LoadEnvironment() first loads two js files, which are located in:
lib/internal/bootstrap/loaders.js and lib / internal / bootstrap / node js.

We node App written by js developer In fact, one of the most important contents of the two js files will be loaded and executed.

LoadEnvironment() then creates three binding functions:

  • get_binding_fn
  • get_linked_binding_fn
  • get_internal_binding_fn

These three binding functions are used to obtain and load node The of js native module will be passed into the js execution environment, that is, you can call it in js code, such as process Binding ('fs'), when we use C + + to develop node js extension module will also be used, which will be expanded in detail in the future.

LoadEnvironment() will then execute lib / internal / bootstrap / loaders js, which mainly defines internal module loaders.

lib/internal/bootstrap/loaders. The module loaders defined by JS are then used as execution parameters and passed in lib / internal / bootstrap / node JS in lib / internal / bootstrap / node JS will use these loaders to load internal modules.

lib/internal/bootstrap/node.js has done a lot of work. All we need to know here is that it finally loads and executes our node App written by JS programmer JS is OK.

So far, we know. On the command line, type node app JS about what happened!

Summary

This is just a general logic, which can cooperate with node JS source code, and then take the time to smooth it out. Relying on the posted code alone, it may still be confused.

The next article is to expand the key points in the execution logic respectively.

The author's level is limited and his writing is hasty. Please point out the mistakes.

By Maslow (wangfugen@126.com), laf.js Author.

lafyun.com Open source cloud development platform, the front end becomes the full stack, and there is no need for the server.

Keywords: node.js TypeScript

Added by GaryE on Fri, 18 Feb 2022 14:38:17 +0200