【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:零基础爬虫第一天

相关推荐
不会飞的鲨鱼6 小时前
抖音验证码滑动轨迹原理(续)
javascript·爬虫·python
晚星star7 小时前
《深入浅出 Node.js》第四章:异步编程 详细总结
前端·node.js
失败又激情的man7 小时前
爬虫逆向之阿里系cookie acw_sc__v2 逆向分析
前端·javascript·爬虫
大布布将军8 小时前
⚡️ 性能加速器:利用 Redis 实现接口高性能缓存
前端·数据库·经验分享·redis·程序人生·缓存·node.js
盼哥PyAI实验室8 小时前
Python 爬虫核心基础:请求与响应机制全解析(从 GET 请求到 JSON 分页实战)
爬虫·python·json
幺零九零零9 小时前
全栈程序员-前端第一节-npm 是什么?
前端·npm·node.js
晚星star1 天前
2.2 Node的模块实现
前端·node.js
电商API_180079052471 天前
淘宝评论API技术解析与调用实战指南
开发语言·爬虫·信息可视化
嫂子的姐夫1 天前
008-字体反爬:猫眼
爬虫·逆向·混淆·字体反爬