tsy77 / blog

78 stars 2 forks source link

Node.js源码-global&process的创建、初始化 #10

Open tsy77 opened 6 years ago

tsy77 commented 6 years ago

global和process是node中大家经常用到的两个对象,那么这两个对象又是如何被创建的,我们在我们的代码中为什么能够使用呢?本篇文章将为大家揭晓答案。

global

global对象在src/node.cc中的被创建,在bootstrap/node.js中被初始化。

创建

在src/node.cc的LoadEnvironment方法中,有以下几行代码是用来创建global对象的。

// Add a reference to the global object
  Local<Object> global = env->context()->Global();

  ......
  // 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);

其中env->context()->Global()获取了当前context中的Global全局对象,global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global)将全局对象本身挂载在其global对象上,这样全局对象中有了一个global对象指向了全局对象本身,我们在该context中可以直接使用global对全局对象的引用进行访问。

以上程序之后,这样我们就可以在context的任何地方对全局对象进行操作。

初始化

初始化在bootstrap/node.js中进行,其在上述逻辑执行后被执行,所以可以直接操作全局对象。

初始化代码分为以下几个部分:

1.为global挂载process、Symbol.toStringTag、buffer等属性

//为global挂载Symbol.toStringTag、buffer等属性
setupGlobalVariables();

function setupGlobalVariables() {
    // global.toString()时访问
    Object.defineProperty(global, Symbol.toStringTag, {
      value: 'global',
      writable: false,
      enumerable: false,
      configurable: true
    });
    global.process = process;
    const util = NativeModule.require('util');

    function makeGetter(name) {
      return util.deprecate(function() {
        return this;
      }, `'${name}' is deprecated, use 'global'`, 'DEP0016');
    }

    function makeSetter(name) {
      return util.deprecate(function(value) {
        Object.defineProperty(this, name, {
          configurable: true,
          writable: true,
          enumerable: true,
          value: value
        });
      }, `'${name}' is deprecated, use 'global'`, 'DEP0016');
    }

    Object.defineProperties(global, {
      GLOBAL: {
        configurable: true,
        get: makeGetter('GLOBAL'),
        set: makeSetter('GLOBAL')
      },
      root: {
        configurable: true,
        get: makeGetter('root'),
        set: makeSetter('root')
      }
    });

    // This, as side effect, removes `setupBufferJS` from the buffer binding,
    // and exposes it on `internal/buffer`.
    NativeModule.require('internal/buffer');

    global.Buffer = NativeModule.require('buffer').Buffer;
    process.domain = null;
    process._exiting = false;
  }

这里将process挂载在global上,这也是为什么我们可以在我们的用户代码直接访问process对象的原因。同时注意Symbol.toStringTag属性会在访问global.toString()时访问。

2.初始化timeout、console、URL

const browserGlobals = !process._noBrowserGlobals;
if (browserGlobals) {
  setupGlobalTimeouts();
  setupGlobalConsole();
  setupGlobalURL();
}

function setupGlobalTimeouts() {
    const timers = NativeModule.require('timers');
    global.clearImmediate = timers.clearImmediate;
    global.clearInterval = timers.clearInterval;
    global.clearTimeout = timers.clearTimeout;
    global.setImmediate = timers.setImmediate;
    global.setInterval = timers.setInterval;
    global.setTimeout = timers.setTimeout;
  }

这里以setupGlobalTimeouts为例,主要是在global上挂载setImmediate等timeout相关的方法。

3.为global加上'assert', 'async_hooks', 'buffer'等属性

const {
  addBuiltinLibsToObject
} = NativeModule.require('internal/modules/cjs/helpers');
// 为global加上'assert', 'async_hooks', 'buffer'等属性
addBuiltinLibsToObject(global);

这里调用的是internal/modules/cjs/helpers下的addBuiltinLibsToObject方法,代码如下:

