Node.js源碼初探~我很好奇

前言:javascript

最近在看Node.js,看了一段時間後便想着看看Node.js源碼,本身本地調試調試;如今便說說這個過程當中的坑,以及一些須要注意的地方;html

      Node.js須要必定C++基礎,建議看完C++Primer再看,不然V8的好多表達方式,指針,引用,模板之類的會看不懂;java

      代碼已上傳GitHub地址:   https://github.com/sven36/cNodenode

      完整編譯的文件太大,上傳GitHub不成功,下載請用百度網盤https://pan.baidu.com/s/1jIC4xCypython

先說一下node.js啓動過程:c++

       node.js的src目錄下的源代碼大部分都是node.js的模塊文件;其實初始化node.js用到的文件只有:node.h , node.cc , env.h , env_inl.h , node_internals.h , node_javascript.h , node_javascript.cc , util.h , util.cc ,以及用js2c.py工具將內置JavaScript代碼轉成C++裏面的數組,生成的node_natives.h文件;git

我實現的過程是按照node.js的啓動過程,須要哪一個方法就實現哪一個方法,能合併的方法都儘可能合併,能忽略的細節都儘可能忽略,下面簡單說說node.js啓動主要的方法和過程;github

入口是在node_main.cc中,根據平臺的不一樣會進入不一樣的Start方法,個人是windows平臺,運行的wmain方法,而後調用了node::Start方法;bootstrap

node::Start方法的具體實現是在node.cc中,node.cc也是node的核心代碼;在啓動過程當中須要注意的有四個方法:StartNodeInstance,在StartNodeInstance裏面調用的CreateEnvironment方法,windows

在CreateEnvironment方法裏面調用的SetupProcessObject方法,以及CreateEnvironment結束以後調用的LoadEnvironment方法;

StartNodeInstance在初始化v8虛擬機,綁定做用域以後就會調用CreateEnvironment方法;CreateEnvironment會初始化Environment類,該方法定義在env_inl.h文件中;

CreateEnvironment在初始化Environment類以後,會先初始化v8的的CPU分析器,再初始化handle的回收方法,而後就會初始化全局process對象;

代碼以下:

Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate);
process_template->SetClassName(node::OneByteString(isolate, "process", sizeof("process") - 1));

Local<Object> process_object = process_template->GetFunction()->NewInstance(context).ToLocalChecked();
env->set_process_object(process_object);

在v8裏面一個template是javascript函數的藍圖。你可使用一個template來將c++函數和結構體包裝到javascript對象中,讓javascirpt腳原本使用它。因此咱們常常調用的process.binding,process.cpuUsage,process.dlopen等方法,實際上是在調用包裝成js腳本的C++方法;

接下來的SetupProcessObject方法就是具體初始化process對象的方法了,除了只讀屬性process.versions,process.moduleLoadList等;更重要的是經過Environment::SetMethod方法,把C++方法包裝成js腳本方法;好比:

  env->SetMethod(process, "binding", Binding);

就把process.binding方法綁定成C++裏面的Binding方法;

初始化process對象以後就會調用LoadEnvironment方法,在該方法中咱們能夠看的:

Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(),
"bootstrap_node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);

其中ExecuteString方法會調用v8::Script::Compile方法來編譯傳入的js文件;那咱們知道了bootstrap_node.js是一個被調用的js文件,在這個裏面又發生了什麼呢?

大概能夠分爲:初始化全局 process 對象上的部分屬性 / 行爲,初始化全局的一些 timer 方法,初始化全局 console 對象等一些方法;這裏咱們不展開了,咱們看一看node的js模塊是如何引入的;


function NativeModule(id) {
this.filename = `${id}.js`;
this.id = id;
this.exports = {};
this.loaded = false;
this.loading = false;
}

NativeModule._source = process.binding('natives');
NativeModule._cache = {};

咱們看的原生模塊會調用process.binding('natives')方法,咱們找到node.cc裏面的Binding方法看看會進行哪些操做;

