瀏覽器進程架構的演化

NO IMAGE

前言

  • 曾經你用 IE6或IE7 或者 firefox 的時候有遇到一個插件崩潰,而你打開的一系列頁面全部崩潰的場景麼?😰
  • 曾經你遇到過打開瀏覽器或某些頁面總是彈出很多你不想打開的惡意窗,要一個一個手動叉掉的情況嗎?😤
  • 曾經在你打開某個頁面由於某種原因整個瀏覽器就卡住,連關閉按鈕都點不動的時候,你是隻能強制關掉整個瀏覽器麼?🤔

如果上述情況你都遇見過,那我們今天就有得聊了。如果沒遇到也沒關係,今天的內容也會讓你拓展一下視野,當你真正遇到的時候,不至於一頭霧水。

今天的主題是《瀏覽器進程架構的演化》,可能你會問了,什麼是瀏覽器進程架構。其實很簡單,架構指的是一個軟件的各個方面的設計,那瀏覽器進程架構你就可以理解為 瀏覽器是怎麼設計其進程的操作和管理方式的

我們會將各種瀏覽器放到一起來聊一聊這些 瀏覽器在歷史發展過程中,其進程架構做了哪些調整?為什麼這樣調整?解決了哪些問題? 相信看完這篇文章,能讓你明白開頭提出的三個問題是怎麼回事兒,對你以後遇到的其它問題也給出一個思考方向。

好了,既然要說進程架構的演化,那你真的瞭解了進程是怎麼回事兒麼?與線程之間有啥關係?我們來簡單的過一遍

瞭解進程和線程

我們一般使用電腦的時候都會打開多個程序同時運行,比如同時打開了音樂程序放著歌,又打開了文檔程序寫記錄,還開了一個下載程序下載電影。但 CPU其實在某一個特定時刻是隻能執行一個程序的(先不考慮多核),那麼我們的電腦又是如何同時運行多個程序的呢?

答案就是 進程。進程是操作系統中的概念,操作系統在面對同時處理多個程序的時候,將應用程序抽象為進程來運行。這樣一來,操作系統就可以根據一定的規則快速的將CPU執行時間這一寶貴的資源分配給不同的進程去使用,因為切換分配的速度很快,所以看起來就像是多個應用程序同時在運行一樣。

但是隻有進程還不夠,往往一個應用程序整體在執行的時候會存在多個子任務的情況,就像一個在線音樂程序在運行的時候,需要同時運行網絡加載的子程序加載音樂流,還要運行音樂數據流的編解碼子程序,還會運行音樂界面的UI程序等。因而操作系統又在進程裡面劃分出了 線程 的概念。有了線程,一個應用程序就可以同時管理自己的多個子任務了。從這裡我們還要更新一個概念,CPU在某個特定時刻其實只能執行某個線程

我們再來整體過一遍一個程序的運行。當你運行一個應用程序的時候,操作系統會把你的應用程序封裝為一個進程來運行。因為程序運行是需要內存的,所以在分配線程的時候也會同時給你分配一塊兒對應的內存,用來存儲程序代碼、數據和文件資源等。當你的進程需要執行子任務的時候,就可以創建新的線程來執行。

瀏覽器進程架構的演化

這裡我們需要了解幾個特性:

  • 線程共享進程資源。進程是操作系統分配資源的基本單位,所以進程所需要的系統資源都是操作系統給的,而裡面的線程要用資源的時候只能共享進程所擁有的資源。這種資源包括內存空間,也包括操作系統的權限。
  • 一個線程崩潰,整個進程跟著崩。 其實很好理解,一個程序運行過程中,如果某個線程出錯了,因為內存是共享的,那如果產生了錯誤的數據,整個進程最終執行結果也很可能是錯的。所以操作系統就直接全部幹掉了。
  • 進程之間相互隔離,通過IPC進行通信。 雖然我們一直說一個進程就是一個應用程序,但有的時候一個應用程序也有可能會啟動子進程,進程之間的數據呢又是隔離開的,各自為戰。要同步某些數據內容,就可以通過某種進程間通信(IPC)的手段來進行。(IPC是一個統稱,指可用於不同進程之間通信的手段,這裡無需細究)

