程序员人生 网站导航

Mozilla与三星之子――Servo特性解读

栏目:互联网时间:2014-09-06 23:31:21

【编者按】Servo是一个实验性质的网页浏览器排版引擎(类似于Gecko),由Mozilla联合三星进行开发,采用Mozilla新推出的Rust语言编写。Servo的愿景是尽可能地发挥多核处理器的“多核”功能,而不是让它成为硬件商的噱头,与三星强强联合是希望Servo和Rust最终在Android平台和ARM设备上开花结果。

背景介绍

一个Web浏览器的宗旨是帮助用户和程序间建立起友好的沟通桥梁。用户希望浏览器是反应迅速的,因而浏览器的布局和渲染算法通常是采用底层本机代码(Native Code)来实现的。同时,通过DOM,我们可以使用JavaScript来重构整个HTML文档。因此,使用浏览器来呈现页面时,实质上是使用了一个多语言的数据结构,使得低级本机代码和高级语言如JavaScript之间可以无缝交流。在Servo项目中,我们会让这个沟通的艺术变得更美好,我们有一个新的DOM内存管理方法还使用了一些Rust的功能,如:自动生成域遍历(Auto-generating field traversals),生命周期检验(Lifetime checking),自定义静态分析插件(custom static analysis plugins)。

DOM内存管理

一个还将使用的DOM一般不会被马上销毁,但这通常会引起use-after-free 漏洞。要解决这个问题,很多浏览器都使用引用计数(reference counting即内存回收)来追踪不同底层DOM对象的指针。当用JS获取一个DOM对象时(如getElementById),浏览器会在JVM中生成一个反射对象来指向一个底层对象。如果JS的垃圾回收器判断出一个反射对象不再使用,它会销毁这个对象并相应调减引用计数,当计数为0时,垃圾收集器会释放引用次数为0的值所占内存。

这个办法的确能解决use-after-free问题,但如果浏览器内存占用可以更少,用户会更满意的。因此,无用对象的销毁是越快越好。然而,跨语言的反射对象体系会是一个障碍,出现循环引用问题。

例如下面代码的一个C++ Element对象,它有一个Event的引用计数指针:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_0.js">struct Element { RefPtr<Event> mEvent; };

假设我们在JS中为element对象添加一个事件:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_1.js">elem.addEventListener('load', function (event) { event.originalTarget = elem; });

代码执行时,event的属性会回指Element,这就是一个跨语言循环引用例子。C++的回收器不会销毁这个循环,JS的垃圾收集器追踪不到C++指针,因此引用计数无法减为0,这些对象将永远不会被销毁。尽管存在一些解决方法,但是可能会导致其它诸如内存泄漏,NULL指针的问题。

有鉴于此,我们在Servo尝试了新的思路―不再对DOM对象进行引用计数。取而代之的,我们赋予了JS垃圾回收器全权来管理这些底层DOM对象。要实现它,Servo的Rust代码与SpiderMonkey的垃圾回收器之间需要做一个相当繁复的互动,还好Rust一些炫酷的特性会带来极大的帮助。

自动生成字段遍历(Auto-generating field traversals)

Rust中有一个traits(特性)的概念,类似于Haskell语言中的类型类或面向对象语言中的接口。

例如Collection trait类型:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_2.js">pub trait Collection { fn len(&self) -> uint; }

这个集合类型描述的是任何元素的类型集合,这里的len方法是用于获取集合的长度。

还有一个是Endodable trait类型,用于序列化。

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_3.js">pub trait Encodable { fn encode<T: Encoder>(&self, encoder: &mut T); }

任何可序列化的类型都有一个泛型的encode方法。Encodable trait是一个特殊的类型,因为编译器能自行来实现它。

我们来看看DOM的Document文档接口在Servo中的实现:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_4.js">#[deriving(Encodable)] pub struct Document { pub node: Node, pub window: JS<Window>, pub is_html_document: bool, ... }

deriving属性会让编译器递归地对node,window等字段进行encode处理。如果往Document中添加了一个非Endoable的字段,编译器会报错,这就确保了在编译时进行全字段跟踪。

生存期检查(Lifetime checking)

Servo中,需要用Rust来传递一个DOM对象指针作为函数参数,在本地变量中存储DOM对象指针等等。这些额外的临时引用,需要在垃圾回收器中进行可到达分析时,以roots的形式来登记。否则,会引致 use-after-free 问题。为了解决该问题,Rust中引入了编译时生存期检查器,它会使编译器识别并拒绝use-after-free或其它的危险错误。

例如:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_5.js">pub fn root_ref<'a>(&'a self) -> JSRef<'a, T> {

这个语法的意思是:
1. <’a>: “任何值为’a生存期”

2. (&'a self):“一个在’a有效生存期内的Root引用”

3. ->JSRef<'a, T>:“返回一个生存期参数为’a的JSRef”

如果我们尝试执行下面的代码:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_6.js">fn bogus_get_window<'a>(&self) -> JSRef<'a, Window> { let window = self.window.root(); window.root_ref() // return the JSRef }

我们会收到这样的错误提示:

code_snippet_id="460315" snippet_file_name="ptcms_1409277338_7.js">document.rs:199:9: 199:15 error: `window` does not live long enough document.rs:199 window.root_ref() ^~~~~~ document.rs:197:57: 200:6 note: reference must be valid for the lifetime 'a as defined on the block at 197:56... document.rs:197 fn bogus_get_window<'a>(&self) -> JSRef<'a, Window> { document.rs:198 let window = self.window.root(); document.rs:199 window.root_ref() document.rs:200 } document.rs:197:57: 200:6 note: ...but borrowed value is only valid for the block at 197:56 document.rs:197 fn bogus_get_window<'a>(&self) -> JSRef<'a, Window> { document.rs:198 let window = self.window.root(); document.rs:199 window.root_ref() document.rs:200 }

有关该语法中有关概念的详细解释,请点击这里进行了解。

自定义静态分析 (custom static analysis)

Rust的编译器能够载入“lint”插件进行自定义静态分析,从字面来看,”lint”的宗旨是采取低开销战略来捕捉常见错误。我们希望其与生存期检查器一起为开发者带来更安全可靠的浏览器引擎。

写在最后

Rust语言(最新版本0.11.0)和Servo还处于实验阶段,仍需各方协力来不断完善,如果想进一步了解Rust或Servo,请尝试访问下面的链接。

  • Rust语言指导;
  • Rust Reddit讨论区
  • 向Servo提出建议;
  • Servo代码范例。

更多详细内容:mozilla.org

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