JS運(yùn)行時(shí)Just源碼解讀
- 1 模塊的設(shè)計(jì)
- 1.1 C++模塊
- 1.2 內(nèi)置JS模塊
- 1.3 普通JS模塊
- 1.4 Addon
- 2 事件循環(huán)
- 3 初始化
- 4 總結(jié)
1 模塊的設(shè)計(jì)
像Node.js一樣,Just也分為內(nèi)置JS和C++模塊,同樣是在運(yùn)行時(shí)初始化時(shí)會(huì)處理相關(guān)的邏輯。
1.1 C++模塊
Node.js在初始化時(shí),會(huì)把C++模塊組織成一個(gè)鏈表,然后加載的時(shí)候通過(guò)模塊名找到對(duì)應(yīng)的模塊配置,然后執(zhí)行對(duì)應(yīng)的鉤子函數(shù)。Just則是用C++的map來(lái)管理C++模塊。目前只有五個(gè)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在初始化時(shí)就會(huì)執(zhí)行以上代碼建立模塊名稱到注冊(cè)函數(shù)地址的關(guān)系。我們看一下C++模塊加載器時(shí)如何實(shí)現(xiàn)C++模塊加載的。
- // 加載C++模塊
- function library (name, path) {
- // 有緩存則直接返回
- if (cache[name]) return cache[name]
- // 調(diào)用
- const lib = just.load(name)
- lib.type = 'module'
- // 緩存起來(lái)
- cache[name] = lib
- return lib
- }
just.load是C++實(shí)現(xiàn)的。
- void just::Load(const FunctionCallbackInfo<Value> &args) {
- Isolate *isolate = args.GetIsolate();
- Local<Context> context = isolate->GetCurrentContext();
- // C++模塊導(dǎo)出的信息
- Local<ObjectTemplate> exports = ObjectTemplate::New(isolate);
- // 加載某個(gè)模塊
- if (args[0]->IsString()) {
- String::Utf8Value name(isolate, args[0]);
- auto iter = just::modules.find(*name);
- register_plugin _init = (*iter->second);
- // 執(zhí)行_init拿到函數(shù)地址
- auto _register = reinterpret_cast<InitializerCallback>(_init());
- // 執(zhí)行C++模塊提供的注冊(cè)函數(shù),見C++模塊,導(dǎo)出的屬性在exports對(duì)象中
- _register(isolate, exports);
- }
- // 返回導(dǎo)出的信息
- args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked());
- }
1.2 內(nèi)置JS模塊
為了提升加載性能,Node.js的內(nèi)置JS模塊是保存到內(nèi)存里的,加載的時(shí)候,通過(guò)模塊名獲取對(duì)應(yīng)的JS模塊源碼編譯執(zhí)行,而不需要從硬盤加。比如net模塊在內(nèi)存里表示為。
- static const uint16_t net_raw[] = {
- 47, 47, 32, 67,111,112,121,114...
- };
以上的數(shù)字轉(zhuǎn)成字符是["/", "/", " ", "C", "o", "p", "y", "r"],我們發(fā)現(xiàn)這些字符是net模塊開始的一些注釋。Just同樣使用了類似的理念,不過(guò)Just是通過(guò)匯編來(lái)處理的。
- .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變量,它對(duì)應(yīng)的值是lib/fs.js的內(nèi)容,binary_lib_fs_js_end表示結(jié)束地址。
值得一提的是,以上的內(nèi)容是在代碼段的,所以是不能被修改的。接著我們看看如何注冊(cè)內(nèi)置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三個(gè)參數(shù)分別是模塊名,模塊內(nèi)容的虛擬開始地址,模塊內(nèi)容大小。來(lái)看一下builtins_add的邏輯。
- struct builtin {
- unsigned int size;
- const char* source;
- };
- std::map<std::string, just::builtin*> just::builtins;
- // 注冊(cè)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;
- }
注冊(cè)模塊的邏輯很簡(jiǎn)單,就是建立模塊名和內(nèi)容信息的關(guān)系,接著看如何加載內(nèi)置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 }
- // 從數(shù)據(jù)結(jié)構(gòu)中獲得模塊對(duì)應(yīng)的源碼
- module.text = just.builtin(path)
- // 編譯
- const fun = vm.compile(module.text, path, params, [])
- module.function = fun
- cache[path] = module
- // 執(zhí)行
- fun.call(exports, exports, p => just.require(p, module), module)
- return module.exports
- }
加載的邏輯也很簡(jiǎn)單,根據(jù)模塊名從map里獲取源碼編譯執(zhí)行,從而拿到導(dǎo)出的屬性。
1.3 普通JS模塊
普通JS模塊就是用戶自定義的模塊。用戶自定義的模塊首次加載時(shí)都是需要從硬盤實(shí)時(shí)加載的,所以只需要看加載的邏輯。
- // 一般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 {
- // 否則嘗試加載內(nèi)置JS模塊
- path = fileName.replace(appRoot, '')
- if (path[0] === '/') path = path.slice(1)
- module.text = just.builtin(path)
- }
- }
- cache[fileName] = module
- // js文件則編譯執(zhí)行,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類似,但是也有些區(qū)別,Node.js加載JS模塊時(shí),會(huì)優(yōu)先判斷是不是內(nèi)置JS模塊,Just則相反。
1.4 Addon
Node.js里的Addon是動(dòng)態(tài)庫(kù),Just里同樣是,原理也類似。
- function loadLibrary (path, name) {
- if (cache[name]) return cache[name]
- // 打開動(dòng)態(tài)庫(kù)
- const handle = just.sys.dlopen(path, just.sys.RTLD_LAZY)
- // 找到動(dòng)態(tài)庫(kù)里約定格式的函數(shù)的虛擬地址
- const ptr = just.sys.dlsym(handle, `_register_${name}`)
- // 以該虛擬地址為入口執(zhí)行函數(shù)
- const lib = just.load(ptr)
- lib.close = () => just.sys.dlclose(handle)
- lib.type = 'module-external'
- cache[name] = lib
- return lib
- }
just.load是C++實(shí)現(xiàn)的函數(shù)。
- void just::Load(const FunctionCallbackInfo<Value> &args) {
- Isolate *isolate = args.GetIsolate();
- Local<Context> context = isolate->GetCurrentContext();
- // C++模塊導(dǎo)出的信息
- Local<ObjectTemplate> exports = ObjectTemplate::New(isolate);
- // 傳入的是注冊(cè)函數(shù)的虛擬地址(動(dòng)態(tài)庫(kù))
- 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);
- // 返回導(dǎo)出的信息
- args.GetReturnValue().Set(exports->NewInstance(context).ToLocalChecked());
- }
因?yàn)锳ddon是動(dòng)態(tài)庫(kù),所以底層原理都是對(duì)系統(tǒng)API的封裝,再通過(guò)V8暴露給JS層使用。
2 事件循環(huán)
Just的事件循環(huán)是基于epoll的,所有生產(chǎn)者生產(chǎn)的任務(wù)都是基于文件描述符的,相比Node.js清晰且簡(jiǎn)潔了很多,也沒(méi)有了各種階段。Just支持多個(gè)事件循環(huán),不過(guò)目前只有內(nèi)置的一個(gè)。我們看看如何創(chuàng)建一個(gè)事件循環(huán)。
- // 創(chuàng)建一個(gè)事件循環(huán)
- 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)
- // 創(chuàng)建一個(gè)epoll
- const loopfd = create(EPOLL_CLOEXEC)
- const handles = {}
- // 判斷是否有事件觸發(fā)
- function poll (timeout = -1, sigmask) {
- let r = 0
- // 對(duì)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]
- // 事件觸發(fā),執(zhí)行回調(diào)
- handles[fd](fd, events[off])
- off += 3
- }
- }
- return r
- }
- // 注冊(cè)新的fd和事件
- function add (fd, callback, events = EPOLLIN) {
- const r = control(loopfd, EPOLL_CTL_ADD, fd, events)
- // 保存回調(diào)
- if (r === 0) {
- handles[fd] = callback
- instance.count++
- }
- return r
- }
- // 刪除之前注冊(cè)的fd和事件
- function remove (fd) {
- const r = control(loopfd, EPOLL_CTL_DEL, fd)
- if (r === 0) {
- delete handles[fd]
- instance.count--
- }
- return r
- }
- // 更新之前注冊(cè)的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
- }
事件循環(huán)本質(zhì)是epoll的封裝,一個(gè)事件循環(huán)對(duì)應(yīng)一個(gè)epoll fd,后續(xù)生產(chǎn)任務(wù)的時(shí)候,就通過(guò)操作epoll fd,進(jìn)行增刪改查,比如注冊(cè)一個(gè)新的fd和事件到epoll中,并保存對(duì)應(yīng)的回調(diào)。然后通過(guò)wait進(jìn)入事件循環(huán),有事件觸發(fā)后,就執(zhí)行對(duì)應(yīng)的回調(diào)。接著看一下事件循環(huán)的執(zhí)行。
- {
- // 執(zhí)行事件循環(huán),即遍歷每個(gè)事件循環(huán)
- 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
- }
- // 執(zhí)行微任務(wù)
- runMicroTasks()
- ...
- },
- stop: () => {
- factory.paused = true
- },
- }
Just初始化完畢后就會(huì)通過(guò)run進(jìn)入事件循環(huán),這個(gè)和Node.js是類似的。
3 初始化
了解了一些核心的實(shí)現(xiàn)后,來(lái)看一下Just的初始化。
- int main(int argc, char** argv) {
- // 忽略V8的一些邏輯
- // 注冊(cè)內(nèi)置模塊
- register_builtins();
- // 初始化isolate
- just::CreateIsolate(argc, argv, just_js, just_js_len);
- return 0;
- }
繼續(xù)看CreateIsolate(只列出核心代碼)
- int just::CreateIsolate(...) {
- Isolate::CreateParams create_params;
- int statusCode = 0;
- // 分配ArrayBuffer的內(nèi)存分配器
- create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
- Isolate *isolate = Isolate::New(create_params);
- {
- Isolate::Scope isolate_scope(isolate);
- HandleScope handle_scope(isolate);
- // 新建一個(gè)對(duì)象為全局對(duì)象
- Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
- // 新建一個(gè)對(duì)象為核心對(duì)象,也是個(gè)全局對(duì)象
- Local<ObjectTemplate> just = ObjectTemplate::New(isolate);
- // 設(shè)置一些屬性到j(luò)ust對(duì)象
- just::Init(isolate, just);
- // 設(shè)置全局屬性just
- global->Set(String::NewFromUtf8Literal(isolate, "just", NewStringType::kNormal), just);
- // 新建上下文,并且以global為全局對(duì)象
- Local<Context> context = Context::New(isolate, NULL, global);
- Context::Scope context_scope(context);
- Local<Object> globalInstance = context->Global();
- // 設(shè)置全局屬性global指向全局對(duì)象
- globalInstance->Set(context, String::NewFromUtf8Literal(isolate,
- "global",
- NewStringType::kNormal), globalInstance).Check();
- // 編譯執(zhí)行just.js,just.js是核心的jS代碼
- MaybeLocal<Value> maybe_result = script->Run(context);
- }
- }
初始化的時(shí)候設(shè)置了全局對(duì)象global和just,所以在JS里可以直接訪問(wèn),然后再給just對(duì)象設(shè)置各種屬性,接著看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
- // 環(huán)境變量
- 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
- // 執(zhí)行用戶js
- just.vm.runScript(just.fs.readFile(just.args[1]), scriptName)
- // 進(jìn)入時(shí)間循環(huán)
- just.factory.run()
- }
4 總結(jié)
Just的底層實(shí)現(xiàn)在modules里,里面的實(shí)現(xiàn)非常清晰,里面對(duì)大量系統(tǒng)API和開源庫(kù)進(jìn)行了封裝。另外使用了timerfd支持定時(shí)器,而不是自己去維護(hù)相關(guān)邏輯。核心模塊代碼非常值得學(xué)習(xí),有興趣的可以直接去看對(duì)應(yīng)模塊的源碼。Just的代碼整體很清晰,而且目前的代碼量不大,通過(guò)閱讀里面的代碼,對(duì)系統(tǒng)、網(wǎng)絡(luò)、V8的學(xué)習(xí)都有幫助,另外里面用到了很多開源庫(kù),也可以學(xué)到如何使用一些優(yōu)秀的開源庫(kù),甚至閱讀庫(kù)的源碼。
源碼解析地址:
https://github.com/theanarkh/read-just-0.1.4-code