早期的單進程瀏覽器

早期的瀏覽器包括IE7及之前的IE瀏覽器,Firefox瀏覽器都是使用的單進程的瀏覽器架構,也就是說整個瀏覽器程序都是在一個進程中運行的。不同類型的瀏覽器的實際進程架構肯定是有一定的差異的,為了描述方便,我們都簡化為下面的這張圖來說明一下單進程瀏覽器存在的問題。

瀏覽器進程架構的演化

在這樣的進程架構裡,整個進程除了要運行瀏覽器窗口,下載資源,其中的頁面線程還要同時承擔了頁面渲染、JS執行以及各種插件的運行。這樣的一個進程運行起來在我們現在看來其實是有點刀尖上跳舞的感覺。因為這樣來運行包含很多頁面甚至很多插件的和功能的瀏覽器,會及其的不穩定,不流暢和不安全

先來看看為啥不穩定。我們回到最開始提的問題,曾經你有用IE6、7或者Firefox的時候,一個插件或頁面崩的時候,整個頁面崩的情況嗎?

現在聽起來是不是有點熟悉,可能你心裡已經有答案了吧。我們剛才談到,線程崩潰,整個進程也會全部崩掉。在單進程架構的瀏覽器裡,我們的頁面渲染引擎、插件程序,還有像IE裡有很多動態鏈接庫的程序都是可能出錯的❌,那麼不管這些程序在進程中的哪個線程裡面運行,其實只要有一個出了個錯,那麼整個瀏覽器就掛掉了。這就是我們說的不穩定

再來看看不流暢。我們已經知道CPU其實在某個時間點只能執行某個進程中的某一條線程。那麼當我們一個頁面線程中既包含了頁面的渲染又包含了JS的執行,還有各種插件執行的時候。假設我們JS代碼中寫了一個死循環的任務,那可想而知,整個瀏覽器中的其他任務就都沒法執行下去了——也就卡住了。那麼即使我們不那麼暴躁,只是我們的JS或者插件程序需要一直運行一些東西,當你頁面正在用JS執行動畫的時候,CPU突然被插件進程搶過去執行其他任務了,那你的動畫效果怕不是卡得你心慌 😫

最後說一下不安全的問題,這就要用到另一條我們剛提過的特性了——線程共享進程資源。在windows上用過殺毒軟件和各種安全衛士軟件的應該對 “惡意插件” 這個詞不陌生吧。插件這種東西本來是用來便捷的擴展瀏覽器功能特性的,比如我們最常見的Adobe Flash Player這個插件,以前幾乎人手必備。當瀏覽器和插件的程序在一個頁面裡面運行的時候,因為進程的內存是被共享的,因而插件就能獲取到瀏覽器運行過程中的數據,以及擁有和瀏覽器同等的系統權限。那麼當你的系統感染了惡意插件,在瀏覽器運行的過程中,這個插件就可以記錄你輸入到網頁的密碼,給你彈出各種窗口,打開多個網頁等等。現在你知道曾經你手動一個一個叉掉的那些無故打開的頁面是怎麼來的吧,同時你也應該瞭解到當初QQ號被盜的一種可能的原因了吧。

這就是早期的單進程瀏覽器存在的問題,不穩定、不流暢和不安全。而這一切都是由於把所有的東西像揉麵團一樣的放到一起所產生的問題,職責是混淆不清的、權限是一給全給的、還有 “一人放火全家遭殃” 的風險。

向多進程架構演化

可能是由於早期的網頁功能特性都比較簡單,不像現在這樣需要非常豐富的功能特性(比如:Canvas、WebGL、Webworker 這些東西),那個時候常規來說單進程就足夠了。曾經前端的開發工作還沒那麼複雜的時候,頁面很多都是後端直接就寫了。現在有了前端開發工程師這樣的一個職位,而且有了更加系統和全面的前端開發工作流,前端的重要性和複雜性都在不斷提升。除了單進程瀏覽器自身存在的一些問題,面對時代的變革,前端的崛起,瀏覽器若還停留在原地肯定是行不通的,必然無法支撐新的技術的發展,何況單進程本身帶來的問題也是需要解決的。