else if (!strcmp(*module_v, "natives")) {
exports = Object::New(env->isolate());
DefineJavaScript(env, exports);
cache->Set(module, exports);

咱們看到當傳入的參數是natives的時候會調用DefineJavaScript方法,咱們看看這個方法作了什麼;

void DefineJavaScript(Environment* env, Local<Object> target) {
auto context = env->context();
#define V(id) \
do { \
auto key = \
String::NewFromOneByte( \
env->isolate(), id##_name, NewStringType::kNormal, \
sizeof(id##_name)).ToLocalChecked(); \
auto value = \
String::NewExternalOneByte( \
env->isolate(), &id##_external_data).ToLocalChecked(); \
CHECK(target->Set(context, key, value).FromJust()); \
} while (0);
NODE_NATIVES_MAP(V)
#undef V
}

是一個複雜的宏定義,看着不太好理解那咱們本身抽離一個實現看看;

void DefineJavaScript(Environment* env, Local<Object> target){
auto context = env->context();
do {
auto key = String::NewFromOneByte(env->isolate(), buffer_name, NewStringType::kNormal, sizeof(buffer_name)).ToLocalChecked();
auto value = String::NewExternalOneByte(env->isolate(), &buffer_external_data).ToLocalChecked();
} while (0);

}

buffer_name是什麼呢?在js2c.py把內置js文件轉成C++數組的node_natives.h文件裏面咱們能夠找到:

static const uint8_t buffer_name[] = {
98,117,102,102,101,114};

因此process.binding('natives')實際上是使用v8引擎,編譯咱們內置的js文件;

到這裏node.js的啓動過程和文件模塊機制基本上就說了個大概了,其它諸如非核心模塊的引入,和buffer,stream等應C++完成核心部分,其它部分用js包裝或導出的模塊就須要你們本身去了解了;

 

我在過程當中碰到一些問題和須要注意的地方:


說明:https://github.com/sven36/cNode這個項目是不能編譯的,由於這個是我最開始的版本(不過代碼我都同步成最新的了),v8等編譯命令都沒設置,我原本想上傳一個新的GitHub的但是項目太大了,老上傳失敗;
      因此我就上傳了個百度網盤https://pan.baidu.com/s/1jIC4xCy
      這個是能夠直接編譯的,有須要的能夠去下載;

 

編譯:我用的win10的環境,具體編譯請參考Node.js的編譯說明:https://github.com/nodejs/node/blob/master/BUILDING.md

        其中的坑:Python應該是2.6或2.7,不要裝3.0或以上的,由於node.js有的py文件3.0編譯出錯Visual C++ Build Tools必須是2015,安裝官方的連接就是,由於Node.js在Windows平臺的編譯用的是vs2015的v140平臺工具集,用低版本的或者vs2017的v141平臺工具集都會報錯;

 

        編譯流程:安裝完python和Visual C++ Build Tools2015以後,下載node.js的源碼  node-v6.10.0.tar.gz  而後用Visual C++ Build Tools2015的命令行運行解壓目錄下的vcbuild.bat處理文件就會開始編譯了;編譯成功後解壓目錄下就會出現.sln文件就可使用vs打開了(vs也須要是2015或2017,而且項目的平臺工具集也須要是v140不然編譯報錯);如圖:

 

 

V8引擎:

          由於node.js其實就是嵌入V8的一個C++程序;首先要對v8的Isolate,LocalHandle,Scope等概念有一個瞭解,此處不展開了,請參考這個文檔:

           https://github.com/Chunlin-Li/Chunlin-Li.github.io/blob/master/blogs/javascript/V8_Embedder's_Guide_CHS.md

          在個人代碼裏面我也加了一些註釋,在src目錄下的node.cpp文件內,能夠參考;

 

C++編譯與日常的面向對象編譯方式的不一樣

          在.NET或Java之類的語言中,能夠沒必要關注方法的聲明順序,好比:

          private void a(){

               b();

         }

         private void b(){

               console.log(1);

         }

         不過在C++中這樣是不行的,調用b以前,必須徹底聲明b;也就是把b放在方法a以前(這也是node.cpp文件中爲何把開始方法Start放在最下端);

     

爲何Node.js的頭文件要用namespace node包起來:

                 是爲了更好的解耦node.js的各個模塊;在C++中命名空間相同而內部成員名字不一樣,它們會自動合併爲同一個命名空間,能夠理解爲追加;

 

Node.js裏面比較複雜的宏定義:

    Node.js和V8用了不少複雜的宏定義,若是不理解它們看起來會很費力;在C++中,宏定義裏面的##符號是鏈接字符串的意思;

    好比:env-inl.h文件下的宏定義:

#define VP(PropertyName, StringValue) V(v8::Private, PropertyName, StringValue)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName, StringValue)
#define V(TypeName, PropertyName, StringValue)                                \
  inline                                                                      \
  v8::Local<TypeName> Environment::IsolateData::PropertyName() const {        \
    /* Strings are immutable so casting away const-ness here is okay. */      \
    return const_cast<IsolateData*>(this)->PropertyName ## _.Get(isolate());  \
  }
    PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
        PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP

它最後的編譯形式是:

    inline v8::Local<v8::String> Environment::IsolateData::async_queue_string() const {
        return const_cast<IsolateData*>(this)->async_queue_string_.Get(isolate());
    }

這種地方多一些耐心仔細分析一下就會懂了;我寫的代碼裏面基本上這種宏定義第一個字符串我都是這種手寫的,其他的是按照原先的宏定義的方式聲明,能夠參考;

 

使用VS編譯須要注意的地方

      node.js項目文件實際上是用google的GYP工具生產的,因此VS項目編譯的過程也在各個項目下的.gyp文件內。

      瞭解gyp工具請參考:http://www.cnblogs.com/nanvann/p/3913880.html#conditions

 

固然還有不少具體的C++問題就須要靠本身多思考,勤搜索了;

 最後附上編譯成功的圖片:

相關文章
相關標籤/搜索