【node源码-2】Node.js 启动流程

开始下一步跟进:

1. 启动入口:StartInternal()src/node_main.cc

Node.js 进程启动后(main()StartInternal()),核心逻辑在这里分流。

cpp 复制代码
uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);  // 启用 libuv 空闲时间指标,用于性能监控

// 处理 SEA(Single Executable Application)打包模式
if (per_process::cli_options->experimental_sea_config) {
  // 打包成单可执行文件 → 直接构建 blob 并退出
  return sea::BuildSingleExecutableBlob(...);
}

// 处理 --build-snapshot(生成 V8 快照,用于 SEA 或自定义加速)
if (per_process::cli_options->per_isolate->build_snapshot) {
  // 生成快照 blob → 写入文件 → 退出
  return GenerateAndWriteSnapshotData(...);
}

// 普通模式:加载内置快照(编译时嵌入的可执行文件中的 blob)
if (!LoadSnapshotData(&snapshot_data)) {
  return ExitCode::kStartupSnapshotFailure;  // 快照损坏或缺失,启动失败
}

// 创建主 Node 实例(包含 Isolate、Context、EventLoop 等)
NodeMainInstance main_instance(
  snapshot_data,               // 内置快照数据
  uv_default_loop(),           // libuv 事件循环
  per_process::v8_platform.Platform(),  // V8 平台抽象
  result->args(),              // 命令行参数
  result->exec_args()          // 执行参数
);

return main_instance.Run();  // 阻塞运行,直到进程退出

内置快照 vs 用户快照

  • 内置快照:Node.js 编译时生成,嵌入可执行文件,只包含核心 JS(bootstrap 等)。加载失败 → 启动失败。
  • 用户快照 :运行时生成(--build-snapshot),可包含自定义代码(如 require('jsdom') 并初始化 global.window)。
    不能 在内置快照中加载 jsdom(因为 jsdom 是第三方模块,且快照是编译时生成的)。
    可以 用用户快照实现预加载:node --build-snapshot snapshot.jsnode --snapshot-blob snapshot.blob app.js
2. 核心运行函数:NodeMainInstance::Run()src/node_main_instance.cc
cpp 复制代码
void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
  if (*exit_code != ExitCode::kNoFailure) return;

  if (!sea::MaybeLoadSingleExecutableApplication(env)) {
    // SEA 加载失败或未启用 → 普通模式
    LoadEnvironment(env, StartExecutionCallback{});  // 初始化 Node.js 环境
  }

  // 进入事件循环,直到进程退出
  *exit_code = SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError);

  // 内存泄漏检查(调试模式)
#if defined(LEAK_SANITIZER)
  __lsan_do_leak_check();
#endif
}
  • SEA:如果打包成单文件,加载预打包代码 → 跳过普通环境加载。
  • 普通模式 :调用 LoadEnvironment() → 初始化 libuv、诊断、编译缓存 → 执行 bootstrap → 进入事件循环。
3. 环境初始化:LoadEnvironment()src/node_environment.cc
cpp 复制代码
MaybeLocal<Value> LoadEnvironment(Environment* env, StartExecutionCallback cb, EmbedderPreloadCallback preload = {}) {
  env->InitializeLibuv();              // libuv 事件循环准备
  env->InitializeDiagnostics();        // Inspector、trace_event 等诊断工具
  if (preload) env->set_embedder_preload(std::move(preload));  // 可选嵌入式预加载
  env->InitializeCompileCache();       // V8 代码缓存加速

  return StartExecution(env, cb);      // 真正执行 JS 入口
}

关键转折StartExecution(env, cb) 根据命令行参数决定运行哪个 JS 入口文件。

4. 入口选择:StartExecution()src/node_bootstrap.cc
cpp 复制代码
MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
  // ... 省略回调作用域等

  // 根据 CLI 参数选择入口 JS 文件
  if (env->worker_context()) {
    return env->RunSnapshotDeserializeMain();  // Worker 线程
  }
  if (first_argv == "inspect") return StartExecution(env, "internal/main/inspect");
  if (per_process::cli_options->print_help) return StartExecution(env, "internal/main/print_help");
  if (env->options()->has_eval_string && !force_repl) return StartExecution(env, "internal/main/eval_string");
  if (env->options()->test_runner) return StartExecution(env, "internal/main/test_runner");
  if (!first_argv.empty() && first_argv != "-") {
    return StartExecution(env, "internal/main/run_main_module");  // node app.js → 这里!
  }
  // REPL 或其他
}

最常见场景node app.jsinternal/main/run_main_module.js