const builtinLibs = [
  'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto',
  'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'net',
  'os', 'path', 'perf_hooks', 'punycode', 'querystring', 'readline', 'repl',
  'stream', 'string_decoder', 'tls', 'trace_events', 'tty', 'url', 'util',
  'v8', 'vm', 'zlib'
];

if (typeof process.binding('inspector').open === 'function') {
  builtinLibs.push('inspector');
  builtinLibs.sort();
}

function addBuiltinLibsToObject(object) {
  // Make built-in modules available directly (loaded lazily).
  builtinLibs.forEach((name) => {
    // Goals of this mechanism are:
    // - Lazy loading of built-in modules
    // - Having all built-in modules available as non-enumerable properties
    // - Allowing the user to re-assign these variables as if there were no
    //   pre-existing globals with the same name.

    const setReal = (val) => {
      // Deleting the property before re-assigning it disables the
      // getter/setter mechanism.
      delete object[name];
      object[name] = val;
    };

    Object.defineProperty(object, name, {
      get: () => {
        const lib = require(name);

        // Disable the current getter/setter and set up a new
        // non-enumerable property.
        delete object[name];
        Object.defineProperty(object, name, {
          get: () => lib,
          set: setReal,
          configurable: true,
          enumerable: false
        });

        return lib;
      },
      set: setReal,
      configurable: true,
      enumerable: false
    });
  });
}

其中builtinLibs主要包含buffer、assert等常用的对象或方法。

process

process在src/env.cc的Environment::Start方法中被创建和初始化,同时在bootstrap/node.js也初始化了process对象。Environment::Start的调用函数是src/node.cc中的inline函数Start。

创建

在Environment::Start中初始化process的逻辑如下:

auto process_template = FunctionTemplate::New(isolate());
  process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));

  auto process_object =
      process_template->GetFunction()->NewInstance(context()).ToLocalChecked();
  // 初始化时声明的Persistent handle,Persistent v8::Object process_object
  // 这里利用process_object.Reset(),最终调用v8的PersistentBase<T>::Reset给Persistent handle重新赋值
  set_process_object(process_object);

这里面主要做了如下几件事:

1.声明还输模版process_template,设置其类名为process
2.获取对象实例process_object
3.调用set_process_object,将上述实例process_object赋值给初始化时声明的Persistent handle(Persistent v8::Object process_object),也就是process_object这个持久化的handle指向新创建的process_object实例。

这里需要注意的是set_process_object是从哪里来的呢?

在env-inl.h中有下面的宏定义:

#define V(PropertyName, TypeName)                                             \
  inline v8::Local<TypeName> Environment::PropertyName() const {              \
    return StrongPersistentToLocal(PropertyName ## _);                        \
  }                                                                           \
  inline void Environment::set_ ## PropertyName(v8::Local<TypeName> value) {  \
    PropertyName ## _.Reset(isolate(), value);                                \
  }
  ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V

#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)                           \
                                \
  V(process_object, v8::Object)                                               \
  V(promise_reject_handled_function, v8::Function)                            \
  V(promise_reject_unhandled_function, v8::Function)                          \
  V(promise_wrap_template, v8::ObjectTemplate)                                \
  V(push_values_to_array_function, v8::Function)                              \
  V(randombytes_constructor_template, v8::ObjectTemplate)                     \
  V(script_context_constructor_template, v8::FunctionTemplate)                \
  V(script_data_constructor_function, v8::Function)                           \
  V(secure_context_constructor_template, v8::FunctionTemplate)                \
  V(shutdown_wrap_template, v8::ObjectTemplate)                               \
  V(tcp_constructor_template, v8::FunctionTemplate)                           \
  V(tick_callback_function, v8::Function)                                     \
  V(timers_callback_function, v8::Function)                                   \
  V(tls_wrap_constructor_function, v8::Function)                              \
  V(tty_constructor_template, v8::FunctionTemplate)                           \
  V(udp_constructor_function, v8::Function)                                   \
  V(vm_parsing_context_symbol, v8::Symbol)                                    \
  V(url_constructor_function, v8::Function)                                   \
  V(write_wrap_template, v8::ObjectTemplate)

