您现在的位置是:亿华云 > 人工智能

JS运行时Just源码解读

亿华云2025-10-04 03:43:05【人工智能】5人已围观

简介1 模块的设计 1.1 C++模块 1.2 内置JS模块 1.3 普通JS模块 1.4 Addon 2 事件循环 3

1 模块的运行t源设计 1.1 C++模块 1.2 内置JS模块 1.3 普通JS模块 1.4 Addon 2 事件循环 3 初始化 4 总结

1 模块的设计

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

1.1 C++模块

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

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++模块加载的运行t源。

// 加载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++模块导出的运行t源信息   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++模块,码解导出的运行t源属性在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的学习都有帮助,另外里面用到了很多开源库,也可以学到如何使用一些优秀的开源库,甚至阅读库的源码。

源码解析地址:

https://github.com/theanarkh/read-just-0.1.4-code

很赞哦!(118)