5. 预执行准备:prepareMainThreadExecution()lib/internal/process/pre_execution.js
javascript 复制代码
prepareMainThreadExecution(expandArgv1 = false, initializeModules = true);
  • 初始化 processglobal 等基本环境。
  • 支持懒加载(lazy loading)模块,避免启动时加载不必要的代码。
6. 真正运行主模块:run_main_module.js
javascript 复制代码
const mainEntry = prepareMainThreadExecution(!isEntryURL);
markBootstrapComplete();  // 标记 bootstrap 完成

// 支持 monkey-patch
require('internal/modules/cjs/loader').Module.runMain(mainEntry);
  • mainEntry:入口文件名(app.js)。
  • Module.runMain(mainEntry) → 进入 CommonJS 加载器。
7. CommonJS 模块加载器:internal/modules/cjs/loader.js

核心类Module(每个 .js 文件对应一个实例)

javascript 复制代码
function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.filename = null;
  this.loaded = false;
  this.children = [];
  // ...
}

加载流程

  1. Module._load(path):检查缓存 → new Module() → module.load(filename)
  2. module.load() → 根据后缀调用 Module._extensions['.js'] → 读取文件内容 → module._compile()

_compile()(执行模块代码的核心)

javascript 复制代码
Module.prototype._compile = function(content, filename, format) {
  // 处理 TypeScript(实验)
  if (isTypeScript(format)) {
    content = stripTypeScriptModuleTypes(content, filename);
    format = adjustFormat(format);
  }

  // 包装成函数(CommonJS)
  const result = wrapSafe(filename, content, this, format);
  compiledWrapper = result.function;

  // 如果是 ESM → 转到 ESM 加载器
  if (result.canParseAsESM || format === 'module') {
    loadESMFromCJS(this, filename, format, content);
    return;
  }

  // CommonJS 执行
  const require = makeRequireFunction(this, redirects);
  const exports = this.exports;
  const module = this;

  // 支持 --inspect-brk 调试断点
  if (this[kIsMainSymbol] && getOptionValue('--inspect-brk')) {
    result = callAndPauseOnStart(compiledWrapper, ...);
  } else {
    result = ReflectApply(compiledWrapper, exports, [exports, require, module, filename, dirname]);
  }

  return result;
};

wrapSafe():把模块代码包装成:

javascript 复制代码
(function (exports, require, module, __filename, __dirname) {
  // 用户代码在这里
})

执行compiledWrapper(exports, require, module, __filename, __dirname) → 你的 app.js 正式运行!

总结流程(简易图)
plain 复制代码
StartInternal() (C++)
  ↓
NodeMainInstance::Run() (C++)
  ↓
LoadEnvironment() → StartExecution() → 选择入口 JS
  ↓ (node app.js)
internal/main/run_main_module.js
  ↓
prepareMainThreadExecution()
  ↓
Module.runMain() → Module._load() → module.load() → module._compile()
  ↓
你的 app.js 执行 → 进入事件循环(uv_run()) → 直到进程结束


你的 app.js 顶层代码执行(同步部分)
  ↓
异步任务注册(setTimeout、fs.readFile 等) → 进入 libuv 事件循环
  ↓
事件循环运行(uv_run()) → 处理所有异步回调、微任务
  ↓
事件循环清空 → 返回退出码
  ↓
NodeMainInstance::Run() 返回
  ↓
StartInternal() 返回
  ↓
进程退出(清理资源)

node 是怎么从c++转js的

1. 整体背景:Node.js 启动流程

  • Node.js 进程启动 → main()StartInternal()NodeMainInstance::Run()LoadEnvironment()StartExecution(env, "internal/main/run_main_module")
  • 到这一行之前:全在 C++ 层(V8 Isolate、Environment 创建、libuv 初始化、快照加载等)。
  • 从这一行开始 :Node.js 正式执行内置的 JS 代码(internal/main/run_main_module.js),进入 JS 世界。

2. StartExecution(env, cb) 的核心实现(src/node_environment.cc / src/node_bootstrap.cc

StartExecution 的简化版实现(省略了部分回调作用域和错误处理):

cpp 复制代码
MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
  // 创建 InternalCallbackScope(用于追踪回调栈、异步钩子等)
  InternalCallbackScope callback_scope(env, ...);

  // 根据 CLI 参数决定运行哪个内置 JS 文件
  if (env->worker_context()) {
    return env->RunSnapshotDeserializeMain();  // Worker 线程
  }

  // ... 其他分支(如 inspect、--eval、--test 等)

  // 最常见:node app.js → 执行 run_main_module.js
  return ExecuteBootstrapScript(env, "internal/main/run_main_module");
}
  • ExecuteBootstrapScript (内部函数):
    • 加载内置 JS 文件(这些文件在 Node.js 编译时嵌入可执行文件中)。
    • 使用 V8 的 v8::Script::Compile + v8::Script::Run 执行 JS 代码。
    • 关键:V8 引擎在 C++ 中直接运行 JS 脚本,并将执行结果返回给 C++。