从这里我们可以看出,set_process_object在这里定义,其真实调用的是processobject.Reset方法,那么v8::Local:: Reset方法又做了什么呢?

void PersistentBase<T>::Reset(Isolate* isolate, const Local<S>& other) {
  TYPE_CHECK(T, S);
  Reset();
  if (other.IsEmpty()) return;
  this->val_ = New(isolate, other.val_);
}

这里Reset方法在PersistentBase类下,这里顾名思义PersistentBase是Persistent handle的基类,调用Reset方法也就是重新给其赋值。

那么开始的Persistent handle process_object又是怎么来的呢?我们看env.h中的如下代码:

#define V(PropertyName, TypeName)                                             \
  inline v8::Local<TypeName> PropertyName() const;                            \
  inline void set_ ## PropertyName(v8::Local<TypeName> value);
  ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V

其中ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES这个宏我们上面见过,就是调用了多次V,只不过这里的V代表V8的handle声明,用在process_object上其实就是如下代码:

inline v8::Local<v8::Object> process_object() const

也就是声明了Persistent handle process_object,也就有了我们后面的利用PersistentBase::Reset进行赋值的操作了。

初始化

process的初始化出现在src/env.cc和bootstrap/node.js。

在src/env.cc中,其实调用的是node.cc中的SetupProcessObject方法,初始化代码比较长,下面摘出了比较具有代表性的代码:

Local<Object> process = env->process_object();

  auto title_string = FIXED_ONE_BYTE_STRING(env->isolate(), "title");
  // 设置process的存取器
  CHECK(process->SetAccessor(env->context(),
                             title_string,
                             ProcessTitleGetter,
                             ProcessTitleSetter,
                             env->as_external()).FromJust());

  // process.version
  // 参数为 obj, name, value
  READONLY_PROPERTY(process,
                    "version",
                    FIXED_ONE_BYTE_STRING(env->isolate(), NODE_VERSION));

这里首先获取从env中获取了process_object,然后设置了一个process.title的存取器,用来获取和设置title,这里的title获取调用了uv_get_process_title,实际上就是开辟了堆内存,copy了一份process_argv[0],也就是入口main函数的argv[0];接着使用READONLY_PROPERTY这个宏设置了process的只读属性version。READONLY_PROPERTY宏定义如下所示:

#define READONLY_PROPERTY(obj, name, value)                                   \
  do {                                                                        \
    obj->DefineOwnProperty(env->context(),                                    \
                           FIXED_ONE_BYTE_STRING(isolate, name),              \
                           value, ReadOnly).FromJust();                       \
  } while (0)

在bootstrap/node.js中初始化process的代码如下:

setupProcessObject();

// do this good and early, since it handles errors.
setupProcessFatal();

// 国际化
setupProcessICUVersions();

......

Object.defineProperty(process, 'argv0', {
  enumerable: true,
  configurable: false,
  value: process.argv[0]
});
process.argv[0] = process.execPath;

......

function setupProcessObject() {
    // set_push_values_to_array_function, node.cc
    process._setupProcessObject(pushValueToArray);

    function pushValueToArray() {
      for (var i = 0; i < arguments.length; i++)
        this.push(arguments[i]);
    }
}

......

function setupProcessFatal() {
    const {
      executionAsyncId,
      clearDefaultTriggerAsyncId,
      clearAsyncIdStack,
      hasAsyncIdStack,
      afterHooksExist,
      emitAfter
    } = NativeModule.require('internal/async_hooks');

    process._fatalException = function(er) {

      ......

      return true;
    };
}

这里主要初始化了process的set_push_values_to_array_function、execPath、错误处理以及国际化版本等。

总结

本文主要讲解了global、process的创建、初始化,以及为什么我们在我们的用户代码中可以对他们进行直接的访问。