深入瀏覽器原理系列(4):多進程架構(gòu)
問題
構(gòu)建一個從不崩潰或掛起的渲染引擎幾乎是不可能的。構(gòu)建一個完全安全的渲染引擎幾乎是不可能的。
在某些方面,2006年左右的web瀏覽器狀態(tài)就像過去的單用戶、協(xié)同多任務操作系統(tǒng)。正如操作系統(tǒng)中行為不當?shù)膽贸绦蚩赡軐е抡麄€系統(tǒng)癱瘓一樣,web瀏覽器中的行為不當?shù)膚eb頁面也可能導致整個系統(tǒng)癱瘓。只需一個瀏覽器或插件漏洞,整個瀏覽器和當前運行的所有選項卡就會關閉。
現(xiàn)代操作系統(tǒng)更加健壯,因為它們將應用程序放置在彼此隔離的獨立進程中。一個應用程序的崩潰通常不會損害其他應用程序或操作系統(tǒng)的完整性,而且每個用戶對其他用戶數(shù)據(jù)的訪問受到限制。
架構(gòu)一覽
我們?yōu)闉g覽器標簽頁使用單獨的進程,以保護整個應用程序不受渲染引擎的bug和小故障的影響。我們還限制了每個渲染引擎進程對其他進程和系統(tǒng)其余部分的訪問。在某些方面,這給web瀏覽帶來了內(nèi)存保護和訪問控制給操作系統(tǒng)帶來的好處。

