乐趣区

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 isolate
let 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 之间交互的实现~~

退出移动版