多進程架構(原文地址:http://www.chromium.org/developers/design-documents/multi-process-architecture )
這篇文檔描述了 Chromium 的上層架構。
a、問題所在
幾乎沒可能建立一個永遠不崩潰或掛機的渲染引擎,也不可能建立一個完美安全的渲染引擎。
某些方面來說,現在的 Browser 有點像過去的單使用者,多任務的作業系統。在那樣的作業系統裡,如果有一個胡作非為的程式,將會讓整個系統崩潰。也就像一個胡作非為的頁面、或外掛程式錯誤,就能帶來整個瀏覽器和當前啟動並執行所有標籤的崩潰。
現在的作業系統強壯多了,因為他們將應用程式劃分到一個個的獨立進程中,這些進程間獨立運行,不互相影響,一個程式中的crash一般不會影響到別的應用程式或者整個系統,而且某使用者要訪問別的使用者的資料也是不允許的。
b、架構概覽
主進程,也叫做 "Browser" 進程,運行UI 和管理tab。
同樣的,tab進程叫做 "Render" 進程,使用 web-kit 排布引擎來解析和排布 HTML。
c、管理多個Render 進程
每個Render 進程都有一個全域的 RenderProcess 對象管理和父進程(Browser)間的通訊以及全域的狀態,Browser對每個RenderProcess都維護了一個對應的 RenderProcessHost,用來管理 Browser 的狀態和用於通訊。Browser 和 Renderer 之間的通訊使用 Chromium's IPC system。
d、管理檢視
每個Render 進程都有一個或者多個的RenderView對象,這些對象都由RenderProcess管理,RenderView對應著每個 Tab的內容。
對應的 RenderProcessHost 維護著一個 RenderViewHost 對象,這個對象對應著每個 Renderer裡的view。
每個 view 擁有一個 viewID,這個viewID是用來區分一個renderer裡的多個不同的view,並且這些id在一個 renderer進程中是唯一的,但在整個Browser 中不是唯一的,所以要區分一個view需要一個RenderProcessHost和一個View ID。
從Browser到指定的tab內容之間的通訊是通過這些 RenderViewHost 對象來完成的,這些RenderViewHost對象知道怎麼把訊息通過 RenderProcessHost 傳到 RenderProcess ,然後再傳到 RenderView。
e、組件和介面
在 Render 進程中:
- RenderProcess 處理 IPC 是通過Browser 中對應的 RenderProcessHost,每個render 進程都只有一個RenderProcess,這就是browser和render的通訊路徑了。
- RenderView 對象和 Browser 中對應的RenderViewHost通訊(通過 RenderProcess),RenderView 對象也和我們的WebKit 嵌入層通訊。這個對象代表tab或視窗裡的頁面內容。
在Browser 進程中:
- Browser對象代表最高層的瀏覽器視窗。
- RenderProcessHost對象代表browser 端,browser和renderView的通訊,每個render 進程只有一個 RenderProcessHost。
- RenderViewHost 對象封裝了和 RenderView 的通訊。
- browser中的RenderWidgetHost 處理RenderWidget的輸入和繪畫。
f、共用render進程
一般來說,一個新視窗或者一個新tab,就會建立一個新進程,並建立一個RenderView,但有時候,可能要多個tab共用一個Render進程。
一個web應用開啟一個新視窗的時候,他是期待一個同步的通訊的,比如javascript的 window.open,這個情況下,我們需要在新開的視窗中重用這個進程。
我們也有辦法讓一個新的tab指向一個已存在的render進程,這種情形是用於進程太多的情況,或者使用者已經有一個進程開啟導航到網域名稱。這些辦法你可以查看這裡(http://www.chromium.org/developers/design-documents/process-models )。
g、監測崩潰和胡作非為的renderer
每個IPC串連到browser進程, browser進程都會監測進程的控制代碼。
如果這些控制代碼激發,render進程已經崩潰並且tabs被告知這個崩潰。到現在為止,我們會顯示一個“sad tab”的頁面,通知使用者,這個renderer已經崩潰了。點擊reload按鈕可以讓這個頁面重新裝載,或者開啟一個新的導航。當這個發生,我們會通知已經沒有進程,並會建立一個新的。
h、讓renderer變成一個沙箱
假設Webkit運行在一個單獨的進程中,我們可以限制它訪問系統資源。例如我們可以讓這個renderer只是通過他的父進程browser訪問網路,同樣的,我們可以通過作業系統的許可權系統來限制它訪問檔案系統。
除了限制renderer訪問檔案系統和網路,我們還可以限制它訪問使用者的顯示和相關的對象。我們每個render進程都運行在一個看不到的視窗裡,這個視窗是獨立的,叫做“Desktop”,這可以阻止一個“危險的”renderer(被植入危險的代碼)開啟一個新window或者截取你的鍵盤點擊。
i、把記憶體還回去
正因為renderer運行在一個單獨的進程中,所以我們需要考慮隱藏的tabs的低優先順序問題。
正常的,windows會把一個最小化的進程自動放進一個“有效記憶體池”中,當這個進程處於 low-memory 狀態,windows 將把這個記憶體存放到硬碟中,直到他們變為higher-priority的記憶體。
為了讓使用者可見的程式更良好的反應,我們可以把這個原則用於“隱藏”的tabs,當一個renderer不是最進階別的tab時,我們可以釋放那個進程的 “working set”大小,作為對系統的暗示,以便系統能把它的記憶體轉放到硬碟上(如果系統認為需要的話)。
因為我們發現,減少working set 的大小,也會降低tab切換的效能。當使用者在兩個tabs之間切換,我們漸漸的釋放記憶體。這意味著,如果使用者切換回最近使用的標籤,該標籤的記憶體是更可能被頁面化(存到disk)的,而少用的tab反而不會被頁面化。
當然,有足夠記憶體的使用者不用管這個,因為windows只回收他需要的資料,所以如果記憶體足夠,這樣做對效能沒有一點的好處。
這有助於我們在低記憶體的情況下得到更最佳化的記憶體佔用,少用的tab將會被完全從記憶體替換到硬碟,而前端的tab,將可以整個裝載進來記憶體中。相比之下,一個單進程的瀏覽器將其所有的tab資料隨機的分發到記憶體中,這樣就沒辦法把使用或者不實用的資料分離得那麼清晰,這樣就浪費記憶體以及效能了。
j、外掛程式
Firefox風格的 NPAPI 外掛程式會運行在他自己的進程中,和renderer隔開。具體可以看這裡:http://www.chromium.org/developers/design-documents/plugin-architecture 。