所謂多進程瀏覽器當然就是將瀏覽器的各種不同類別的任務拆分出來,放到多個不同的進程中去執行。這裡會用到一個關鍵的安全技術,就是Sandox。

Sandbox(沙盒)可以看做就是一個被限制了權限的進程,一個稍微嚴格的定義是這樣的

沙箱技術按照安全策略來限制程序對系統資源的使用,進而防止其對系統進行破壞,其有效性依賴於所使用的安全策略的有效性

也就是說沙盒限制了多少權限是根據你的安全策略來的,它能防止系統被惡意破壞,提供了一定的安全性,這也是我們上面提到的 “不安全” 這一個問題的解決方案的一環。具體的沙盒的實現方法呢在不同的操作系統上都是有差異的,畢竟進程是操作系統提供的,這不是我們的重點,就此打住。

接著我們一起來看一下Chrome、IE 和 Firefox這三款瀏覽器是如何演化自身到多進程的一個架構方案的?

Chrome的多進程架構

Google Chrome瀏覽器最早是在2008年發佈的,比起IE這種老牌瀏覽器,現在看來算是後起之秀。不過這個後起之秀並非只是突兀的來搶佔瀏覽器市場份額的,這一點從 Chrome一開始就是基於多進程架構 可見端倪。

瀏覽器進程架構的演化

在Chrome的多進程架構中,包含這樣幾種進程

  • 瀏覽器主進程。整個瀏覽器的主要進程,其他幾個進程都是這個進程的子進程,由它來管理和調配;同時你所看到的瀏覽器的整個窗口,包含地址輸入欄,書籤欄這些東西也都是它來展示的;
  • 渲染進程。一般來說一個Tab標籤頁面一個渲染進程(對於不一般的情況可以瞭解Site Isolation策略);每個渲染進程中會運行Blink佈局引擎,V8 JavaScript執行引擎等,單獨服務於一個Tab標籤頁;運行在沙盒中無法訪問系統資源。
  • 插件進程。一個插件單獨存在於一個進程當中,同時為了安全性,運行在沙盒中限制其權限。
  • 網絡進程。發起網絡請求訪問。
  • GPU進程。處理GPU渲染方面的任務。