我們將運行UI并管理標簽和插件進程的主進程稱為“瀏覽器進程”或“瀏覽器”。類似地,標簽頁相關的進程稱為“渲染進程”或“渲染器”。渲染器使用Blink開源布局引擎來解釋和布局HTML頁面。在這里插入圖片描述
管理渲染進程
每個渲染進程都有一個全局的RenderProcess對象,它管理與父親進程-瀏覽器進程的通信并維護全局狀態(tài)。
瀏覽器為每個渲染進程維護一個相應的RenderProcessHost,它管理瀏覽器的狀態(tài)和同渲染器之間的通信。
瀏覽器和渲染器使用Chromium的IPC系統(tǒng)進行通信
管理視圖Views
每個渲染進程都有一個或多個由 RenderProcess 管理的 RenderView 對象,這些對象與content的標簽頁相對應。
相應的RenderProcessHost維護一個與渲染器中每個視圖相對應的RenderViewHost。
每個視圖都有一個視圖ID,該ID用于區(qū)分同一渲染器中的多個視圖。這些ID在同一個渲染器進程中是唯一的,但在瀏覽器進程中不是唯一的,因此標識一個視圖需要一個RenderProcessHost和一個視圖ID。
通過這些RenderViewHost對象可以完成從瀏覽器進程到特定content的標簽頁的通信,這些對象知道如何通過其RenderProcessHost將消息發(fā)送到RenderProcess以及繼續(xù)發(fā)送到RenderView
組件和接口
渲染器進程中:
RenderProcess使用瀏覽器進程中相應的RenderProcessHost處理IPC消息。每個渲染器進程只有一個RenderProcess對象。這就是所有瀏覽器進程<->渲染器進程之間通信的方式。
RenderView對象與瀏覽器進程中相應的RenderViewHost通信(通過RenderProcess),以及我們的WebKit嵌入層。此對象表示一個標簽頁或彈出窗口中的web頁面的內(nèi)容
瀏覽器進程中:
Browser對象表示一個頂級瀏覽器窗口。
RenderProcessHost對象表示單個 瀏覽器進程<–>渲染器進程 IPC通信連接中的瀏覽器進程端。每個渲染器進程在瀏覽器進程中都有一個RenderProcessHost
RenderViewHost對象封裝了與遠程RenderView的通信,RenderWidgetHost處理瀏覽器進程中RenderWidget的輸入和繪制
有關此嵌入的工作方式的更多詳細信息,請參閱Chromium如何顯示網(wǎng)頁設計文檔
共享渲染進程
通常,每個新窗口或標簽頁都會在新進程中打開。瀏覽器將產(chǎn)生一個新進程,并指示它創(chuàng)建一個RenderView。
有時有必要或有需要的在標簽頁或窗口之間共享渲染進程。Web應用程序會打開一個新窗口,希望與之進行同步通信,例如,使用JavaScript中的window.open。在這種情況下,當我們創(chuàng)建一個新的窗口或標簽頁時,我們需要重新使用打開窗口的進程。如果進程總數(shù)太大,或者用戶已經(jīng)打開一個導航到該域domain的進程,我們也有策略將新的標簽頁分配到現(xiàn)有進程中去。這些策略在進程模型中進行了描述
崩潰或錯誤行為的渲染器檢測
一個瀏覽器進程的每個IPC連接都會監(jiān)視該進程的句柄。如果這些句柄被通知,渲染進程已經(jīng)崩潰,標簽頁被通知崩潰。現(xiàn)在,我們會顯示一個“sad標簽”屏幕,通知用戶渲染器崩潰了。可以通過按reload按鈕或啟動一個新的導航來重新加載頁面。當這種情況發(fā)生時,我們注意到?jīng)]有進程,并創(chuàng)建一個新的進程
沙盒渲染器
鑒于渲染器在單獨的進程中運行,我們可以通過沙盒限制其對系統(tǒng)資源的訪問。例如,我們可以確保渲染器對網(wǎng)絡的唯一訪問是通過其父進程-瀏覽器進程進行的。同樣,我們可以使用主機操作系統(tǒng)的內(nèi)置權(quán)限來限制渲染器進程對文件系統(tǒng)的訪問。
除了限制渲染器對文件系統(tǒng)和網(wǎng)絡的訪問之外,我們還可以限制對渲染器對用戶的顯示和相關對象的訪問。我們在用戶看不見的單獨Windows“桌面”上運行每個渲染過程。這樣可以防止受損的渲染器打開新窗口或捕獲擊鍵。
回饋記憶
假如渲染器在單獨的進程中運行,那么將隱藏的標簽頁視為較低優(yōu)先級變得很簡單。
通常,Windows上最小化的進程會將其內(nèi)存自動放入“可用內(nèi)存”池中。在低內(nèi)存情況下,Windows將在交換出更高優(yōu)先級的內(nèi)存之前將該最小化進程的內(nèi)存交換到磁盤,這有助于使用戶可見的程序保持更高的響應速度。
我們可以將相同的原理應用于隱藏的標簽頁。當渲染進程沒有頂級標簽頁時,我們可以釋放該進程的“工作集”大小,以提示系統(tǒng)在必要時先將該內(nèi)存換出到磁盤。
因為我們發(fā)現(xiàn)減小工作集大小,當用戶在兩個標簽頁之間切換時也會降低標簽頁切換性能,所以我們逐漸釋放此內(nèi)存。這意味著,如果用戶切換回最近使用的標簽頁,則與最近很少使用的標簽頁相比,該標簽頁的內(nèi)存更有可能被分頁。
具有足夠內(nèi)存來運行所有程序的用戶根本不會注意到此過程:Windows僅會在需要時才真正回收這些數(shù)據(jù),因此,如果有足夠的內(nèi)存,則不會影響性能。
這有助于我們在低內(nèi)存情況下獲得更好的內(nèi)存占用。與很少使用的背景標簽相關聯(lián)的內(nèi)存可以完全換出,而前景標簽的數(shù)據(jù)可以完全加載到內(nèi)存中。相比之下,單進程瀏覽器會將所有標簽頁的數(shù)據(jù)隨機分布在其內(nèi)存中,并且不可能如此干凈地分離使用和未使用的數(shù)據(jù),從而浪費內(nèi)存和性能。
插件和擴展
Firefox樣式的NPAPI插件在其自身的進程中運行,與渲染器分開。插件架構(gòu)中對此進行了詳細描述。
“網(wǎng)站隔離”項目旨在提供渲染器之間的更多隔離,該項目的早期交付成果包括在隔離的進程中運行Chrome的HTML /JavaScript內(nèi)容擴展。