3. 从 C++ 执行 JS 的具体步骤

Node.js 使用 V8 的 API 在 C++ 中运行 JS 代码(这是从 C++ 转 JS 的核心桥梁):

  1. 创建 V8 Contextenv->context()
    • 每个 Environment 都有一个独立的 V8 v8::Context(JS 全局执行上下文)。
  2. 加载内置 JS 源码
    • 内置 JS 文件(如 internal/main/run_main_module.js)在 Node.js 编译时被打包成字符串(通过 node_snapshot.ccnode_bootstrap.cc)。
    • StartExecution 拿到源码字符串:const char* source = "const { ... } = require('internal/process/pre_execution'); ..."
  3. 编译 JS 源码
cpp 复制代码
v8::Local<v8::Script> script = v8::Script::Compile(context, v8::String::NewFromUtf8(isolate, source).ToLocalChecked());
  1. 运行 JS 脚本
cpp 复制代码
v8::Local<v8::Value> result = script->Run(context);
复制代码
- V8 引擎在这里**真正开始执行 JS**:解析、编译、运行 `run_main_module.js`。
- 执行过程中,JS 代码可以调用 `require()` 等(这些是 C++ 绑定的内置函数)。

4. internal/main/run_main_module.js 做了什么?

这个 JS 文件是C++ → JS 的桥梁,它接管了后续启动:

javascript 复制代码
// internal/main/run_main_module.js
const { prepareMainThreadExecution } = require('internal/process/pre_execution');
const mainEntry = prepareMainThreadExecution(...);  // 准备 process、global 等

markBootstrapComplete();  // 标记 bootstrap 完成

// 真正运行你的 app.js
require('internal/modules/cjs/loader').Module.runMain(mainEntry);
  • prepareMainThreadExecution :初始化 processglobalrequire 等全局对象。
  • Module.runMain :调用 Module._load(mainEntry)module.load()module._compile() → 执行你的 app.js 顶层代码。

5. 总结:C++ → JS 的完整切换过程

步骤 层级 关键操作 说明
1 C++ StartExecution(env, "internal/main/run_main_module") 选择入口 JS 文件
2 C++ ExecuteBootstrapScript V8 编译 + 运行 JS 源码
3 V8 引擎 v8::Script::Compile + Run 真正开始执行 JS
4 JS run_main_module.js 执行 初始化环境 → 调用 Module.runMain
5 JS Module._load_compile 执行你的 app.js 顶层代码
6 C++ SpinEventLoopInternal 进入 libuv 事件循环,处理异步

一句话概括

Node.js 通过 V8 的 C++ APIScript::Compile + Run)在 C++ 层直接执行内置 JS 代码(internal/main/run_main_module.js),从而无缝切换到 JS 世界。之后一切(模块加载、事件循环、用户代码)都在 JS 层完成。

是在哪里调用的v8

刚才的流程 StartExecution 漏了一步,接着往下跟进的话是在StartExecution:

D:\Code\C\node\src\node_realm.cc

plain 复制代码
MaybeLocal<Value> StartExecution(Environment* env, const char* main_script_id) {
  EscapableHandleScope scope(env->isolate());  // V8 的 HandleScope,确保临时对象被 GC
  CHECK_NOT_NULL(main_script_id);              // 确保脚本 ID 不为空(如 "internal/main/run_main_module")
  Realm* realm = env->principal_realm();       // 获取当前 Realm(V8 的执行上下文,包含 Context)

  return scope.EscapeMaybe(realm->ExecuteBootstrapper(main_script_id));  // 执行内置 JS
}


MaybeLocal<Value> Realm::ExecuteBootstrapper(const char* id) {
  EscapableHandleScope scope(isolate());  // V8 HandleScope:管理临时对象 GC
  Local<Context> ctx = context();         // 当前 V8 Context(JS 全局上下文)

  // 关键:调用 BuiltinLoader 编译 + 执行内置 JS
  MaybeLocal<Value> result =
      env()->builtin_loader()->CompileAndCall(ctx, id, this);

  // 错误处理:如果执行失败(比如 max call stack),清空异步 ID 栈
  if (result.IsEmpty()) {
    env()->async_hooks()->clear_async_id_stack();
  }

  return scope.EscapeMaybe(result);  // 返回结果(MaybeLocal<Value>)
}
然后到CompileAndCall