可以看得出,chrome這樣的多進程設計,將原本揉到一起的各種任務根據職責的不同分拆了出去,極大的減輕了單進程的執行負擔。(其實,最早的chrome進程設計其實沒有單獨的網絡進程和GPU進程的,都是放到瀏覽器主進程中的

當你打開瀏覽器的時候,默認先啟動瀏覽器主進程,展示了整個瀏覽器窗口和地址欄等一系列的基礎UI界面。然後主進程啟動其他的各項子進程。當你附帶要運行插件的時候,就給對應的插件分配一個進程,如果沒有就不分配。當你打開了一個Tab標籤頁的時候,又會根據這個標籤頁創建一個渲染進程,用於標籤頁內頁面的渲染和腳本的執行。當然,如果你的頁面使用了WebGL或者CSS3動畫之類的需要GPU做渲染的東西,Chrome也給了一個統一的CPU進程來維護這些渲染任務。你的html頁面或者js腳本是需要從服務器下載到本地才能被渲染進程使用的,那我們就單獨有個網絡進程來處理這些和網絡交互相關的任務。因此,一個基本的多進程架構就這樣呈現了出來。

其中我們需要注意到幾點:

  1. 其他各個進程都是由瀏覽器主進程啟動和管理的,進程間通過IPC進行數據通信。
  2. 渲染進程和插件進程可能不止一個,根據要渲染的頁面和插件數而定。

解決問題了麼

那這樣解決了我們單進程瀏覽器中出現的問題麼?當然是解決了。

首先我們看不穩定的問題,不穩定是由於插件或者渲染引擎之類的出錯導致的,現在不管是插件還是頁面的渲染工作都是單獨在各自的線程中運行的,如果某個插件出錯了,那最多是那個插件不能用,其他頁面的瀏覽和其他的插件都會照常運行。如果是某個渲染進程出錯,那也只是那個頁面看不了了而已,其他頁面也不會受到影響,更別說把整個瀏覽器搞崩潰。說到這裡,你是不是又想起了曾經某個頁面顯示“崩潰了”,然後你關閉那個Tab標籤然後重新打開一下就好了;又或者曾經瀏覽器告訴你你的Flash插件崩潰了,你想看有些視頻網站看不了,但是網頁還是正常的,重啟瀏覽器之後,Flash又恢復了。🤓

然後再是不流暢。在頁面、插件和瀏覽器三種各自分別有各自的線程的情況下,插件的執行不流暢只會影響到插件自身,頁面的無論渲染還是js的執行,不流暢那也只是那一個頁面。若是放在單進程瀏覽器時代,我們說的不流暢那就是不管任何頁面還是任何一個插件引起的,會導致整個瀏覽器窗口、所有頁面、所有插件的不流暢。多進程的方式,讓這種“共患難”的模式不復存在。🤣

最後再談不安全。多進程對安全問題的處理主要靠兩方面:1.渲染進程和插件進程運行在沙盒環境中;2.相互隔離的進程數據不共享。拆分成多線程的形式讓我們能夠對每個單獨的頁面和每個單獨的插件加上沙盒限制,剝除了渲染進程的文件系統權限,網絡訪問權限等;對於插件進程,也無法對某個頁面全權操控,或者對瀏覽器主進程數據任意訪問和修改,插件自身能做什麼也嚴格受到限制。如果他們需要某種操作,比如網絡請求或者文件訪問怎麼辦呢?通過IPC告知瀏覽器主進程,主進程來決定給不給你權限以及給你哪些權限。是不是又想起了在chrome中打開某個地圖網站,瀏覽器問你“是否允許網站獲取你的位置信息”,又或者 “是否允許網站使用攝像頭” 😇

做一個小實驗

現在你可以跟著一起打開Chrome 【設置】=> 【更多工具】=> 【任務管理器】看看你的Chrome運行了哪些進程。

瀏覽器進程架構的演化

這裡我直接打開Chrome瀏覽器,然後打開了兩個Tab,一個是百度一個是Google。我們可以看到這裡的任務管理器裡顯示了我們的瀏覽器運行了哪些進程,包括進程的名稱,內存佔用大小,CPU使用率,網絡和進程ID等信息(實際你可以在這個標頭點擊鼠標右鍵列出更多參考信息)。和我們剛才說的差不多,我們無論如何得有一個瀏覽器主進程,然後有一個GPU進程渲染一些圖形相關的東西,還有一個Network 的網絡進程,另外兩個標籤頁分別就是兩個渲染進程。這個時候我想模擬一下當某個渲染進程崩潰會出現啥,點擊百度這這個標籤頁,然後點擊右下角的結束進程。你是不是也和我一樣,出現了下面的頁面呢。

瀏覽器進程架構的演化

你應該也注意到了整個瀏覽器仍然是穩定運行的,Google的那個頁面也是正常的,崩掉的只有百度的這一個頁面而已。如果你的瀏覽器裡運行了插件,你也可以試著關掉插件進程試試。

這個小實驗是否對 “曾經在你打開某個頁面由於某種原因整個瀏覽器就卡住,連關閉按鈕都點不動的時候,你是隻能強制關掉整個瀏覽器麼?” 這個問題於你有所啟發呢?

站點隔離(Site Isolation)

對於Chrome的多進程架構還有一個必然需要談到的東西,就是站點隔離 Site Isolation 策略。我們之前說的是一個標籤頁一個渲染進程,但實際情況下如果真的每個便籤頁都是一個渲染進程的話,那還是有點浪費進程資源的。另一個方面有個很特殊的問題就是如果只是一個標籤頁一個渲染進程的話,那如果存在一個標籤頁,裡面有一個iframe引用了另一個頁面,那這兩個不同網站的頁面就會在同一個進程中去渲染和執行腳本。這樣來看,Tab的進程隔離並不能對站點做隔離。而且由於已知存在的CPU級別的漏洞Spectre和Meltdown(它們可以讓程序訪問到不屬於當前進程的數據),從安全性上來說,Chrome也必須對“一個標籤頁一個渲染進程”的策略做調整。

這裡的調整策略就是站點隔離策略。所謂站點隔離,就是指同一個域下的內容,會放在同一個渲染進程中進行渲染。對於剛才我們提到的一個Tab標籤頁中存在iframe引入其他網頁的情況,標籤頁自身肯定是一個渲染進程,但對於內部的iframe,如果iframe是和標籤頁屬於同一域,那就共用渲染進程,否則會給這個iframe一個單獨的渲染進程。比如下面這張圖中的 a.comb.comc.com 的頁面就是單獨各自都有一個渲染進程。而如果 b.com你將其換成 a.com 下的某個其他頁面,那他們就會使用同一個渲染進程了。

瀏覽器進程架構的演化

其實站點隔離最重要的作用還是對於安全性的要求,其次才是可以節省那麼一點內存。

這裡給各位提供一個測試這個特性的網站: csreis.github.io/tests/cross… 。 在Chrome中打開,同時打開上面我們實驗的時候打開過的【任務管理器】,點擊測試網站的按鈕,觀察觀察會發生什麼。(注:屬於同一個進程的時候,只有表示頁面的信息行,沒有進程ID )。😎

這裡其實站點隔離策略也是適用於從一個頁面裡打開的新的Tab頁的,比如我先打開得到招聘頁的網站 www.igetget.com/join/work 點擊右上角 “瞭解我們”,這個時候會打開一個新的Tab,然後再去【任務管理器】裡面查看,你會發現,新打開的Tab頁是沒有進程ID的,當你點擊這個進程的時候,它會選中這兩個便籤頁行,其實也就是說它們是用的同一個進程。(注意,這裡我們新打開的Tab和原始頁面屬於同一個域)

瀏覽器進程架構的演化

IE的多進程架構

IE瀏覽器從IE8開始其進程架構就變成多進程的了。下圖就是IE8的進程架構示意圖。看起來是不是有點慌亂,我們來理一下。

瀏覽器進程架構的演化

在說IE的多線程之前,我們需要先簡單瞭解一下DLL這個東西,DLL叫做動態鏈接庫,可以看做是某些個特定功能實現的代碼庫,在windows上編程要調用一些windows系統的功能的時候,就可以直接通過調用一些DLL庫給我們提供對應的功能。

IE的主要進程其實就兩類:

  1. 框架進程。
  2. Tab進程。

框架進程就是我們圖中最上方的那個紅色區域表示的東西,它呢其實主要就是一個UI進程,底層使用了 BrowseUI.dll 這個DLL庫,用來構建用戶界面,包括工具欄、菜單一類的東西。框架進程可以創建多個Tab進程,Tab進程與框架進程之間的通信是用了ALPC(高級本地調用)的機制,這種機制是windows內核中常用的,感興趣可以自己再去了解。

Tab進程是用來渲染頁面,執行JS代碼(當然這裡可能是JScript和VBscript,對於IE8至少不是標準的Ecmascript),以及執行一個IE插件程序。Tab進程裡會調用到有關歷史記錄的 ShDocVw.dll 庫,有關HTML解析、DOM生成和操作和JScript腳本的 MSHTML.dll庫,有關網絡和緩存的 Winlnet.dll 以及再上面包裹了一層的更安全的提供下載資源的 URLMon.dll 庫等。可能你會想 **Tab進程就是說一個Tab頁一個進程麼?**實際上不是的,考慮到創建一個進程的開銷比較大,在windows上創建一個新的Tab進程也需要載入這麼一堆的動態鏈接庫。IE8的Tab進程數目是有一定的限制策略的,當Tab進程達到最大限度的時候,新打開的網頁會複用之前創建的Tab進程來處理。(另:這個策略存在於windows註冊表中的 TabProcGrowth 鍵值,你可以Google一下這個配置信息看看改變它會如何改變Tab進程的使用策略)

Firefox的多進程架構

早期的firefox也是單進程的,當他們也發現把所有Tab頁的HTML渲染和JavaScript執行,以及瀏覽器窗口的UI都放到一個進程裡是一個非常糟糕的設計的時候,也開始在進程架構上做出了調整。然後Mozilla啟動了一個叫做 Electrolysis (也叫做 e10s )的計劃準備逐步將進程架構往多進程上去遷移,這個時間節點是在2016年。於是經歷了Firefox一共9個版本的迭代,從Firefox 48 到 56,逐步的完善了多進程架構。

對於瀏覽器主進程、GPU進程、擴展程序進程來說,相信各位已經很瞭解了。也就不多廢話了。

主要來看Tab進程,Tab進程和Chrome的渲染進程類似,也是用來渲染頁面,執行Javascript代碼的。不過對於Firefox的Tab進程來說,可以看出Firefox的Tab進程和IE的Tab進程有個相似點就是:有多個Tab進程,但都不一定是一個頁面一個Tab進程,一個Tab進程可能會負責多個頁面的渲染。作為對比,Chrome是以一個頁面一個渲染進程,加上站點隔離的策略來進行的。所以我們可想而知,一般情況下,Chrome所需要的內存消耗應該也會更多,畢竟不像Firefox和IE一樣對頁面渲染所用的進程做最大值的限制,站點隔離的策略也只是優化了那麼一些。我在下面附上一張內存消耗對比圖,各位可以自行看一下。

瀏覽器進程架構的演化

總結

對於早期的單進程瀏覽器來說,頁面渲染、JS執行、插件運行、還有瀏覽器主程序的運行都放在單個的一個進程裡,對於瀏覽器來說,無法應用更好的安全特性,而且很容易一崩全崩,即使是正常運行也會出現一些不流暢的問題。於是各個瀏覽器廠商最終都在向多進程瀏覽器做轉變,Chrome瀏覽器在最開始發佈的時候就是採用了多進程的架構,IE是從IE8開始做的調整,而Firefox則是開啟 Electrolysis 計劃,在2016年前後逐步將Firefox遷移到了多進程的架構模式。

在遷移的過程中,各個瀏覽器有個相同點是各個瀏覽器都將瀏覽器的主進程,也就是用來運行瀏覽器窗口的進程單獨抽離成一個來運行,以此為基礎創建各個去進行頁面渲染的渲染進程等子進程,並統一管理。IE8的多進程架構比較簡單,且強依賴於windows系統的各種動態鏈接庫;Chrome呢比較大方的給到每個頁面一個渲染進程,同時也用站點隔離的策略做好了優化,對插件進程也是比較大方的給每個插件都有單獨的進程,這樣執行的各個程序從進程層面隔離開來,相互影響降到很小的程度,唯一的問題就是進程分配太大方,內存佔用也上去了;Firefox在多進程架構的設計上,給了一個專門運行插件進程,用來渲染頁面的Tab進程和IE8的Tab進程有個相同點在於都是對Tab進程有個最大值的限制的。

另外Chrome最新的進程架構也在往SOA,也就是面向服務的架構的方向轉型。實現的方式就是將網絡、設備、UI、媒體一類的程序抽象為服務,統一放入基礎服務層中,供瀏覽器主進程、插件進程和渲染進程調用。利於節省資源以及擁有更良好可擴展性,降低現有多進程架構中耦合性太高的問題。這塊兒可以各位自行了解,文末給出的參考資料中也有涉及。

相信各位對於瀏覽器的進程架構至少也略知一二了,希望對你的日常開發或對瀏覽器的使用上有一定的幫助。

參考資料

  1. Internet Explorer Wikipedia: en.wikipedia.org/wiki/Intern…
  2. Internet Exploere Architecture: docs.microsoft.com/en-us/previ…
  3. Modern Multi-Process Browser Architecture: helgeklein.com/blog/2019/0…
  4. Inside look at mordern web browser (part 1): developers.google.com/web/updates…
  5. Multi-process Architecture: dev.chromium.org/developers/…
  6. Multiprocess Firefox: developer.mozilla.org/en-US/docs/…
  7. Multi-Process Firefox: everything you need to know: (www.ghacks.net/2016/07/22/…)[www.ghacks.net/2016/07/22/…]
  8. 極客時間《瀏覽器工作原理與實戰》– Chrome架構: 僅僅打開了1個頁面,為什麼有4個進程?
  9. Servicification: www.chromium.org/servicifica…
  10. Meltdown/Spectre: developers.google.com/web/updates…

相關文章

聊聊rocketmq的RemotingTooMuchRequestException

HTMLEmail的編寫

「從模板消息改版訂閱消息」小程序推送

【Oracle學習06】DML與併發性,UNDO,死鎖