« 上一篇下一篇 »

简单解析child_process模块fork方法调用流程

本文测试环境是win7 32位。 先下载nodejs源代码以备查看之用。同时安装好node-inspector环境并配置好,命令:  npm install -g node-inspector
写一段测试代码如下:
var childProcess = require('child_process');
var n = childProcess. fork( './son.js');
保存为parent.js 然后调试运行 node --debug-brk  parent.js  并且打开GoogleChrome进行调试
其中第一行 "(function (exports, require, module, __filename, __dirname) {" 是nodejs内核对我们编写的js代码的包装,将我们写的js代码包装到一个函数内,相关详细可自行查阅资料。
开始跟踪fork方法,跟踪堆栈如下:
首先fork将进入exports.fork。 我们打开源代码查看,源码位于child_process.js中
exports.fork = function (modulePath /*, args, options*/) {

 
 // Get options and args arguments.
 
 var options, args, execArgv;
 
 if ( util. isArray( arguments[ 1])) {
   
 args = arguments [1 ];
   
 options = util ._extend ({}, arguments [2 ]);
  }
 else if (arguments [1 ] && typeof arguments[1] !== 'object' ) {
   
 throw new TypeError( 'Incorrect value of args option');
  }
 else {
   
 args = [];
   
 options = util ._extend ({}, arguments [1 ]);
  }

 
 // Prepare arguments for fork:
 
 execArgv = options .execArgv || process.execArgv;
 
 args = execArgv. concat([modulePath], args);

 
 // Leave stdin open for the IPC channel. stdout and stderr should be the
  // same as the parent's if silent isn't set.
 
 options. stdio = options. silent ? [ 'pipe', 'pipe', 'pipe', 'ipc'] :
      [
0, 1, 2, 'ipc' ];

 
 options. execPath = options. execPath || process. execPath;

 
 return spawn(options.execPath, args, options);
};

流程将从exports.fork=>spawn中。

打开spawn函数,代码仍然位于child_process.js中:
var spawn = exports. spawn = function( /*file, args, options*/) {
 
 var opts = normalizeSpawnArguments.apply( null, arguments);
 
 var options = opts.options;
 
 var child = new ChildProcess();

 
 child. spawn({
   
 file: opts.file,
   
 args: opts.args,
   
 cwd: options .cwd ,
   
 windowsVerbatimArguments : !!options .windowsVerbatimArguments ,
   
 detached: !!options .detached ,
   
 envPairs: opts.envPairs ,
   
 stdio: options .stdio ,
   
 uid: options .uid ,
   
 gid: options .gid
 
 });

 
 return child;
};

流程将从exports.fork=>spawn=>ChildProcess. spawn中

接下来看 ChildProcess. spawn函数,代码仍然位于child_process.js中,此函数比较长 这里只贴出关键代码:
var Process = process. binding( 'process_wrap').Process;
function ChildProcess() {
  .......  
 
 this. _handle = new Process();
  .......
}
ChildProcess. prototype. spawn = function(options) {
  .......
 
 var err = this._handle.spawn(options);
  .......
}

流程将进入this. _handle. spawn中,此函数已经属于原生函数了,原理可自行了解process. binding的实现。
代码位于src\process_wrap.cc中

  NODE_SET_PROTOTYPE_METHOD(constructor, "spawn", Spawn);

  static void Spawn(const FunctionCallbackInfo<Value>& args) {
         .......
         int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
       ......
  }

流程将进入uv_spawn函数,此函数位于deps\uv\src\win\process.c中

int uv_spawn(uv_loop_t* loop,
             uv_process_t* process,
             const uv_process_options_t* options) {
  .........
  if (!CreateProcessW(application_path,
                     arguments,
                     NULL,
                     NULL,
                     1,
                     process_flags,
                     env,
                     cwd,
                     &startup,
                     &info)) {
    /* CreateProcessW failed. */
    err = GetLastError();
    goto done;
  }
  .........
}

这下子终于看到windows下创建进程的api函数CreateProcessW了。
总结:
exports.fork(js层面)=>spawn(js层面)=>ChildProcess. spawn(js层面)=>Spawn(原生层面)=>uv_spawn(原生层面)=>CreateProcessW(操作系统api)