1模块设计1.1C++模块1.2内置JS模块1.3普通JS模块1.4Addon2事件循环3初始化4总结1和Node.js一样,Just也分为内置-在JS和C++模块在运行时初始化的时候也会处理相关的逻辑。1.1C++模块Node.js在初始化时将C++模块组织成一个链表,然后在加载时通??过模块名找到对应的模块配置,然后执行对应的钩子函数。只是使用C++映射来管理C++模块。目前只有五个C++模块。just::modules["sys"]=&_register_sys;just::modules["fs"]=&_register_fs;just::modules["net"]=&_register_net;just::modules["vm"]=&_register_vm;只是::modules["epoll"]=&_register_epoll;只要在初始化时执行上面的代码,就可以建立模块名和注册函数地址的关系。下面我们就来看看C++模块加载器是如何实现C++模块加载的。//加载C++模块functionlibrary(name,path){//如果有缓存则直接返回if(cache[name])returncache[name]//调用constlib=just.load(name)lib.type='module'//缓存cache[name]=libreturnlib}just.load是用C++实现的。voidjust::Load(constFunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();Localcontext=isolate->GetCurrentContext();//C++模块导出信息Localexports=ObjectTemplate::New(isolate);//加载一个模块if(args[0]->IsString()){String::Utf8Valuename(isolate,args[0]);autoiter=just::modules.find(*name);register_plugin_init=(*iter->second);//执行_init获取函数地址auto_register=reinterpret_cast(_init());//执行C++模块提供的注册函数,见C++模块,导出的属性为in_register(isolate,exports)intheexportsobject;}//返回导出的信息args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked());}1.2内置JS模块为了提高加载性能,将Node.js的内置JS模块保存在内存中。加载时通过模块名编译执行对应JS模块的源码,无需从硬盘添加。例如,net模块在内存中表示为.staticconstuint16_tnet_raw[]={47,47,32,67,111,112,121,114...};将上面的数字转换成字符["/","/","","C","o","p","y","r"],我们发现这些字符是注释处的一些net模块的开头。Just也使用了类似的概念,只是通过汇编来处理。.结束地址。值得一提的是,以上内容在代码段中,无法修改。下面我们来看一下如何注册内置的JS模块,以fs模块为例。//builtins.S汇编文件定义externchar_binary_lib_fs_js_start[];externchar_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的逻辑。structbuiltin{unsignedintsize;constchar*source;};std::mapjust::builtins;//注册JS模块voidjust::builtins_add(constchar*name,constchar*source,unsignedintsize){structbuiltin*b=newbuiltin();b->size=size;b->source=source;builtins[name]=b;}注册模块的逻辑很简单,就是建立关系模块名称和内容信息,然后看如何加载内置JS模块。函数requireNative(path){path=`lib/${path}.js`if(cache[path])returncache[path].exportsconst{vm}=justconstparams=['exports','require','module']constexports={}constmodule={exports,type:'native',dirName:appRoot}//从数据结构module.text=just.builtin(path)中获取模块对应的源码//编译constfun=vm。compile(module.text,path,params,[])module.function=funcache[path]=module//执行fun.call(exports,exports,p=>just.require(p,module),module)returnmodule.exports}loaded逻辑也很简单。根据模块名从map中编译执行源码,从而获取导出的属性。1.3普通JS模块普通JS模块是用户自定义的模块。自定义模块在第一次加载时需要实时从硬盘加载,所以只需要看一下加载逻辑就可以了。//通用JS模块加载函数require(path,parent={dirName:appRoot}){const{join,baseName,fileName}=just.pathif(path[0]==='@')path=`${appRoot}/lib/${path.slice(1)}/${fileName(path.slice(1))}.js`constext=path.split('.').slice(-1)[0]//jsorjsonfileif(ext==='js'||ext==='json'){letdirName=parent.dirNameconstfileName=join(dirName,path)//如果有缓存则返回if(cache[fileName])returncache[fileName].exportsdirName=baseName(fileName)constparams=['exports','require','module']constexports={}constmodule={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直接parseif(ext==='js'){constfun=just.vm.compile(module.text,fileName,params,[])fun.call(exports,exports,p=>require(p,module),module)}else{//如果是json文件,直接parsemodule.exports=JSON.parse(module.text)}returnmodule.exports}即可在中,普通JS模块的加载原理与Node.js类似,但也有一些区别。Node.js在加载JS模块时,会先判断是否是内置的JS模块。恰恰相反。1.4AddonAddon在Node.js中是一个动态库。Just中也是如此,原理类似。functionloadLibrary(path,name){if(cache[name])returncache[name]//打开动态库constandle=just.sys.dlopen(path,just.sys.RTLD_LAZY)//找到约定格式的函数动态库虚拟地址constptr=just.sys.dlsym(handle,`_register_${name}`)//以这个虚拟地址为入口执行函数constlib=just.load(ptr)lib.close=()=>just.sys。dlclose(handle)lib.type='module-external'cache[name]=libreturnlib}just.load是C++实现的函数。voidjust::Load(constFunctionCallbackInfo&args){Isolate*isolate=args.GetIsolate();Localcontext=isolate->GetCurrentContext();//C++模块导出信息Localexports=ObjectTemplate::New(isolate);//传入的是注册函数(动态库)的虚拟地址Localaddress64=Local::Cast(args[0]);void*ptr=reinterpret_cast(address64->Uint64Value());register_plugin_init=reinterpret_cast(ptr);auto_register=reinterpret_cast(_init());_register(isolate,exports);//返回导出的信息args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked());}因为Addon是一个动态库,底层原理是封装系统API,然后通过V8暴露给JS层。2事件循环Just的事件循环是基于epoll的,生产者生产的所有任务都是基于文件描述符的,比Node.js清晰简洁很多,没有各个阶段。只支持多个事件循环,但目前只有一个内置。让我们看看如何创建事件循环。//创建事件循环functioncreate(nevents=128){constloop=createLoop(nevents)factory.loops.push(loop)returnloop}functioncreateLoop(nevents=128){constevbuf=newArrayBuffer(nevents*12)constevents=newUint32Array(evbuf)//创建一个epollconstloopfd=create(EPOLL_CLOEXEC)consthandles={}//判断是否有事件触发函数poll(timeout=-1,sigmask){letr=0//对epoll_wait的封装if(sigmask){r=wait(loopfd,evbuf,timeout,sigmask)}else{r=wait(loopfd,evbuf,timeout)}if(r>0){letoff=0for(leti=0;i{factory.paused=falseletempty=0while(!factory.paused){lettotal=0for(constloopofffactory.loops){if(loop.count>0)loop.poll(ms)total+=loop.count}//执行microtasksrunMicroTasks()...},stop:()=>{factory.paused=true},}刚初始化完它会通过run进入事件循环,类似于Node.js。3初始化了解了一些核心实现之后,我们来看一下Just的初始化。intmain(intargc,char**argv){//忽略V8的一些逻辑//注册内置模块register_builtins();//初始化isolatejust::CreateIsolate(argc,argv,just_js,just_js_len);return0;}继续CreateIsolate(只列出核心代码)intjust::CreateIsolate(...){Isolate::CreateParamscreate_params;intstatusCode=0;//分配ArrayBuffer的内存分配器create_params.array_buffer_allocator=ArrayBuffer::Allocator::NewDefaultAllocator();Isolate*isolate=Isolate::New(create_params);{Isolate::Scopeisolate_scope(isolate);HandleScopehandle_scope(isolate);//新建一个对象作为全局对象Localglobal=ObjectTemplate::New(isolate);//新建一个对象作为核心对象,也是一个全局对象Localjust=ObjectTemplate::New(isolate);//给just对象设置一些属性just::Init(isolate,just);//设置全局属性justglobal->Set(String::NewFromUtf8Literal(isolate,"just",NewStringType::kNormal),just);//创建一个新的上下文,并使用global作为全局对象Localcontext=Context::New(isolate,NULL,global);Context::Scopecontext_scope(context);Local