ChakraCore学习笔记(二):从Hello World看ChakraCore顶层数据结构
在上一篇里我们已经大概了解了如何编译和使用ChakraCore,现在我们来仔细看一看ChakraCore的Hello World,以此出发来了解一下ChakraCore的主体结构大概长的什么样子吧。
1. 代码结构
在看代码之前,先让我们来大概看一眼ChakraCore的代码结构:
1 | . |
2. Runtime和执行上下文(Execution context)
从Hello World中我们可以看到,如果我们需要ChakraCore来运行一段JS脚本,我们至少需要创建两样东西:JsRuntimeHandle(Runtime)和JsContextRef(执行上下文,execution context),这也是ChakraCore一开始最重要的两个概念。
2.1. Runtime:JsrtRuntime
Runtime这个词一听就知道非常重要,在这里也一样,它表示一个完整的用来支持JavaScript运行的环境,它具有自己独立的堆,编译器,JIT的线程和垃圾回收的线程。
在ChakraCore里,Runtime和线程是紧密相关的,但是他们并不是一对一的,一个线程上可以存在多个Runtime,但在任意时刻都只允许存在一个活动的Runtime,而一个Runtime在任意时刻也只能运行在一个线程上,不过如果一个Runtime没有被任何地方使用,比如正在执行一段JS,那么你可以安全地在另一个没有被占用的线程上调用它。
在JSRT API里,JsRuntimeHandle用于表示一个Runtime,如果我们进入JsCreateRuntime这个API,我们就会发现它其实就是其真正Runtime的实现——JsrtRuntime的内存地址。
2.2. 执行上下文:JsrtContext
执行上下文和Runtime不同,它用来隔离JavaScript脚本的执行环境,换句话说,一个Runtime里面可以有多个执行上下文,每个执行上下文里面都有自己的GlobalObject,执行上下文互相之间默认看不到对方定义的变量。就比如浏览器中,一个页面可能会有很多个iframe一样。
创建执行上下文的时候我们需要指定一个Runtime,新的执行上下文会被绑定到这个Runtime中,并且不能更改。另外多个执行上下文之间其实是共享了一个堆的,因为ChakraCore的堆是跟着Runtime走的,而且这样执行上下文之间才能互相访问对方的变量,而不用担心多线程的问题。
在JSRT API里,JsContextRef用于表示一个执行上下文,同样,如果我们进入JsCreateContext这个API,我们会发现它其实也是其真正的实现JsrtContext的内存地址。
2.3. 原始关系图
现在来看,Runtime和执行上下文的关系主要如下:
[//]: <> (class JsrtRuntime)
[//]: <> (class JsrtContext)
[//]: <> (class Recycler)
[//]: <> (class GlobalObject)
[//]: <> ()
[//]: <> (JsrtRuntime “1”–“n” JsrtContext)
[//]: <> (JsrtRuntime “1”–“1” Recycler)
[//]: <> (JsrtContext “1”*–“1” GlobalObject)
3. ThreadContext和ScriptContext
在了解了JSRT API里看到的顶层结构之后,让我们来看看他们的具体实现吧。
3.1. JsrtRuntime和ThreadContext
我们先来看看JsrtRuntime。在Debugger下我们可以看见,这个类里面直接包含的信息其实非常的少,而且也没有看到我们期望的Recycler,和我们刚刚在介绍代码结构里提到的一样,它更像是一个最外层的封装,但是里面的另外一个类却很有意思——ThreadContext。
3.1.1. ThreadContext
在CreateRuntimeCore里,我们可以看到,在创建JsrtRuntime之前我们需要创建一个ThreadContext,而主要的初始化都是在ThreadContext上进行的。在ThreadContext里面,我们还可以看到比JsrtRuntime多得多的成员变量,并且有很多我们都非常感兴趣,比如:
- 和内存管理相关的Recycler,各种Page Allocator
- 和控制流相关的异常信息
- 各种统计信息
- 我们马上会提到的JsrtContext的主要实现——ScriptContext的列表
- …等等
可以看出,上面我们提到的Runtime提供的主要功能基本都在ThreadContext里面,可以说它是JsrtRuntime的主要实现。而通过代码我们可以看得到,JsrtRuntime和ThreadContext是一对一的,所以在读ChakraCore的代码时,我们基本可以把他们认为是一个东西。
3.1.2. ThreadContextScope
在CreateRuntimeCore的最后,我们可以看到一些只有在开启调试之类状态下才会有的初始化,而在初始化之前ChakraCore会创建一个叫做ThreadContextScope的类,这个类是干什么的呢?
还记得前面提到一个线程上虽然可以有多个Runtime,但是只能有一个活动的Runtime么,这个类就是用来将Runtime注册为活动并放入TLS中的。而在此之后就可以使用ThreadContext::GetContextForCurrentThread来获取当前活动的Runtime了,不过这个函数调用的地方非常的少,基本一些异常处理和Sanity Check在用,主要还是需要传入或者通过ScriptContext去拿。
[//]: <> (class ThreadContextTLSEntry)
[//]: <> (class ThreadContext)
[//]: <> (ThreadContextTLSEntry -down-> ThreadContext : Only when thread context scope is created.)
3.1.3. 其他的类
除了ThreadContext以外,JsrtRuntime里还有一些别的类:
- 和ChakraCore内存管理相关的:比如AllocationPolicyManager和JsrtThreadService,我们在后面写内存管理的时候再来看这几个类,
- JSRT API Callback:比如beforeCollectCallback等等。
3.2. JsrtContext,JavascriptLibrary和ScriptContext
JsrtContext和JsrtRuntime类似,它也只是一个Jsrt API里最外层的一个封装,其主要的实现实在两个类之中:JavascriptLibrary和ScriptContext。
3.2.1. ScriptContext
虽然在上图中ScriptContext并没有直接被JsrtContext所持有,而是放在了JavascriptLibrary之中,但是我们还是先来看看这个类,因为这个类其实更加重要也更加的靠上层。
在JsrtContext的构造函数里面,我们可以看到第一步就是创建ScriptContext,而在销毁JsrtContext时,其主要做的事情也是由ScriptContext来完成的,可见ScriptContext其实就是JsrtContext的真实实现。(其实看名字我们也看的出来……)
还记得JsrtContext提供的功能么?在ScriptContext中,我们都可以在其成员变量中找到踪迹:
- globalObject:这个就是浏览器里JavaScript中的window变量。
- url:当前ScriptContext的创建者的URL。
- sourceList:用于储存每个ScriptContext中加载的代码。
3.2.2. JavascriptLibrary
现在让我们看回JavascriptLibrary,这个类的作用非常的大,它保存着所有类型的Prototype对象和类型对象,并且是所有Javascript对象的工厂,虽然名字有点让人无力吐槽,因为每次一说JavascriptLibrary,别人总以为是啥开源库……
在ScriptContext初始化时,它会创建并初始化GlobalObject,而GlobalObject初始化的时候,它会创建并初始化JavascriptLibrary,在它初始化的函数里面我们可以看到JavascriptLibrary会做如下几样事情:
1 | void JavascriptLibrary::Initialize(ScriptContext* scriptContext, GlobalObject * globalObject) |
为了了解JavascriptLibrary的生命周期,最后再来看看它什么时候被销毁,这个函数是在ScriptContext被关闭的时候被调用的,所以它的生命周期基本就是跟着ScriptContext走的。
3.3. 顶层数据结构主要关系图
好了,现在我们已经基本对ChakraCore的顶层数据结构有所了解了,让我们来再次梳理一下他们之前的关系吧:
[//]: <> (class JsrtRuntime {)
[//]: <> ( JsrtContext* contextList)
[//]: <> (})
[//]: <> ()
[//]: <> (class JsrtContext {)
[//]: <> ( Js::JavascriptLibrary* javascriptLibrary)
[//]: <> (})
[//]: <> ()
[//]: <> (class ThreadContextGlobalList)
[//]: <> ()
[//]: <> (class ThreadContext {)
[//]: <> ( Js::ScriptContext* scriptContextList)
[//]: <> ( Recycler* recycler)
[//]: <> (})
[//]: <> ()
[//]: <> (class ThreadContextTLSEntry)
[//]: <> ()
[//]: <> (class Js::ScriptContext {)
[//]: <> ( Js::JavascriptLibrary* javascriptLibrary)
[//]: <> ( Js::GlobalObject* globalObject)
[//]: <> (})
[//]: <> ()
[//]: <> (class Recycler)
[//]: <> (class Js::JavascriptLibrary {)
[//]: <> ( Js::ScriptContext* scriptContext)
[//]: <> (})
[//]: <> ()
[//]: <> (class Js::GlobalObject {)
[//]: <> ( Js::JavascriptLibrary* library)
[//]: <> (})
[//]: <> ()
[//]: <> (JsrtRuntime “1”-down-“n” JsrtContext)
[//]: <> (JsrtContext “1”-right-“1” Js::ScriptContext)
[//]: <> (Js::ScriptContext -down-> Js::JavascriptLibrary)
[//]: <> (Js::ScriptContext “1”-down-“1” Js::GlobalObject)
[//]: <> (Js::GlobalObject “1”-down-“1” Js::JavascriptLibrary)
[//]: <> (Js::JavascriptLibrary -up-> Js::ScriptContext)
[//]: <> (JsrtContext -down-> Js::JavascriptLibrary)
[//]: <> ()
[//]: <> (JsrtRuntime “1”-right-“1” ThreadContext)
[//]: <> (ThreadContextGlobalList “1”-down->“n” ThreadContext)
[//]: <> (ThreadContextTLSEntry -down-> ThreadContext : Only when thread context scope is created.)
[//]: <> (ThreadContext “1”-down->“n” Js::ScriptContext)
[//]: <> (ThreadContext “1”*-down-“1” Recycler)
[//]: <> (Js::ScriptContext *-[hidde#right-> Recycler)
4. 结语
希望这篇文章能帮助大家了解ChakraCore顶层数据结构,从而让使用JSRT API或者阅读ChakraCore的代码都变得更加容易。当然,我也是现学现写点笔记,所以难免有出错的地方,还请大家能多多指教,谢谢。
本文链接地址:ChakraCore学习笔记(二):从Hello World看ChakraCore顶层数据结构