轉(zhuǎn)載請注明出處:
https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/Start_Here_Background_Reading/Multi-process_Architecture.html
全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續(xù)更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh
這個(gè)文檔描述了Chromium的高層架構(gòu)
問題
構(gòu)建一個(gè)從不會(huì)掛起或崩潰的渲染引擎幾乎是不可能的。構(gòu)建一個(gè)完全安全的渲染引擎也是幾乎不可能的。
在某種程度上,web瀏覽器當(dāng)前狀態(tài)就像一個(gè)與過去的多任務(wù)操作系統(tǒng)合作的單獨(dú)的用戶。正如在一個(gè)這樣的操作系統(tǒng)中的錯(cuò)誤程序會(huì)讓整個(gè)系統(tǒng)掛掉,所以一個(gè)錯(cuò)誤的web頁面也可以讓一個(gè)現(xiàn)代瀏覽器掛掉。僅僅需要一個(gè)瀏覽器或插件的bug,就餓能讓整個(gè)瀏覽器和所有正在運(yùn)行的標(biāo)簽頁停止運(yùn)行。
現(xiàn)代操作系統(tǒng)更加魯棒,因?yàn)樗麄儼褢?yīng)用程序分成了彼此隔離的獨(dú)立線程。一個(gè)程序中的crash通常不會(huì)影響其他程序或整個(gè)操作系統(tǒng),每個(gè)用戶對用戶數(shù)據(jù)的訪問也是有限制的。
架構(gòu)概覽
我們?yōu)闉g覽器的標(biāo)簽頁使用獨(dú)立的進(jìn)程,以此保護(hù)整個(gè)應(yīng)用程序免受渲染引擎中的bug和故障的傷害。我們也會(huì)限制每個(gè)渲染引擎進(jìn)程的相互訪問,以及他們與系統(tǒng)其他部分的訪問。某些程度上,這為web瀏覽提供了內(nèi)存保護(hù),為操作系統(tǒng)提供了訪問控制。
我們把運(yùn)行UI的進(jìn)程叫做主進(jìn)程(main),把插件進(jìn)程稱為“瀏覽器進(jìn)程”或“瀏覽器(Browser)”。相似的,標(biāo)簽頁相關(guān)的進(jìn)程被稱作“渲染線程”或“渲染器(renderer)”。渲染器使用WebKit開源引擎來實(shí)現(xiàn)中斷與html的布局。
管理渲染進(jìn)程
每個(gè)渲染進(jìn)程有一個(gè)全局的RenderProcess對象,管理它與父瀏覽器進(jìn)程之間的通信,維護(hù)全局的狀態(tài)。瀏覽器為每個(gè)渲染進(jìn)程維護(hù)一個(gè)對應(yīng)的RenderViewHost,用來管理瀏覽器狀態(tài),并與渲染器交流。瀏覽器與渲染器使用Chromium's IPC system進(jìn)行交流。
管理view
每個(gè)渲染進(jìn)程有一個(gè)以上的RenderView對象,由RenderProcess管理(它與標(biāo)簽頁的內(nèi)容相關(guān))。對應(yīng)的RenderProcessHost維護(hù)一個(gè)與渲染器中每個(gè)view相關(guān)的RenderViewHost。每個(gè)view被賦予一個(gè)view ID,以區(qū)分同一個(gè)渲染器中的不同view。這些ID在每個(gè)渲染器內(nèi)是唯一的,但在瀏覽器中不是,所以區(qū)分一個(gè)view需要一個(gè)RenderProcessHost和一個(gè)view ID。
瀏覽器與一個(gè)包含內(nèi)容的特定標(biāo)簽頁之間的交流是通過這些RenderViewHost對象來完成的,它們知道如何通過他們的RenderProcessHost向RenderProcess和RenderView送消息。
組件與接口
在渲染進(jìn)程中:
RenderProcess處理與瀏覽器中對應(yīng)的RenderProcessHost的通信。每個(gè)渲染進(jìn)程就有唯一的一個(gè)RenderProcess對象。這就是所有瀏覽器-渲染器之間的交互發(fā)生的方式。
RenderView對象與它在瀏覽器進(jìn)程中對應(yīng)的RenderViewHost和我們的webkit嵌入層通信(通過RenderProcess)。這個(gè)對象代表了一個(gè)網(wǎng)頁在標(biāo)簽頁或一個(gè)彈出窗口的內(nèi)容。
在瀏覽器進(jìn)程中:
- Browser對象代表了頂級(jí)瀏覽器窗口
- RenderProcessHost對象代表了瀏覽器端瀏覽器的與渲染器的IPC連接。在瀏覽器進(jìn)程里,每個(gè)渲染進(jìn)程有一個(gè)RenderProcessHost對象。
- RenderViewHost對象封裝了與遠(yuǎn)端瀏覽器的交流,RenderWidgetHost處理輸入并在瀏覽器中為RenderWidget進(jìn)行繪制。
想要得到更多關(guān)于這種嵌入是如何工作的詳細(xì)信息,可以查看How Chromium displays web pages design document。
共享繪制器進(jìn)程
通常,每個(gè)新的window或標(biāo)簽頁是在一個(gè)新進(jìn)程里打開的。瀏覽器會(huì)生成一個(gè)新的進(jìn)程,然后指導(dǎo)它去創(chuàng)建一個(gè)RenderView。
有時(shí)候,有這樣一種必要或欲望在標(biāo)簽頁或窗口間共享渲染進(jìn)程。一個(gè)web應(yīng)用程序會(huì)在期望同步交流時(shí),打開一個(gè)新的窗口,比如,在javascript里使用window.open。這種情況下,當(dāng)我們創(chuàng)建一個(gè)新的window或標(biāo)簽頁時(shí),我們需要重用打開這個(gè)window的進(jìn)程。我們也有一些策略來把新的標(biāo)簽頁分配的已有的進(jìn)程(如果總的進(jìn)程數(shù)太大的話,或者如果用戶已經(jīng)為這個(gè)域名打開了一個(gè)進(jìn)程)。這些策略在Process Models里也有闡述。
檢測crash或者失誤的渲染
每個(gè)到瀏覽器進(jìn)程的IPC連接會(huì)觀察進(jìn)程句柄。如果這些句柄是signaled(有信號(hào)的),那么渲染進(jìn)程已經(jīng)掛了,標(biāo)簽頁會(huì)得到一個(gè)通知。從這時(shí)開始,我們會(huì)展示一個(gè)“sad tab”畫面來通知用戶渲染器已經(jīng)掛掉了。這個(gè)頁面可以按刷新按鈕或者通過打開一個(gè)新的導(dǎo)航來重新加載。這時(shí),我們會(huì)注意到?jīng)]有對應(yīng)的進(jìn)程,然后創(chuàng)建一個(gè)新的。
渲染器中的沙箱
給定的WebKit是運(yùn)行在獨(dú)立的進(jìn)程中的,所以我們有機(jī)會(huì)限制它對系統(tǒng)資源的訪問。例如,我們可以確保渲染器唯一的網(wǎng)絡(luò)權(quán)限是通過它的父瀏覽器進(jìn)程實(shí)現(xiàn)。相似的,我們可以限制它對文件系統(tǒng)的訪問權(quán)限來使用host操作系統(tǒng)內(nèi)置的權(quán)限。
除了限制渲染器對文件系統(tǒng)和網(wǎng)絡(luò)的訪問權(quán)限,我們也可以限制它對用戶的顯示器以及相關(guān)的東西的一些權(quán)限。我們在獨(dú)立的windows桌面(對用戶不可見)中運(yùn)行每個(gè)進(jìn)程。這避免了讓渲染器在新的標(biāo)簽頁或捕捉按鍵之間妥協(xié)。
歸還內(nèi)存
讓渲染器運(yùn)行在獨(dú)立的進(jìn)程中,賦予隱藏的標(biāo)簽頁更低的優(yōu)先級(jí)會(huì)更加直接。通常,Windows平臺(tái)上的最小化的進(jìn)程會(huì)把它們的內(nèi)存自動(dòng)房東一個(gè)“可用內(nèi)存”池里。在低內(nèi)存的情況下,Windows會(huì)在交換這部分內(nèi)存到更高優(yōu)先級(jí)內(nèi)存前,把它們交換到磁盤,以保證用戶可見的程序更易響應(yīng)。我們可以對隱藏的標(biāo)簽頁使用相同的策略。當(dāng)渲染器進(jìn)程沒有頂層標(biāo)簽頁時(shí),我們可以釋放進(jìn)程的“工作集”空間,作為一個(gè)給系統(tǒng)的信號(hào),讓它如果必要的話,優(yōu)先把這些內(nèi)存交換到磁盤。因?yàn)槲覀儼l(fā)現(xiàn),當(dāng)用戶在兩個(gè)標(biāo)簽頁間切換時(shí),減少工作集大小也會(huì)減少標(biāo)簽頁切換性能,所以我們是逐漸釋放這部分內(nèi)存的。這意味著如果用戶切換回最近使用的標(biāo)簽頁,這個(gè)標(biāo)簽頁的內(nèi)存比最近較少訪問的標(biāo)簽頁更可能被換入。有著足夠內(nèi)存的用戶運(yùn)行他們所有的程序時(shí)根本不會(huì)注意到這個(gè)進(jìn)程:事實(shí)上Windows只會(huì)在需要的時(shí)候重新聲明這塊數(shù)據(jù),所以在有充分內(nèi)存時(shí),不會(huì)有性能瓶頸。
這能幫助我們在低內(nèi)存的情況下得到最佳的內(nèi)存軌跡。幾乎不被使用的后臺(tái)標(biāo)簽頁相關(guān)的內(nèi)存可以被完全交換掉,前臺(tái)標(biāo)簽頁的數(shù)據(jù)可以被完全加載進(jìn)內(nèi)存。相反的,一個(gè)單進(jìn)程瀏覽器會(huì)在它的內(nèi)存里隨機(jī)分配所有標(biāo)簽頁的數(shù)據(jù),并且不可能如此清晰地隔離已使用的和未使用的數(shù)據(jù),導(dǎo)致了內(nèi)存和性能上的浪費(fèi)。
插件
Firefox風(fēng)格的NPAPI插件運(yùn)行在他們自己的進(jìn)程里,與渲染器隔離。這會(huì)在Plugin Architecture中描述。
如何添加新特性(不用擴(kuò)充RenderView/RenderViewHost/WebContents)
問題
過去,新的特性(比如,自動(dòng)填充選取樣例)可以通過把新特性的代碼導(dǎo)入到RenderView類(在渲染器進(jìn)程里)和RenderViewHost類(在瀏覽器進(jìn)程里)。如果一個(gè)新的特性是在瀏覽器進(jìn)程的IO線程里處理的,那么它的IPC信息由BrowserMessageFilter調(diào)度。RenderViewHost會(huì)只為了調(diào)用WebContent對象進(jìn)程調(diào)用IPC信息,這會(huì)調(diào)用另一塊代碼。所有的瀏覽器與渲染器之間的IPC信息會(huì)被聲明在一個(gè)巨大的render_messages_internal.h里,為每個(gè)新特性修改所有的這些文件意味著這些類會(huì)變得臃腫。
解決方案
我們增加了helper類和對上面的每個(gè)線程IPC信息的過濾的機(jī)制。這使得編寫自洽的特性更加容易。
渲染器端
如果你想要過濾和發(fā)送IPC信息,實(shí)現(xiàn)RenderViewObserver接口(content/renderer/render_view_observer.h)。RenderViewObserver基類持有一個(gè)RenderView類,管理對象的生命周期,使其綁定到RenderView(它是可重寫的)。這個(gè)類就可以過濾和發(fā)送IPC消息,此外還可以獲得許多特性需要的關(guān)于頁面加載與關(guān)閉的通知。作為一個(gè)例子,可以查看ChromeExtensionHelper (chrome/renderer/extensions/chrome_extension_helper.h)。
如果你的特性有一部分代碼是在WebKit內(nèi)的,避免通過WebViewClient接口回調(diào),這樣我們就不會(huì)使得WebViewClient變得龐大。考慮創(chuàng)建新的WebKit接口給WebKit代碼調(diào)用,讓渲染器端的類去實(shí)現(xiàn)它。作為一個(gè)例子,查看WebAutoFillClient (WebKit/chromium/public/WebAutoFillClient.h).
瀏覽器UI線程
WebContentsObserver (content/public/browser/web_contents_observer.h)接口允許UI線程的對象過濾IPC信息,以及給出關(guān)于頁面導(dǎo)航的通知。作為一個(gè)例子:查看TabHelper (chrome/browser/extensions/tab_helper.h)。
瀏覽器其他線程
為了過濾和發(fā)送IPC信息給其他的瀏覽器線程,比如IO/FILE/WEBKIT等等,實(shí)現(xiàn)BrowserMessageFilter接口(content/browser/browser_message_filter.h)。BrowserRenderProcessHost對象在它的CreateMessageFilters函數(shù)里創(chuàng)造和增加過濾器。
通常,如果一個(gè)特性有許多IPC消息,這些消息應(yīng)該移動(dòng)到一個(gè)獨(dú)立的文件(例如,不要加到render_messages_internal.h里)。這對過濾線程(除了IO線程)也有幫助。作為一個(gè)例子,查看content/common/pepper_file_messages.h。這允許他們的過濾器PepperFileMessageFilter方便的發(fā)送文件到file線程,而不用在很多位置指定它們的ID。
void PepperFileMessageFilter::OverrideThreadForMessage(
const IPC::Message& message,
BrowserThread::ID* thread) {
if (IPC_MESSAGE_CLASS(message) == PepperFileMsgStart)
*thread = BrowserThread::FILE;
}