【編者按】Servo是一個實(shí)驗(yàn)性質(zhì)的網(wǎng)頁瀏覽器排版引擎(類似于Gecko),由Mozilla聯(lián)合三星進(jìn)行開發(fā),采用Mozilla新推出的Rust語言編寫。Servo的愿景是盡可能地發(fā)揮多核處理器的“多核”功能,而不是讓它成為硬件商的噱頭,與三星強(qiáng)強(qiáng)聯(lián)合是希望Servo和Rust最終在Android平臺和ARM設(shè)備上開花結(jié)果。
背景介紹
一個Web瀏覽器的宗旨是幫助用戶和程序間建立起友好的溝通橋梁。用戶希望瀏覽器是反應(yīng)迅速的,因而瀏覽器的布局和渲染算法通常是采用底層本機(jī)代碼(Native Code)來實(shí)現(xiàn)的。同時,通過DOM,我們可以使用JavaScript來重構(gòu)整個HTML文檔。因此,使用瀏覽器來呈現(xiàn)頁面時,實(shí)質(zhì)上是使用了一個多語言的數(shù)據(jù)結(jié)構(gòu),使得低級本機(jī)代碼和高級語言如JavaScript之間可以無縫交流。在Servo項(xiàng)目中,我們會讓這個溝通的藝術(shù)變得更美好,我們有一個新的DOM內(nèi)存管理方法還使用了一些Rust的功能,如:自動生成域遍歷(Auto-generating field traversals),生命周期檢驗(yàn)(Lifetime checking),自定義靜態(tài)分析插件(custom static analysis plugins)。
DOM內(nèi)存管理
一個還將使用的DOM一般不會被馬上銷毀,但這通常會引起use-after-free 漏洞。要解決這個問題,很多瀏覽器都使用引用計(jì)數(shù)(reference counting即內(nèi)存回收)來追蹤不同底層DOM對象的指針。當(dāng)用JS獲取一個DOM對象時(如getElementById),瀏覽器會在JVM中生成一個反射對象來指向一個底層對象。如果JS的垃圾回收器判斷出一個反射對象不再使用,它會銷毀這個對象并相應(yīng)調(diào)減引用計(jì)數(shù),當(dāng)計(jì)數(shù)為0時,垃圾收集器會釋放引用次數(shù)為0的值所占內(nèi)存。
這個辦法的確能解決use-after-free問題,但如果瀏覽器內(nèi)存占用可以更少,用戶會更滿意的。因此,無用對象的銷毀是越快越好。然而,跨語言的反射對象體系會是一個障礙,出現(xiàn)循環(huán)引用問題。
例如下面代碼的一個C++ Element對象,它有一個Event的引用計(jì)數(shù)指針:
假設(shè)我們在JS中為element對象添加一個事件:
代碼執(zhí)行時,event的屬性會回指Element,這就是一個跨語言循環(huán)引用例子。C++的回收器不會銷毀這個循環(huán),JS的垃圾收集器追蹤不到C++指針,因此引用計(jì)數(shù)無法減為0,這些對象將永遠(yuǎn)不會被銷毀。盡管存在一些解決方法,但是可能會導(dǎo)致其它諸如內(nèi)存泄漏,NULL指針的問題。
有鑒于此,我們在Servo嘗試了新的思路―不再對DOM對象進(jìn)行引用計(jì)數(shù)。取而代之的,我們賦予了JS垃圾回收器全權(quán)來管理這些底層DOM對象。要實(shí)現(xiàn)它,Servo的Rust代碼與SpiderMonkey的垃圾回收器之間需要做一個相當(dāng)繁復(fù)的互動,還好Rust一些炫酷的特性會帶來極大的幫助。
自動生成字段遍歷(Auto-generating field traversals)
Rust中有一個traits(特性)的概念,類似于Haskell語言中的類型類或面向?qū)ο笳Z言中的接口。
例如Collection trait類型:
這個集合類型描述的是任何元素的類型集合,這里的len方法是用于獲取集合的長度。
還有一個是Endodable trait類型,用于序列化。
任何可序列化的類型都有一個泛型的encode方法。Encodable trait是一個特殊的類型,因?yàn)榫幾g器能自行來實(shí)現(xiàn)它。
我們來看看DOM的Document文檔接口在Servo中的實(shí)現(xiàn):
deriving屬性會讓編譯器遞歸地對node,window等字段進(jìn)行encode處理。如果往Document中添加了一個非Endoable的字段,編譯器會報(bào)錯,這就確保了在編譯時進(jìn)行全字段跟蹤。
生存期檢查(Lifetime checking)
在Servo中,需要用Rust來傳遞一個DOM對象指針作為函數(shù)參數(shù),在本地變量中存儲DOM對象指針等等。這些額外的臨時引用,需要在垃圾回收器中進(jìn)行可到達(dá)分析時,以roots的形式來登記。否則,會引致 use-after-free 問題。為了解決該問題,Rust中引入了編譯時生存期檢查器,它會使編譯器識別并拒絕use-after-free或其它的危險錯誤。
例如:
這個語法的意思是:
1. <’a>: “任何值為’a生存期”
2. (&'a self):“一個在’a有效生存期內(nèi)的Root引用”
3. ->JSRef<'a, T>:“返回一個生存期參數(shù)為’a的JSRef”
如果我們嘗試執(zhí)行下面的代碼:
我們會收到這樣的錯誤提示:
有關(guān)該語法中有關(guān)概念的詳細(xì)解釋,請點(diǎn)擊這里進(jìn)行了解。
自定義靜態(tài)分析 (custom static analysis)
Rust的編譯器能夠載入“lint”插件進(jìn)行自定義靜態(tài)分析,從字面來看,”lint”的宗旨是采取低開銷戰(zhàn)略來捕捉常見錯誤。我們希望其與生存期檢查器一起為開發(fā)者帶來更安全可靠的瀏覽器引擎。
寫在最后
Rust語言(最新版本0.11.0)和Servo還處于實(shí)驗(yàn)階段,仍需各方協(xié)力來不斷完善,如果想進(jìn)一步了解Rust或Servo,請嘗試訪問下面的鏈接。
更多詳細(xì)內(nèi)容:mozilla.org