花了差不多两周时间过了下primer C++5th,完成了《C++从入门到精通》。(手动滑稽)

  这两天看了下node源码的一些入口方法,其实还是比较懵逼的,语法倒不是难点,主要是大量的宏造成直接阅读上的不方便。

  有些宏感觉真是一点鸟用都没有,比如说:

#define LIKELY(expr) expr
#define UNLIKELY(expr) expr
#define PRETTY_FUNCTION_NAME ""

  这玩意翻译成JS大概就是:

const LIKELY = (expr) => expr;

  JS中有些情况确实是需要这么一个函数,比如vue组件中的data,主要是为了防止复杂类型的引用问题。但是在C++我就不明白了,深拷贝简直不要太简单,可能这里为了强行语义化吧。

  不过好在源码的规范超级棒,比如说:

1、内置常量宏都是全大写多单词下划线连接:FILE_TYPE_UNKNOWN代表无法识别的文件类型,对应16进制数字0x0000

2、函数名基本上能猜到用处:IsWindow7OrGreater函数判断操作系统是不是win7版本以上

3、注释随处可见

4、代码结构划分非常清晰,部分看不懂(或者不想看)的可以直接跳过,不影响后续内容理解

  另外IDE也好使,直接帮我把LINUX系统的兼容代码直接置灰了,跳转大部分情况也很好用。

  本来打算从简单一点的API来讲,比如说require方法是如何运作的,但是这东西过于简单,百度一搜一大把,已经是被人写烂的内容,所以不打算开这个坑(更重要的是那玩意是JS,如何对得起我学了两周C++)。

  因此我决定开一个目录坑,总览一下node从启动到运行,哪些部分我能看懂,理一理。

正文开始

  声明:本文目前基于node-v10.1.0,未来如有更改会说明。代码来源于官网下载的源码压缩包,详情见上一篇文章。

  首先上个图瞧一眼大概的流程:

  是不是很少很简单?对啊,因为我只能看懂这么多……

  开始吹。

  首先每个C++项目都有一个启动项目,每个项目里有一个主函数,当初我以为只能叫main,后来发现wmain也是OK的。

  而在node中,wmain是windows系统的主函数,main是LINUX系统的主函数。区分系统的关键在于一个特定的宏,windows是_WIN32,这个宏会被自动定义,相关源码如下:

#ifdef _WIN32
int wmain(int argc, wchar_t *wargv[]) {
// ...
return node::Start(argc, argv);
}
#else
// UNIX
#ifdef __linux__
int main(int argc, char *argv[]) {
// ...
}

  这里就不扯什么头文件了,主函数会调用node模块的Start方法,node模块文件名是node.cc,相当于项目的主文件,从这里开始,也从这里结束。

  从上面可以看到,这个函数有三大步(能看懂的),非常简单粗暴,一个一个讲。

Init

  这个函数相关源码如下:

void Init(int* argc,
const char** argv,
int* exec_argc,
const char*** exec_argv) {
// Initialize prog_start_time to get relative uptime.
prog_start_time = static_cast<double>(uv_now(uv_default_loop()));
// 加载内置模块
RegisterBuiltinModules();
// Make inherited handles noninheritable.
uv_disable_stdio_inheritance(); // 后面还有很多代码 但是我看不懂 流下了没技术的眼泪……
}

  需要关注的只要那个RegisterBuiltinModules方法,从名字也可以看出来,就是加载内置模块,描述有误,下一节修正。

  这个函数的定义也很奇妙,简直就是宏函数,如下:

void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
NODE_BUILTIN_MODULES(V)
#undef V
}

  函数的声明相信学JS的也能看懂是什么,主要是函数内容很奇怪,竟然是一个宏定义。

  用JS翻译一下那个宏,意思大概就是:

// C++:#define V(modname) _register_##modname();
const V = (modname) => `_register_${modname}`();

  当然,这个鸟代码是不可能执行的,只是为了方便理解。

  打个比方就能明白了,假如我调用了V(a),那么在执行的时候,实际上调用的是_register_a这个方法,双警号只是一个字符串拼接。

  好,解决了宏问题,可以看下这个NODE_BUILTIN_MODULES是什么函数了。然而,这也是一个宏,内容如下:

#define NODE_BUILTIN_MODULES(V)                                               \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V)

  看到这闪亮亮的格式,应该可以猜到,这三个东西还是宏!!!

  好在宏不过三代,随便点一个跳转就会发现,实际上这些宏都是为了调用具体的模块加载方法,比如:

#define NODE_BUILTIN_STANDARD_MODULES(V)                                      \
V(async_wrap) \
V(buffer) \
// 还有很多比如fs、url等

  这样把最初的函数简单转换一下,大概就是:

void RegisterBuiltinModules() {
_register_async_wrap();
_register_buffer();
// 等等
}

  至于这些方法的内容是什么?地点在哪?我还没找到。

Start

  V8引擎的加载就先忽略,触及到我的知识盲区。最后看一下那个内联Start函数,这个方法主要完成三件事:

1、初始化并加载全局变量global

2、加载辅助工具

3、事件轮询

  其中1、2两个都是在同一个函数中执行的,即LoadEnviroment,上一些关键的源码证明下:

void LoadEnvironment(Environment* env) {
// ... Local<String> loaders_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
Local<Function> loaders_bootstrapper =
GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
// 还有"internal/bootstrap/node.js" // 生成全局global对象的引用
Local<Object> global = env->context()->Global(); // ... // 设置全局对象
global->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "global"), global); // ... // 生成函数参数
Local<Value> loaders_bootstrapper_args[] = {
env->process_object(),
get_binding_fn,
get_linked_binding_fn,
get_internal_binding_fn
}; // 加载辅助函数
Local<Value> bootstrapped_loaders;
if (!ExecuteBootstrapper(env, loaders_bootstrapper,
arraysize(loaders_bootstrapper_args),
loaders_bootstrapper_args,
&bootstrapped_loaders)) {
return;
} // 加载Bootstrap Node.js
}

  这里通过Environment类生成了global对象,然后挂载到全局。

  辅助函数则是加载了internal/bootstrap中的两个JS文件,加载的时候参数传入了C++代码生成的特殊对象,配合对应的JS源文件就很好理解:

'use strict';
// internal/bootstrap/loader.js
(function bootstrapInternalLoaders(process, getBinding, getLinkedBinding,getInternalBinding) {
// ...模块内容
});

  这个JS文件内容看起来只是一个函数定义,但是实际上在启动后已经执行完了,四个参数是通过上述C++代码注入的,依次对应上面的4个东西。

  这里有一个小地方稍微讲讲吧,如果在启动时已经执行完了,那么看一下下面的代码:

// internal/bootstrap/loader.js
const { NativeModule } = require('internal/bootstrap/loaders');

  这是node中调用require方法的入口JS文件,理论上引入的应该是上面那个函数,不可能得到NativeModule对象的。

  不过node的require并不像webpack那么简单粗暴,readFile + parse直接得到文件输出对象,而是区分了内部模块与外部模块。对于内部模块,有一套新的逻辑,相关代码如下:

// require => _load
// 判断传入的字符串是否是内部模块名
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
// 调用另一套逻辑代码
return NativeModule.require(filename);
}

  而NativeModule就是一开始加载过的辅助工具JS,涉及到的代码如下:

const loaderExports = { internalBinding, NativeModule };
const loaderId = 'internal/bootstrap/loaders';
NativeModule.require = function(id) {
// Do not expose this to user land even with --expose-internals
// 对此require进行特殊处理
if (id === loaderId) {
return loaderExports;
}
// ...
}

  可以看到在require的地方做了特殊处理,会直接返回指定的对象,至于这两个对象是什么,后面再慢慢讲吧。

  值得注意的是那个注释,这个字符串十分特殊,node并不希望用户获取该模块,因为得到的对象拥有直接调用底层C++代码的能力,十分危险。

  完成准备工作后,就开始了事件轮询跑进程,相关代码如下:

