deno-原理篇一启动加载
之前篇章
deno-基础篇,主要是deno的一些基本概念介绍。
deno执行过程概述
deno初始化时会读取内嵌的Typescript代码和加载v8 isolate实例,将需要执行的文件路径作为参数传入,在内部解析传入的Typescript/Javascript文件地址,加载需要执行的代码,如果是Typescript代码,通过初始化加载的Typescript编译器将代码编译成Javascript,然后将Javacript传给v8 isolate实例,并获取可执行句柄对象,调用执行方法执行具体的代码。
deno初始化过程
初始化流程主要分为几步:
- 解析外部输入
- 加载全局状态信息,
- 创建权限实例对象
- 读取Typescript编译器及运行时API代码
- 初始化v8 isolate
- 启动运行时
deno启动入口是rust,启动代码包含在main.rs的rust文件中,执行deno时会去执行main.rs文件的main方法。下面是main方法中的部分代码:
fn main() { // ... let args = env::args().collect(); let (mut flags, mut rest_argv, usage_string) = flags::set_flags(args) .unwrap_or_else(|err| { eprintln!("{}", err); std::process::exit(1) }); // .... let should_prefetch = flags.prefetch || flags.info; let should_display_info = flags.info; let state = Arc::new(isolate::IsolateState::new(flags, rest_argv, None)); let isolate_init = isolate_init::deno_isolate_init(); let permissions = permissions::DenoPermissions::from_flags(&state.flags); let mut isolate = isolate::Isolate::new(isolate_init, state, ops::dispatch, permissions); tokio_util::init(|| { // Setup runtime. isolate .execute("denoMain();") .map_err(errors::RustOrJsError::from) .unwrap_or_else(print_err_and_exit); // Execute main module. if let Some(main_module) = isolate.state.main_module() { debug!("main_module {}", main_module); isolate .execute_mod(&main_module, should_prefetch) .unwrap_or_else(print_err_and_exit); if should_display_info { // Display file info and exit. Do not run file modules::print_file_info( &isolate.modules.borrow(), &isolate.state.dir, main_module, ); std::process::exit(0); } } isolate .event_loop() .map_err(errors::RustOrJsError::from) .unwrap_or_else(print_err_and_exit); }); }解析外部输入
首先通过rust的env模块去搜集在命令行运行deno命令时传入的参数,然后根据传入参数变量作为条件去完成初始化和执行过程的任务。
let args = env::args().collect();加载全局状态消息
let state = Arc::new(isolate::IsolateState::new(flags, rest_argv, None));
Arc是rust的一个crate,它可以创建一个线程安全的,会记录引用数的指针(A thread-safe reference-counting pointer)。上面代码将获取的外部输入作为参数,构造了一个IsolateState对象。该对象的数据结构如下:
pub struct IsolateState { pub dir: deno_dir::DenoDir, pub argv: Vec<String>, pub flags: flags::DenoFlags, pub metrics: Metrics, pub worker_channels: Option<Mutex<WorkerChannels>>, }
这里对IsolateState除了包含了外部传入的基本数据信息,还包含了处理数据的基本方法,这里对它不做详细的解析。获取到state变量后,它由于被Arc包裹后,可以被多个线程安全的访问,实现线程之前的状态共享。这里对IsolateState不做过多的叙述。
创建权限实例let permissions = permissions::DenoPermissions::from_flags(&state.flags);
permission实例结构如下,它是一个struct类型:
pub struct DenoPermissions { // Keep in sync with src/permissions.ts pub allow_read: AtomicBool, pub allow_write: AtomicBool, pub allow_net: AtomicBool, pub allow_env: AtomicBool, pub allow_run: AtomicBool, }
通过from_flags方法,对上面结构体中的变量赋值
pub fn from_flags(flags: &DenoFlags) -> Self { Self { allow_read: AtomicBool::new(flags.allow_read), allow_write: AtomicBool::new(flags.allow_write), allow_env: AtomicBool::new(flags.allow_env), allow_net: AtomicBool::new(flags.allow_net), allow_run: AtomicBool::new(flags.allow_run), } }
permission实例中还包含了一些权限检查的方法,主要针对是否允许读文件、写文件、设置环境变量、访问网络请求、代码执行等几个方面。这也是算是deno的一大特点吧,在安全方面可以做到很好的限制。
读取Typescript编译器及运行时API代码let isolate_init = isolate_init::deno_isolate_init();
deno_isolate_init方法初始化构造了v8 isolate初始化时需要加载的数据。主要是创建了一个IsolateInit实例,它包含的结构如下:
pub struct IsolateInitScript { pub source: String, pub filename: String, } pub struct IsolateInit { pub snapshot: Option<deno_buf>, pub init_script: Option<IsolateInitScript>, }
在deno_isolate_init方法中,主要做的事情是加载typescript编译器和User API的代码。
pub fn deno_isolate_init() -> IsolateInit { if cfg!(not(feature = "check-only")) { if cfg!(feature = "use-snapshot-init") { let data = include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_deno.bin")); unsafe { IsolateInit { snapshot: Some(deno_buf::from_raw_parts(data.as_ptr(), data.len())), init_script: None, } } } else { #[cfg(not(feature = "check-only"))] let source_bytes = include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js")); #[cfg(feature = "check-only")] let source_bytes = vec![]; IsolateInit { snapshot: None, init_script: Some(IsolateInitScript { filename: "gen/bundle/main.js".to_string(), source: std::str::from_utf8(source_bytes).unwrap().to_string(), }), } } } else { IsolateInit { snapshot: None, init_script: None, } } }
上面的方法中主要通过两种方式加载初始化代码,一种是加载二进制的形式的Typescript代码,二进制代码都包含在snapshot_deno.bin文件中,第二中是直接通过Javascript的文件方式初始化,代码主要包含在main.js文件中。
初始化v8 isolatelet mut isolate = isolate::Isolate::new(isolate_init, state, ops::dispatch, permissions);
上面这行代码将全局状态数据、初始化Typescript代码数据、权限数据传给Isolate的new方法,构造了Isolate实例,Isolate的new方法可执行代码如下:
pub fn new( init: IsolateInit, state: Arc<IsolateState>, dispatch: Dispatch, permissions: DenoPermissions, ) -> Self { DENO_INIT.call_once(|| { unsafe { libdeno::deno_init() }; }); let config = libdeno::deno_config { will_snapshot: 0, load_snapshot: match init.snapshot { Some(s) => s, None => libdeno::deno_buf::empty(), }, shared: libdeno::deno_buf::empty(), // TODO Use for message passing. recv_cb: pre_dispatch, }; let libdeno_isolate = unsafe { libdeno::deno_new(config) }; // This channel handles sending async messages back to the runtime. let (tx, rx) = mpsc::channel::<(usize, Buf)>(); let new_isolate = Self { libdeno_isolate, dispatch, rx, tx, ntasks: Cell::new(0), timeout_due: Cell::new(None), modules: RefCell::new(Modules::new()), state, permissions: Arc::new(permissions), }; // Run init script if present. match init.init_script { Some(init_script) => new_isolate .execute2(init_script.filename.as_str(), init_script.source.as_str()) .unwrap(), None => {} }; new_isolate }
上面代码中很重要的一个变量是libdeno,它的主要实现使用的c++,包含在deno的中间层,它主要作用是初始化v8引擎,以及实现Javascript和rust之间的消息传递。libdeno::deno_new方法的代码如下:
Deno* deno_new(deno_config config) { if (config.will_snapshot) { return deno_new_snapshotter(config); } deno::DenoIsolate* d = new deno::DenoIsolate(config); v8::Isolate::CreateParams params; params.array_buffer_allocator = d->array_buffer_allocator_; params.external_references = deno::external_references; if (config.load_snapshot.data_ptr) { params.snapshot_blob = &d->snapshot_; } v8::Isolate* isolate = v8::Isolate::New(params); d->AddIsolate(isolate); v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); { v8::HandleScope handle_scope(isolate); auto context = v8::Context::New(isolate, nullptr, v8::MaybeLocal<v8::ObjectTemplate>(), v8::MaybeLocal<v8::Value>(), v8::DeserializeInternalFieldsCallback( deno::DeserializeInternalFields, nullptr)); if (!config.load_snapshot.data_ptr) { // If no snapshot is provided, we initialize the context with empty // main source code and source maps. deno::InitializeContext(isolate, context); } d->context_.Reset(isolate, context); } return reinterpret_cast<Deno*>(d); }
代码可以看出,deno_config是在rust代码中调用libdeno:new时传入的配置参数,然后调用v8::Isolate::New方法初始化了一个v8 isolate实例。libdeno这里不详细说明了,后面一节讲Javascript和rust传递消息机制时在详细说明。
到这里,deno的初始化阶段基本完成了80%了。
启动运行时isolate .execute("denoMain();") .map_err(errors::RustOrJsError::from) .unwrap_or_else(print_err_and_exit);
由前面的步骤知道,isolate对象包裹了一个v8 isolate实例,并加载了运行时所需Ttypescript代码,isolate.execute("denoMain()")实际上是去调用v8 isolate实例的方法执行初始化过程中加载的Typescript代码,当然不是直接执行Typescript,而是由Typescript编译后的Javascript代码或者是二进制字节码。doMain方法包含的代码如下:
export default function denoMain() { const startResMsg = os.start(); // TODO(kitsonk) remove when import "deno" no longer supported libdeno.builtinModules["deno"] = deno; Object.freeze(libdeno.builtinModules); setVersions(startResMsg.denoVersion()!, startResMsg.v8Version()!); // handle `--version` if (startResMsg.versionFlag()) { console.log("deno:", deno.version.deno); console.log("v8:", deno.version.v8); console.log("typescript:", deno.version.typescript); os.exit(0); } // handle `--types` // TODO(kitsonk) move to Rust fetching from compiler if (startResMsg.typesFlag()) { console.log(libDts); os.exit(0); } const mainModule = startResMsg.mainModule(); if (mainModule) { assert(mainModule.length > 0); setLocation(mainModule); } const cwd = startResMsg.cwd(); log("cwd", cwd); for (let i = 1; i < startResMsg.argvLength(); i++) { args.push(startResMsg.argv(i)); } log("args", args); Object.freeze(args); if (!mainModule) { replLoop(); } }
上面的代码中关键的两行代码如下
const startResMsg = os.start(); libdeno.builtinModules["deno"] = deno;
第一行代码表示向deno的后端发送启动消息,并获取基本状态信息和版本等数据,返回的数据都包含在startResMSG中。返回的消息数据格式定义如下:
table StartRes { cwd: string; pid: uint32; argv: [string]; exec_path: string; main_module: string; // Absolute URL. debug_flag: bool; deps_flag: bool; types_flag: bool; version_flag: bool; deno_version: string; v8_version: string; no_color: bool; }
StartRes表示从rust返回的消息格式,主要包含v8的版本信息,当前pid,以及主模块等。
第二行代码表示将deno对象赋值给libdeno.buildModules["deno"]变量,deno对象主要包含所有User API。
执行Typescript/Javascript代码
deno启动工作完成后,就时执行Javascript/Typescript的代码,此处主要分析deno直接执行文件代码的形式,交互式的方式在后边章节会分析。在命令行执行deno ./**.ts的时候,在解析外部输入的环节可以获取需要执行的文件路径,将路径基于当前环境上下文做一些处理,构造成完整的文件地址。接着调用isolate对象的execute_mod方法去执行具体的代码。代码细节如下:
if let Some(main_module) = isolate.state.main_module() { debug!("main_module {}", main_module); isolate .execute_mod(&main_module, should_prefetch) .unwrap_or_else(print_err_and_exit); if should_display_info { // Display file info and exit. Do not run file modules::print_file_info( &isolate.modules.borrow(), &isolate.state.dir, main_module, ); std::process::exit(0); } }
主线程加载完执行的代码后启动EventLoop,去执行异步操作
isolate .event_loop() .map_err(errors::RustOrJsError::from) .unwrap_or_else(print_err_and_exit);
文末
~~下一节聊聊deno内部的Event Loop以及和Typescript和rust之间交互的实现~~