jsernews 1.0.1

<~>

ts 142 days ago.

原文链接:https://blog.safia.rocks/post/169734955505/how-does-the-node-main-process-start

好!所以在我之前的一篇博文中,我试图弄清楚 Node 主进程是如何初始化的。我最终没有在这方面取得成功。事实证明,作为文档来指引代码阅读的幻灯片有点过时,它引用的部分代码已经被移动了。谢天谢地,Node 的一位维护人员(@Fishrock123)在 Twitter 上为我澄清了这一点,并引导我走向正确的文件。所以现在我知道从正确的地方开始!

事实证明,src/node_main.cc 是一切开始的地方。让我们深入其中!

如果你熟悉 C/C++,那么这个文件的结构很容易掌握。main 函数是在程序启动时自动执行的函数。这个函数的最后一行让我们知道我们将所有参数传递给 node::Start 函数。

return node::Start(argc, argv);

我搜索了一下头文件和源文件,并在这里找到了 node::Start 函数的定义。当你考虑它包含的代码行数时,这个函数实际上并不是那么大,但那是因为它主要调用负责引导 Node 进程的其他函数。这里我来解释一下。函数中的第一对几行看起来像这样。

atexit([] () { uv_tty_reset_mode(); });
PlatformInit();
node::performance::performance_node_start = PERFORMANCE_NOW();

atexit 部分引起了我的注意。我曾在其他系统级代码中看到过这个函数,但对它做了什么只有一个模糊的概念:关于退出处理的一些事情。我决定在这一次做更多的研究,并发现它负责确定当前进程退出时该怎么做。在这种情况下,当 Node 进程退出时,将调用 uv_tty_reset_mode 函数。我不知道这个函数做了什么。尽管到目前为止,我知道它有一个 uv 前缀这一事实,意味着它与 libuv 有关,即某种方式的异步 I/O 库。我在 libuv 的文档中做了一些挖掘,并在这里找到了一些关于它的细节。本质上,当您退出 Node 进程时,您的 TTY 设置也会重置。

TTY 代表“TeleTYpewriter”,是从早期遗传下来的术语。电传打字机是用来连接电脑的外设。这是一个人在使用的照片。我总是发现这些从早期带过来的小术语是非常有趣的。给东西起名是件很困难的事情,所以当你可以使用旧的时为什么要用新的名称呢?

下一段代码调用 PlatformInit 函数。我看到很多关于“文件描述符”和“信号”的注释。我不会假装现在知道那些事情,但我现在正在参加一个操作系统课程,所以我可能会在将来更聪明地讲述它们!现在,我要说这个函数主要负责初始化一些平台相关的东西。对我来说已经足够!

接下来被调用的是 Init 函数。

Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);

通过阅读 Init 函数的代码,我发现它负责执行诸如注册内置模块,将开发人员在命令行提供的 V8 配置标志传递给 V8,并配置其他一些环境变量。这里引起我注意的一个函数调用就是这个。

node::RegisterBuiltinModules();

我认为找出更详细的情况会很有意思,但我可能会在稍后时间做到这一点。

最后,这个函数中的最后几行代码负责完成与 V8 相关的大量工作。

V8::Initialize();
node::performance::performance_v8_start = PERFORMANCE_NOW();
v8_initialized = true;
const int exit_code =
  Start(uv_default_loop(), argc, argv, exec_argc, exec_argv);
if (trace_enabled) {
  v8_platform.StopTracingAgent();
}
v8_initialized = false;
V8::Dispose();

// uv_run cannot be called from the time before the beforeExit callback
// runs until the program exits unless the event loop has any referenced
// handles after beforeExit terminates. This prevents unrefed timers
// that happen to terminate during shutdown from being run unsafely.
// Since uv_run cannot be called, uv_async handles held by the platform
// will never be fully cleaned up.
v8_platform.Dispose();

所以它看起来像 V8 被初始化,然后调用 Start 函数的多态定义并传递一个事件循环。然后在 V8 中会有一些跟踪代理的相关的逻辑。

如果您尝试使用 Google “tracing agent”来了解有关追踪代理的更多信息,您将获得完全意外的结果。

我做了一些关于跟踪代理的更多研究,并发现这个颇有帮助的 Wiki 文档。所以看起来追踪在 V8 中是一个相当新的功能。基本上,这部分代码负责配置 V8 跟踪器使用的特殊日志记录所需的任何自定义代码。这就说得通了。

接下来的两行对我来说有点奇怪。

v8_initialized = false;
V8::Dispose();

我们刚刚初始化了 V8 解释器,但现在我们将初始化状态设置为 false 并处理它。什么?为了弄清楚这里发生了什么,我决定进入代码文件的历史,看看这些更改何时引入代码库,并发现在大约9年前的这个提交。从这个提交来看起来像 V8::Dispose 行被添加回代码库是由于在特定的测试中导致一些错误之后。这仍然没有给我很好的见解,为什么它被处理,所以我决定找出 V8::Dispose 函数实际上做了什么。

我发现一些文档解释了 V8::Dispose 负责释放 V8 使用的任何资源。我开始意识到这里发生的事情是 Node 进程实际上在 Start 函数中得到了清理。所以在这里调用多态 Start 函数。

const int exit_code =
  Start(uv_default_loop(), argc, argv, exec_argc, exec_argv);

运行,直到您退出 Node。此时,您需要完成V8引擎的所有清理以及停止V8使用的跟踪代理。尽管只有大约50行代码(带有注释和换行符),但这个函数给了我很多我想在 Node 解释器探索的东西。

  • 内置模块如何加载?
  • PlatformInit 究竟在做什么?这些文件描述符和信号是怎么回事?
  • Start 函数的所有多态变体是做什么的?

我会尽力在其他博客文章中分开挑选这些内容。

整个过程非常有趣。这个代码库的 C/C++ 部分相当“粗糙”(译注:寓意外行只能看个大概)。特别是对于我来说,我只在学术环境中使用过 C/C++,所以阅读这个“行业强度”的代码一直很有趣。

这也表明,每个维护 Node 的人都在做一些真正严格和真棒的技术工作,我们都应该热爱并赞美他们!