{
SealHandleScope seal(isolate);
// 循环控制参数
bool more;
// 标记事件轮询开始
env.performance_state()->Mark(
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
// 开始event_loop
do {
uv_run(env.event_loop(), UV_RUN_DEFAULT);
v8_platform.DrainVMTasks(isolate);
more = uv_loop_alive(env.event_loop());
if (more)
continue;
RunBeforeExit(&env);
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env.event_loop());
} while (more == true);
// 标记事件轮询结束
env.performance_state()->Mark(
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
}

  事件机制的实现在第三方依赖模块uv当中,轮询过程则是这里的do-while循环。

  一旦node轮询结束,会返回一个exit_code,然后退出整个进程。

  完结,下一节写啥还得再想想。

深入出不来nodejs源码-流程总览的更多相关文章

  1. 深入出不来nodejs源码-V8引擎初探

    原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...

  2. 深入出不来nodejs源码-编译启动(1)

    整整弄了两天,踩了无数的坑,各种奇怪的error,最后终于编译成功了. 网上的教程基本上都过时了,或者是版本不对,都会报一些奇怪的错误,这里总结一下目前可行的流程. node版本:v10.1.0. 首 ...

  3. 深入出不来nodejs源码-timer模块(JS篇)

    鸽了好久,最近沉迷游戏,继续写点什么吧,也不知道有没有人看. 其实这个node的源码也不知道该怎么写了,很多模块涉及的东西比较深,JS和C++两头看,中间被工作耽搁回来就一脸懵逼了,所以还是挑一些简单 ...

  4. 深入出不来nodejs源码-从fs.stat方法来看node架构

    node的源码分析还挺多的,不过像我这样愣头完全平铺源码做解析的貌似还没有,所以开个先例,从一个API来了解node的调用链. 首先上一张整体的图,网上翻到的,自己懒得画: 这里的层次结构十分的清晰, ...

  5. 深入出不来nodejs源码-events模块

    这一节内容超级简单,纯JS,就当给自己放个假了,V8引擎和node的C++代码看得有点脑阔疼. 学过DOM的应该都知道一个API,叫addeventlistener,即事件绑定.这个东西贯穿了整个JS ...

  6. 深入出不来nodejs源码-timer模块(C++篇)

    终于可以填上坑了. 简单回顾一下之前JS篇内容,每一次setTimeout的调用,会在一个对象中添加一个键值对,键为延迟时间,值为一个链表,将所有该时间对应的事件串起来,图如下: 而每一个延迟键值对的 ...

  7. 深入出不来nodejs源码-内置模块引入再探

    我发现每次细看源码都能发现我之前写的一些东西是错误的,去改掉吧,又很不协调,不改吧,看着又脑阔疼…… 所以,这一节再探,是对之前一些说法的纠正,另外再缝缝补补一些新的内容. 错误在哪呢?在之前的初探中 ...

  8. 深入出不来nodejs源码-内置模块引入初探

    重新审视了一下上一篇的内容,配合源码发现有些地方说的不太对,或者不太严谨. 主要是关于内置模块引入的问题,当时我是这样描述的: 需要关注的只要那个RegisterBuiltinModules方法,从名 ...

  9. Flask 源码流程,上下文管理

    源码流程 创建对象 from flask import Flask """ 1 实例化对象 app """ app = Flask(__na ...

随机推荐

  1. 转:mysql触发器

    原文地址:http://www.cnblogs.com/nicholas_f/archive/2009/09/22/1572050.html CREATE TRIGGER <触发器名称>  ...

  2. 如何判断ScrollView滑动方向

    1/判断滚动视图左右滚动 { CGFloat startContentOffsetX;//滚动开始的坐标 CGFloat willEndContentOffsetX; //滚动即将停止的坐标 CGFl ...

  3. 软件测试实践平台(Mooctest)FAQ

    0. 背景 我们在<软件测试技术>课程使用 MOOCTEST (mooctest.net) 作为课程的实践教学平台. 在教学过程中学生们遇到了一些问题,本文将一一总结. 注意:本文在解决问 ...

  4. wpf Im

  5. Spring IOC 容器源码分析系列文章导读

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

  6. Deque(队列)

    目录 Deque 概述 特点 常用方法 双向队列操作 ArrayDeque Deque 概述 一个线性 collection,支持在两端插入和移除元素.名称 deque 是"double e ...

  7. vue.js 常用组件库

    vux github ui demo:https://github.com/airyland/vux Mint UI 项目主页:http://mint-ui.github.io/#!/zh-cndem ...

  8. mac终端常用命令

    1.du #查看文件目录大小 示例:查看DataCenter目录下所有文件/文件夹的大小 everSeeker:DataCenter pingping$ -h .9G ./Books 1.2M ./C ...

  9. Swift5 语言指南(二十七) 访问控制

    访问控制限制从其他源文件和模块中的代码访问部分代码.此功能使您可以隐藏代码的实现细节,并指定一个首选接口,通过该接口可以访问和使用该代码. 您可以为各个类型(类,结构和枚举)以及属于这些类型的属性,方 ...

  10. HTTP的请求报文与响应报文

    报文: 简单来说,报文就是也就是HTTP报文,作用是在各个系统之间进行和响应时用来交换与传输的数据单元,即站点一次性要发送的数据块,这些数据块以一些文本形式的元信息开头,这些信息描述了报文的内容及含义 ...