======>

plain 复制代码
MaybeLocal<Value> BuiltinLoader::CompileAndCall(Local<Context> context,
                                                const char* id,
                                                Realm* realm) {
  Isolate* isolate = Isolate::GetCurrent();  // 获取当前 V8 Isolate(JS 运行时实例)

  // 根据 id 检测脚本类型,并为特定 bootstrap 脚本准备参数
  // internal/bootstrap/realm: 需要 process, getLinkedBinding, getInternalBinding, primordials
  if (strcmp(id, "internal/bootstrap/realm") == 0) {
    Local<Value> get_linked_binding;
    Local<Value> get_internal_binding;

    // 创建 C++ 函数模板 → V8 Function(绑定 C++ 函数到 JS)
    // binding::GetLinkedBinding:处理 linked bindings(如模块的 C++ 绑定)
    if (!NewFunctionTemplate(isolate, binding::GetLinkedBinding)
             ->GetFunction(context)
             .ToLocal(&get_linked_binding) ||
        !NewFunctionTemplate(isolate, binding::GetInternalBinding)
             ->GetFunction(context)
             .ToLocal(&get_internal_binding)) {
      return MaybeLocal<Value>();  // 创建失败,返回空
    }

    // 准备参数数组(传递给 JS 脚本的形参)
    Local<Value> arguments[] = {
      realm->process_object(),      // process 对象
      get_linked_binding,           // getLinkedBinding 函数
      get_internal_binding,         // getInternalBinding 函数
      realm->primordials()          // primordials(原始全局对象,如 Object、Array 等)
    };

    // 调用重载版本,传入 argc 和 argv
    return CompileAndCall(
        context, id, arraysize(arguments), &arguments[0], realm);
  } 
  // 其他 bootstrap/main 脚本:需要 process, require, internalBinding, primordials
  else if (strncmp(id, "internal/main/", strlen("internal/main/")) == 0 ||
           strncmp(id, "internal/bootstrap/", strlen("internal/bootstrap/")) == 0) {
    // 准备参数数组
    Local<Value> arguments[] = {
      realm->process_object(),              // process
      realm->builtin_module_require(),      // require 函数(内置模块加载)
      realm->internal_binding_loader(),     // internalBinding 函数
      realm->primordials()                  // primordials
    };

    return CompileAndCall(
        context, id, arraysize(arguments), &arguments[0], realm);
  }

  // 其他情况(如 internal/per_context/*)不支持直接调用此重载
  // 这些脚本的参数在 JS 层或 InitializePrimordials() 中生成
  UNREACHABLE();  // 不可达代码(assert 失败时崩溃)
}
第二次重载:
plain 复制代码
MaybeLocal<Value> BuiltinLoader::CompileAndCall(Local<Context> context,
                                                const char* id,
                                                int argc,
                                                Local<Value> argv[],
                                                Realm* optional_realm) {
  // 先查找并编译脚本,返回 JS Function
  MaybeLocal<Function> maybe_fn = LookupAndCompile(context, id, optional_realm);
  Local<Function> fn;
  if (!maybe_fn.ToLocal(&fn)) {
    return MaybeLocal<Value>();  // 编译失败,返回空
  }

  // 执行 Function(V8 API!)
  Local<Value> undefined = Undefined(Isolate::GetCurrent());  // this = undefined
  return fn->Call(context, undefined, argc, argv);  // 调用 JS 函数,传入 argc 和 argv
}
  • 核心:LookupAndCompile → 编译脚本成 Local。
  • 执行:fn->Call 是 V8 的 Function::Call API,直接在 C++ 中调用 JS 函数(this 为 undefined,参数为 argv)。
  • 这就是 C++ 调用 V8 执行 JS 的最终入口:先编译成 Function,再用 Call 执行。

===》

