本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
JS运行时Just源码解读
mycodes · 262浏览 · 发布于2021-08-27 +关注

像Node.js一样,Just也分为内置JS和C++模块,同样是在运行时初始化时会处理相关的逻辑。

1 模块的设计

像Node.js一样,Just也分为内置JS和C++模块,同样是在运行时初始化时会处理相关的逻辑。

1.1 C++模块

Node.js在初始化时,会把C++模块组织成一个链表,然后加载的时候通过模块名找到对应的模块配置,然后执行对应的钩子函数。Just则是用C++的map来管理C++模块。目前只有五个C++模块。

just::modules["sys"] = &_register_sys; 
just::modules["fs"] = &_register_fs; 
just::modules["net"] = &_register_net; 
just::modules["vm"] = &_register_vm; 
just::modules["epoll"] = &_register_epoll;

    Just在初始化时就会执行以上代码建立模块名称到注册函数地址的关系。我们看一下C++模块加载器时如何实现C++模块加载的。

    // 加载C++模块 
    function library (name, path) { 
      // 有缓存则直接返回 
      if (cache[name]) return cache[name] 
      // 调用 
      const lib = just.load(name) 
      lib.type = 'module' 
      // 缓存起来 
      cache[name] = lib 
      return lib 
    }

      just.load是C++实现的。

      void just::Load(const FunctionCallbackInfo<Value> &args) { 
        Isolate *isolate = args.GetIsolate(); 
        Local<Context> context = isolate->GetCurrentContext(); 
        // C++模块导出的信息 
        Local<ObjectTemplate> exports = ObjectTemplate::New(isolate); 
        // 加载某个模块 
        if (args[0]->IsString()) { 
          String::Utf8Value name(isolate, args[0]); 
          auto iter = just::modules.find(*name); 
          register_plugin _init = (*iter->second); 
          // 执行_init拿到函数地址 
          auto _register = reinterpret_cast<InitializerCallback>(_init()); 
          // 执行C++模块提供的注册函数,见C++模块,导出的属性在exports对象中 
          _register(isolate, exports); 
        } 
        // 返回导出的信息 
        args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked()); 
      }

        1.2 内置JS模块

        为了提升加载性能,Node.js的内置JS模块是保存到内存里的,加载的时候,通过模块名获取对应的JS模块源码编译执行,而不需要从硬盘加。比如net模块在内存里表示为。

        static const uint16_t net_raw[] = { 
         47, 47, 32, 67,111,112,121,114... 
        };

          以上的数字转成字符是["/", "/", " ", "C", "o", "p", "y", "r"],我们发现这些字符是net模块开始的一些注释。Just同样使用了类似的理念,不过Just是通过汇编来处理的。

          .global _binary_lib_fs_js_start 
          _binary_lib_fs_js_start: 
                  .incbin "lib/fs.js" 
                  .global _binary_lib_fs_js_end 
          _binary_lib_fs_js_end: 
          ...

            Just定义里一系列的全局变量 ,比如以上的binary_lib_fs_js_start变量,它对应的值是lib/fs.js的内容,binary_lib_fs_js_end表示结束地址。

            值得一提的是,以上的内容是在代码段的,所以是不能被修改的。接着我们看看如何注册内置JS模块,以fs模块为例。

            // builtins.S汇编文件里定义 
            extern char _binary_lib_fs_js_start[]; 
            extern char _binary_lib_fs_js_end[]; 
            
            just::builtins_add("lib/fs.js", _binary_lib_fs_js_start, _binary_lib_fs_js_end - _binary_lib_fs_js_start);

              builtins_add三个参数分别是模块名,模块内容的虚拟开始地址,模块内容大小。来看一下builtins_add的逻辑。

              struct builtin { 
                unsigned int size; 
                const char* source; 
              }; 
              
              std::map<std::string, just::builtin*> just::builtins; 
              
              // 注册JS模块 
              void just::builtins_add (const char* name, const char* source,  unsigned int size) { 
                struct builtin* b = new builtin(); 
                b->size = size; 
                b->source = source; 
                builtins[name] = b; 
              }

                注册模块的逻辑很简单,就是建立模块名和内容信息的关系,接着看如何加载内置JS模块。

                function requireNative (path) { 
                      path = `lib/${path}.js` 
                      if (cache[path]) return cache[path].exports 
                      const { vm } = just 
                      const params = ['exports', 'require', 'module'] 
                      const exports = {} 
                      const module = { exports, type: 'native', dirName: appRoot } 
                      // 从数据结构中获得模块对应的源码 
                      module.text = just.builtin(path) 
                      // 编译 
                      const fun = vm.compile(module.text, path, params, []) 
                      module.function = fun 
                      cache[path] = module 
                      // 执行 
                      fun.call(exports, exports, p => just.require(p, module), module) 
                      return module.exports 
                }

                  加载的逻辑也很简单,根据模块名从map里获取源码编译执行,从而拿到导出的属性。

                  1.3 普通JS模块

                  普通JS模块就是用户自定义的模块。用户自定义的模块首次加载时都是需要从硬盘实时加载的,所以只需要看加载的逻辑。

                  // 一般JS模块加载器 
                    function require (path, parent = { dirName: appRoot }) { 
                      const { join, baseName, fileName } = just.path 
                      if (path[0] === '@') path = `${appRoot}/lib/${path.slice(1)}/${fileName(path.slice(1))}.js` 
                      const ext = path.split('.').slice(-1)[0] 
                      // js或json文件 
                      if (ext === 'js' || ext === 'json') { 
                        let dirName = parent.dirName 
                        const fileName = join(dirName, path) 
                        // 有缓存则返回 
                        if (cache[fileName]) return cache[fileName].exports 
                        dirName = baseName(fileName) 
                        const params = ['exports', 'require', 'module'] 
                        const exports = {} 
                        const module = { exports, dirName, fileName, type: ext } 
                        // 文件存在则直接加载 
                        if (just.fs.isFile(fileName)) { 
                          module.text = just.fs.readFile(fileName) 
                        } else { 
                          // 否则尝试加载内置JS模块 
                          path = fileName.replace(appRoot, '') 
                          if (path[0] === '/') path = path.slice(1) 
                             module.text = just.builtin(path) 
                          } 
                        } 
                        cache[fileName] = module 
                        // js文件则编译执行,json则直接parse 
                        if (ext === 'js') { 
                          const fun = just.vm.compile(module.text, fileName, params, []) 
                          fun.call(exports, exports, p => require(p, module), module) 
                        } else { 
                          // 是json文件则直接parse 
                          module.exports = JSON.parse(module.text) 
                        } 
                        return module.exports 
                      }

                    Just里,普通JS模块的加载原理和Node.js类似,但是也有些区别,Node.js加载JS模块时,会优先判断是不是内置JS模块,Just则相反。

                    1.4 Addon

                    Node.js里的Addon是动态库,Just里同样是,原理也类似。

                    function loadLibrary (path, name) { 
                          if (cache[name]) return cache[name] 
                          // 打开动态库 
                          const handle = just.sys.dlopen(path, just.sys.RTLD_LAZY) 
                          // 找到动态库里约定格式的函数的虚拟地址 
                          const ptr = just.sys.dlsym(handle, `_register_${name}`) 
                          // 以该虚拟地址为入口执行函数 
                          const lib = just.load(ptr) 
                          lib.close = () => just.sys.dlclose(handle) 
                          lib.type = 'module-external' 
                          cache[name] = lib 
                          return lib 
                    }

                      just.load是C++实现的函数。

                      void just::Load(const FunctionCallbackInfo<Value> &args) { 
                        Isolate *isolate = args.GetIsolate(); 
                        Local<Context> context = isolate->GetCurrentContext(); 
                        // C++模块导出的信息 
                        Local<ObjectTemplate> exports = ObjectTemplate::New(isolate); 
                        // 传入的是注册函数的虚拟地址(动态库) 
                         Local<BigInt> address64 = Local<BigInt>::Cast(args[0]); 
                         void* ptr = reinterpret_cast<void*>(address64->Uint64Value()); 
                         register_plugin _init = reinterpret_cast<register_plugin>(ptr); 
                         auto _register = reinterpret_cast<InitializerCallback>(_init()); 
                         _register(isolate, exports); 
                        // 返回导出的信息 
                        args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked()); 
                      }

                        因为Addon是动态库,所以底层原理都是对系统API的封装,再通过V8暴露给JS层使用。

                        2 事件循环

                        Just的事件循环是基于epoll的,所有生产者生产的任务都是基于文件描述符的,相比Node.js清晰且简洁了很多,也没有了各种阶段。Just支持多个事件循环,不过目前只有内置的一个。我们看看如何创建一个事件循环。

                        // 创建一个事件循环 
                        function create(nevents = 128) { 
                          const loop = createLoop(nevents) 
                          factory.loops.push(loop) 
                          return loop 
                        } 
                        
                        function createLoop (nevents = 128) { 
                          const evbuf = new ArrayBuffer(nevents * 12) 
                          const events = new Uint32Array(evbuf) 
                          // 创建一个epoll 
                          const loopfd = create(EPOLL_CLOEXEC) 
                          const handles = {} 
                          // 判断是否有事件触发 
                          function poll (timeout = -1, sigmask) { 
                            let r = 0 
                            // 对epoll_wait的封装 
                            if (sigmask) { 
                              r = wait(loopfd, evbuf, timeout, sigmask) 
                            } else { 
                              r = wait(loopfd, evbuf, timeout) 
                            } 
                            if (r > 0) { 
                              let off = 0 
                              for (let i = 0; i < r; i++) { 
                                const fd = events[off + 1] 
                                // 事件触发,执行回调 
                                handles[fd](fd, events[off]) 
                                off += 3 
                              } 
                            } 
                            return r 
                          } 
                          // 注册新的fd和事件 
                          function add (fd, callback, events = EPOLLIN) { 
                            const r = control(loopfd, EPOLL_CTL_ADD, fd, events) 
                            // 保存回调 
                            if (r === 0) { 
                              handles[fd] = callback 
                              instance.count++ 
                            } 
                            return r 
                          } 
                          // 删除之前注册的fd和事件 
                          function remove (fd) { 
                            const r = control(loopfd, EPOLL_CTL_DEL, fd) 
                            if (r === 0) { 
                              delete handles[fd] 
                              instance.count-- 
                            } 
                            return r 
                          } 
                          // 更新之前注册的fd和事件 
                          function update (fd, events = EPOLLIN) { 
                            const r = control(loopfd, EPOLL_CTL_MOD, fd, events) 
                            return r 
                          } 
                          const instance = { fd: loopfd, poll, add, remove, update, handles, count: 0 } 
                          return instance 
                        }

                          事件循环本质是epoll的封装,一个事件循环对应一个epoll fd,后续生产任务的时候,就通过操作epoll fd,进行增删改查,比如注册一个新的fd和事件到epoll中,并保存对应的回调。然后通过wait进入事件循环,有事件触发后,就执行对应的回调。接着看一下事件循环的执行。

                          { 
                                  // 执行事件循环,即遍历每个事件循环 
                            run: (ms = -1) => { 
                              factory.paused = false 
                              let empty = 0 
                              while (!factory.paused) { 
                                let total = 0 
                                for (const loop of factory.loops) { 
                                  if (loop.count > 0) loop.poll(ms) 
                                  total += loop.count 
                                } 
                                // 执行微任务 
                                runMicroTasks() 
                                ... 
                            }, 
                          
                            stop: () => { 
                              factory.paused = true 
                            }, 
                          }

                            Just初始化完毕后就会通过run进入事件循环,这个和Node.js是类似的。

                            3 初始化

                            了解了一些核心的实现后,来看一下Just的初始化。

                            int main(int argc, char** argv) { 
                              // 忽略V8的一些逻辑 
                              // 注册内置模块 
                              register_builtins(); 
                              // 初始化isolate 
                              just::CreateIsolate(argc, argv, just_js, just_js_len); 
                              return 0; 
                            }

                              继续看CreateIsolate(只列出核心代码)

                              int just::CreateIsolate(...) { 
                                Isolate::CreateParams create_params; 
                                int statusCode = 0; 
                                // 分配ArrayBuffer的内存分配器 
                                create_params.array_buffer_allocator =  ArrayBuffer::Allocator::NewDefaultAllocator(); 
                                Isolate *isolate = Isolate::New(create_params); 
                                { 
                                  Isolate::Scope isolate_scope(isolate); 
                                  HandleScope handle_scope(isolate); 
                              
                                  // 新建一个对象为全局对象 
                                  Local<ObjectTemplate> global = ObjectTemplate::New(isolate); 
                                  // 新建一个对象为核心对象,也是个全局对象 
                                  Local<ObjectTemplate> just = ObjectTemplate::New(isolate); 
                                  // 设置一些属性到just对象 
                                  just::Init(isolate, just); 
                                  // 设置全局属性just 
                                  global->Set(String::NewFromUtf8Literal(isolate, "just", NewStringType::kNormal), just); 
                                  // 新建上下文,并且以global为全局对象 
                                  Local<Context> context = Context::New(isolate, NULL, global); 
                                  Context::Scope context_scope(context); 
                                  Local<Object> globalInstance = context->Global(); 
                                  // 设置全局属性global指向全局对象 
                                  globalInstance->Set(context, String::NewFromUtf8Literal(isolate,  
                                    "global",  
                                    NewStringType::kNormal), globalInstance).Check(); 
                              
                                  // 编译执行just.js,just.js是核心的jS代码 
                                  MaybeLocal<Value> maybe_result = script->Run(context); 
                                } 
                              }

                                初始化的时候设置了全局对象global和just,所以在JS里可以直接访问,然后再给just对象设置各种属性,接着看just.js的逻辑。

                                function main (opts) { 
                                    // 获得C++模块加载器和缓存 
                                    const { library, cache } = wrapLibrary() 
                                
                                    // 挂载C++模块到JS 
                                    just.vm = library('vm').vm 
                                    just.loop = library('epoll').epoll 
                                    just.fs = library('fs').fs 
                                    just.net = library('net').net 
                                    just.sys = library('sys').sys 
                                    // 环境变量 
                                    just.env = wrapEnv(just.sys.env) 
                                    // JS模块加载器 
                                    const { requireNative, require } = wrapRequire(cache) 
                                
                                    Object.assign(just.fs, requireNative('fs')) 
                                
                                    just.path = requireNative('path') 
                                    just.factory = requireNative('loop').factory 
                                    just.factory.loop = just.factory.create(128) 
                                    just.process = requireNative('process') 
                                    just.setTimeout = setTimeout 
                                    just.library = library 
                                    just.requireNative = requireNative 
                                    just.net.setNonBlocking = setNonBlocking 
                                    just.require = global.require = require 
                                    just.require.cache = cache 
                                    // 执行用户js 
                                    just.vm.runScript(just.fs.readFile(just.args[1]), scriptName) 
                                    // 进入时间循环 
                                    just.factory.run() 
                                  }

                                  4 总结

                                  Just的底层实现在modules里,里面的实现非常清晰,里面对大量系统API和开源库进行了封装。另外使用了timerfd支持定时器,而不是自己去维护相关逻辑。核心模块代码非常值得学习,有兴趣的可以直接去看对应模块的源码。Just的代码整体很清晰,而且目前的代码量不大,通过阅读里面的代码,对系统、网络、V8的学习都有帮助,另外里面用到了很多开源库,也可以学到如何使用一些优秀的开源库,甚至阅读库的源码。


                                  相关推荐

                                  PHP实现部分字符隐藏

                                  沙雕mars · 1325浏览 · 2019-04-28 09:47:56
                                  Java中ArrayList和LinkedList区别

                                  kenrry1992 · 908浏览 · 2019-05-08 21:14:54
                                  Tomcat 下载及安装配置

                                  manongba · 970浏览 · 2019-05-13 21:03:56
                                  JAVA变量介绍

                                  manongba · 962浏览 · 2019-05-13 21:05:52
                                  什么是SpringBoot

                                  iamitnan · 1086浏览 · 2019-05-14 22:20:36
                                  加载中

                                  0评论

                                  评论
                                  我从事编程工作,现在在一家网络公司上班,偶尔也是发布博客,逛论坛等,希望可以在这里交到志同道合的朋友。
                                  分类专栏
                                  小鸟云服务器
                                  扫码进入手机网页