开始下一步跟进:
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.js→node --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.js → internal/main/run_main_module.js。
5. 预执行准备:prepareMainThreadExecution()(lib/internal/process/pre_execution.js)
javascript
prepareMainThreadExecution(expandArgv1 = false, initializeModules = true);
- 初始化
process、global等基本环境。 - 支持懒加载(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 = [];
// ...
}
加载流程:
Module._load(path):检查缓存 → new Module() →module.load(filename)。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 的核心桥梁):
- 创建 V8 Context (
env->context())- 每个
Environment都有一个独立的 V8v8::Context(JS 全局执行上下文)。
- 每个
- 加载内置 JS 源码
- 内置 JS 文件(如
internal/main/run_main_module.js)在 Node.js 编译时被打包成字符串(通过node_snapshot.cc或node_bootstrap.cc)。 StartExecution拿到源码字符串:const char* source = "const { ... } = require('internal/process/pre_execution'); ..."
- 内置 JS 文件(如
- 编译 JS 源码
cpp
v8::Local<v8::Script> script = v8::Script::Compile(context, v8::String::NewFromUtf8(isolate, source).ToLocalChecked());
- 运行 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 :初始化
process、global、require等全局对象。 - 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++ API (Script::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
流程详细分解(从调用到执行)
- 进入函数:
- 参数:isolate、context、recv(this)、argc、argv(参数数组)。
- 典型调用:fn->Call(context, undefined, 4, argv)(bootstrap 时传入 process、require 等)。
- 性能追踪和作用域:
- TRACE_EVENT_CALL_STATS_SCOPED:记录 V8 执行统计(用于 --prof 等)。
- EnterV8Scope:V8 API 作用域,确保临时 Local<> 对象在返回时被 Escape(避免 GC 误删)。
- TimerEventScope 和 NestedTimedHistogramScope:V8 内部计时器,统计执行耗时。
- 安全打开 Handle:
- Utils::OpenDirectHandle(this):将 v8::Function* 转为 i::Handle<i::JSFunction>(V8 内部对象)。
- Utils::ApiCheck:如果 Function 是 null,抛出 API 异常。
- Utils::OpenDirectHandle(*recv):打开 this 值(通常是 undefined)。
- 准备参数:
- PrepareArguments(argc, argv):将 v8::Localv8::Value[] 转为 V8 内部 Handle 数组(i::Handle<i::Object>)。
- 内部会分配临时内存,确保参数是 i::Object。
- 核心执行:
- i::Execution::Call(i_isolate, self, recv_obj, args):
- 这是 V8 JavaScript 执行引擎 的入口(src/execution.cc 或 src/execution/execution.cc)。
- 内部流程(简化版):
- 进入执行上下文(栈帧、微任务队列)。
- 如果 Function 是 JSFunction → 触发 JIT 编译或解释执行。
- 调用 self->Call(i::JSFunction::Call)。
- 处理 this(recv_obj)、参数(args)。
- 执行 JS 代码(bootstrap 脚本)。
- 处理异常 → 返回 MaybeHandle(可能失败)。
- 如果是 Native Function(C++ 绑定的)→ 调用 C++ 回调。
- i::Execution::Call(i_isolate, self, recv_obj, args):
- 返回:
- Utils::ToMaybeLocal(...):将 i::MaybeHandle 转为 v8::MaybeLocal。
- api_scope.EscapeMaybe(...):Escape 临时句柄,返回给调用者。
next继续跟进Execution::Call
更多文章,敬请关注gzh:零基础爬虫第一天