就进入了v8
plain 复制代码
MaybeLocal<v8::Value> Function::Call(v8::Isolate* isolate,
                                     Local<Context> context,
                                     v8::Local<v8::Value> recv, int argc,
                                     v8::Local<v8::Value> argv[]) {
  // 将 v8::Isolate* 转为内部 i::Isolate*(V8 内部实现)
  auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);

  // 性能追踪:记录执行时间和统计
  TRACE_EVENT_CALL_STATS_SCOPED(i_isolate, "v8", "V8.Execute");
  EnterV8Scope<InternalEscapableScope> api_scope{i_isolate, context, RCCId::kAPI_Function_Call};

  // 计时器:记录执行耗时
  i::TimerEventScope<i::TimerEventExecute> timer_scope(i_isolate);
  i::NestedTimedHistogramScope execute_timer(i_isolate->counters()->execute(), i_isolate);

  // 将 v8::Function* 转为内部 Handle(安全打开)
  auto self = Utils::OpenDirectHandle(this);

  // 检查 Function 是否有效
  Utils::ApiCheck(!self.is_null(), "v8::Function::Call", "Function to be called is a null pointer");

  // 打开接收者(this 值)
  auto recv_obj = Utils::OpenDirectHandle(*recv);

  // 准备参数数组(转为 V8 内部 Handle)
  auto args = PrepareArguments(argc, argv);

  // 执行调用:核心!调用 V8 内部 Execution::Call
  return api_scope.EscapeMaybe(
      Utils::ToMaybeLocal(i::Execution::Call(i_isolate, self, recv_obj, args)));
}
//D:\Code\C\node\deps\v8\src\api\api.cc  

流程详细分解(从调用到执行)

  1. 进入函数:
    • 参数:isolate、context、recv(this)、argc、argv(参数数组)。
    • 典型调用:fn->Call(context, undefined, 4, argv)(bootstrap 时传入 process、require 等)。
  2. 性能追踪和作用域:
    • TRACE_EVENT_CALL_STATS_SCOPED:记录 V8 执行统计(用于 --prof 等)。
    • EnterV8Scope:V8 API 作用域,确保临时 Local<> 对象在返回时被 Escape(避免 GC 误删)。
    • TimerEventScope 和 NestedTimedHistogramScope:V8 内部计时器,统计执行耗时。
  3. 安全打开 Handle:
    • Utils::OpenDirectHandle(this):将 v8::Function* 转为 i::Handle<i::JSFunction>(V8 内部对象)。
    • Utils::ApiCheck:如果 Function 是 null,抛出 API 异常。
    • Utils::OpenDirectHandle(*recv):打开 this 值(通常是 undefined)。
  4. 准备参数:
    • PrepareArguments(argc, argv):将 v8::Localv8::Value\[\] 转为 V8 内部 Handle 数组(i::Handle<i::Object>)。
    • 内部会分配临时内存,确保参数是 i::Object。
  5. 核心执行:
    • i::Execution::Call(i_isolate, self, recv_obj, args):
      • 这是 V8 JavaScript 执行引擎 的入口(src/execution.cc 或 src/execution/execution.cc)。
      • 内部流程(简化版):
        1. 进入执行上下文(栈帧、微任务队列)。
        2. 如果 Function 是 JSFunction → 触发 JIT 编译或解释执行。
        3. 调用 self->Call(i::JSFunction::Call)。
        4. 处理 this(recv_obj)、参数(args)。
        5. 执行 JS 代码(bootstrap 脚本)。
        6. 处理异常 → 返回 MaybeHandle(可能失败)。
      • 如果是 Native Function(C++ 绑定的)→ 调用 C++ 回调。
  6. 返回:
    • Utils::ToMaybeLocal(...):将 i::MaybeHandle 转为 v8::MaybeLocal。
    • api_scope.EscapeMaybe(...):Escape 临时句柄,返回给调用者。

next继续跟进Execution::Call

更多文章,敬请关注gzh:零基础爬虫第一天

相关推荐
没事别瞎琢磨10 分钟前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨20 分钟前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨1 小时前
六、输出捕获与截断
人工智能·node.js
没事别瞎琢磨1 小时前
七、敏感路径预检——Protected Paths
人工智能·node.js
没事别瞎琢磨2 小时前
五、进程执行——spawn、超时与进程树清理
人工智能·node.js
没事别瞎琢磨2 小时前
四、命令风险分级与审批策略
人工智能·node.js
深蓝电商API2 小时前
Playwright深入浅出:从入门到企业级项目实战
爬虫·playwright
没事别瞎琢磨2 小时前
三、配置系统——默认值与解析
人工智能·node.js
小白学大数据3 小时前
爬虫性能天花板:asyncio赋能 Aiohttp,并发提速 10 倍
开发语言·爬虫·数据分析
右耳朵猫AI3 小时前
Node.js周刊2026W22 | Node.js 26、Deno 2.8、Rolldown 1.0、TypeORM 1.0、Bun v1.3.14
node.js