瀏覽器架構
在講瀏覽器架構之前,先理解兩個概念,程序和執行緒。
程序(process)是程式的一次執行過程,是一個動態概念,是程式在執行過程中分配和管理資源的基本單位,執行緒(thread)是CPU排程和分派的基本單位,它可與同屬一個程序的其他的執行緒共享程序所擁有的全部資源。
簡單的說呢,程序可以理解成正在執行的應用程式,而執行緒呢,可以理解成我們應用程式中的程式碼的執行器。而他們的關係可想而知,執行緒是跑在程序裡面的,一個程序裡面可能有一個或者多個執行緒,而一個執行緒,只能隸屬於一個程序。
大家都知道,瀏覽器屬於一個應用程式,而應用程式的一次執行,可以理解為計算機啟動了一個程序,程序啟動後,CPU會給該程序分配相應的記憶體空間,當我們的程序得到了記憶體之後,就可以使用執行緒進行資源排程,進而完成我們應用程式的功能。
而在應用程式中,為了滿足功能的需要,啟動的程序會建立另外的新的程序來處理其他任務,這些創建出來的新的程序擁有全新的獨立的記憶體空間,不能與原來的程序內向記憶體,如果這些程序之間需要通訊,可以透過IPC機制(Inter Process Communication)來進行。
很多應用程式都會採取這種多程序的方式來工作,因為程序和程序之間是互相獨立的它們互不影響,也就是說,當其中一個程序掛掉了之後,不會影響到其他程序的執行,只需要重啟掛掉的程序就可以恢復執行。
瀏覽器的多程序架構
假如我們去開發一個瀏覽器,它的架構可以是一個單程序多執行緒的應用程式,也可以是一個使用IPC通訊的多程序應用程式。
不同的瀏覽器使用不同的架構,下面主要以Chrome為例,介紹瀏覽器的多程序架構。
在Chrome中,主要的程序有4個:
瀏覽器程序 (Browser Process):負責瀏覽器的TAB的前進、後退、位址列、書籤欄的工作和處理瀏覽器的一些不可見的底層操作,比如網路請求和檔案訪問。
渲染程序 (Renderer Process):負責一個Tab內的顯示相關的工作,也稱渲染引擎。
外掛程序 (Plugin Process):負責控制網頁使用到的外掛
GPU程序 (GPU Process):負責處理整個應用程式的GPU任務
這4個程序之間的關係是什麼呢?
首先,當我們是要瀏覽一個網頁,我們會在瀏覽器的位址列裡輸入URL,這個時候Browser Process會向這個URL傳送請求,獲取這個URL的HTML內容,然後將HTML交給Renderer Process,Renderer Process解析HTML內容,解析遇到需要請求網路的資源又返回來交給Browser Process進行載入,同時通知Browser Process,需要Plugin Process載入外掛資源,執行外掛程式碼。解析完成後,Renderer Process計算得到影象幀,並將這些影象幀交給GPU Process,GPU Process將其轉化為影象顯示螢幕。
多程序架構的好處
Chrome為什麼要使用多程序架構呢?
第一,更高的容錯性。當今WEB應用中,HTML,JavaScript和CSS日益複雜,這些跑在渲染引擎的程式碼,頻繁的出現BUG,而有些BUG會直接導致渲染引擎崩潰,多程序架構使得每一個渲染引擎執行在各自的程序中,相互之間不受影響,也就是說,當其中一個頁面崩潰掛掉之後,其他頁面還可以正常的執行不收影響。
第二,更高的安全性和沙盒性(sanboxing)。渲染引擎會經常性的在網路上遇到不可信、甚至是惡意的程式碼,它們會利用這些漏洞在你的電腦上安裝惡意的軟體,針對這一問題,瀏覽器對不同程序限制了不同的許可權,併為其提供沙盒執行環境,使其更安全更可靠
第三,更高的響應速度。在單程序的架構中,各個任務相互競爭搶奪CPU資源,使得瀏覽器響應速度變慢,而多程序架構正好規避了這一缺點。
多程序架構最佳化
之前的我們說到,Renderer Process的作用是負責一個Tab內的顯示相關的工作,這就意味著,一個Tab,就會有一個Renderer Process,這些程序之間的記憶體無法進行共享,而不同程序的記憶體常常需要包含相同的內容。
瀏覽器的程序模式
為了節省記憶體,Chrome提供了四種程序模式(Process Models),不同的程序模式會對 tab 程序做不同的處理。
Process-per-site-instance (default) - 同一個 site-instance 使用一個程序
Process-per-site - 同一個 site 使用一個程序
Process-per-tab - 每個 tab 使用一個程序
Single process - 所有 tab 共用一個程序
這裡需要給出 site 和 site-instance 的定義
site 指的是相同的 registered domain name(如: google.com ,bbc.co.uk)和scheme (如:https://)。比如a.baidu.com和b.baidu.com就可以理解為同一個 site(注意這裡要和 Same-origin policy 區分開來,同源策略還涉及到子域名和埠)。
site-instance 指的是一組 connected pages from the same site,這裡 connected 的定義是 can obtain references to each other in script code 怎麼理解這段話呢。滿足下面兩中情況並且開啟的新頁面和舊頁面屬於上面定義的同一個 site,就屬於同一個 site-instance
使用者透過這種方式點選開啟的新頁面
JS程式碼開啟的新頁面(比如 window.open)
理解了概念之後,下面解釋四個程序模式
首先是Single process,顧名思義,單程序模式,所有tab都會使用同一個程序。接下來是Process-per-tab ,也是顧名思義,每開啟一個tab,會新建一個程序。而對於Process-per-site,當你開啟 a.baidu.com 頁面,在開啟 b.baidu.com 的頁面,這兩個頁面的tab使用的是共一個程序,因為這兩個頁面的site相同,而如此一來,如果其中一個tab崩潰了,而另一個tab也會崩潰。
Process-per-site-instance 是最重要的,因為這個是 Chrome 預設使用的模式,也就是幾乎所有的使用者都在用的模式。當你開啟一個 tab 訪問 a.baidu.com ,然後再開啟一個 tab 訪問 b.baidu.com,這兩個 tab 會使用兩個程序。而如果你在 a.baidu.com 中,透過JS程式碼打開了 b.baidu.com 頁面,這兩個 tab 會使用同一個程序。
預設模式選擇
那麼為什麼瀏覽器使用Process-per-site-instance作為預設的程序模式呢?
Process-per-site-instance相容了效能與易用性,是一個比較中庸通用的模式。
相較於 Process-per-tab,能夠少開很多程序,就意味著更少的記憶體佔用
相較於 Process-per-site,能夠更好的隔離相同域名下毫無關聯的 tab,更加安全
導航過程都發生了什麼
前面我們講了瀏覽器的多程序架構,講了多程序架構的各種好處,和Chrome是怎麼最佳化多程序架構的,下面從使用者瀏覽網頁這一簡單的場景,來深入瞭解程序和執行緒是如何呈現我們的網站頁面的。
網頁載入過程
之前我們我們提到,tab以外的大部分工作由瀏覽器程序Browser Process負責,針對工作的不同,Browser Process 劃分出不同的工作執行緒:
UI thread:控制瀏覽器上的按鈕及輸入框;
network thread:處理網路請求,從網上獲取資料;
storage thread: 控制檔案等的訪問;
第一步:處理輸入
當我們在瀏覽器的位址列輸入內容按下回車時,UI thread會判斷輸入的內容是搜尋關鍵詞(search query)還是URL,如果是搜尋關鍵詞,跳轉至預設搜尋引擎對應都搜尋URL,如果輸入的內容是URL,則開始請求URL。
第二步:開始導航
回車按下後,UI thread將關鍵詞搜尋對應的URL或輸入的URL交給網路執行緒Network thread,此時UI執行緒使Tab前的圖示展示為載入中狀態,然後網路程序進行一系列諸如DNS定址,建立TLS連線等操作進行資源請求,如果收到伺服器的301重定向響應,它就會告知UI執行緒進行重定向然後它會再次發起一個新的網路請求。
第三步:讀取響應
network thread接收到伺服器的響應後,開始解析HTTP響應報文,然後根據響應頭中的Content-Type欄位來確定響應主體的媒體型別(MIME Type),如果媒體型別是一個HTML檔案,則將響應資料交給渲染程序(renderer process)來進行下一步的工作,如果是 zip 檔案或者其它檔案,會把相關資料傳輸給下載管理器。
與此同時,瀏覽器會進行 Safe Browsing 安全檢查,如果域名或者請求內容匹配到已知的惡意站點,network thread 會展示一個警告頁。除此之外,網路執行緒還會做 CORB(Cross Origin Read Blocking)檢查來確定那些敏感的跨站資料不會被髮送至渲染程序。
第四步:查詢渲染程序
各種檢查完畢以後,network thread 確信瀏覽器可以導航到請求網頁,network thread 會通知 UI thread 資料已經準備好,UI thread 會查詢到一個 renderer process 進行網頁的渲染。
瀏覽器為了對查詢渲染程序這一步驟進行最佳化,考慮到網路請求獲取響應需要時間,所以在第二步開始,瀏覽器已經預先查詢和啟動了一個渲染程序,如果中間步驟一切順利,當 network thread 接收到資料時,渲染程序已經準備好了,但是如果遇到重定向,這個準備好的渲染程序也許就不可用了,這個時候會重新啟動一個渲染程序。
第五步:提交導航
到了這一步,資料和渲染程序都準備好了,Browser Process 會向 Renderer Process 傳送IPC訊息來確認導航,此時,瀏覽器程序將準備好的資料傳送給渲染程序,渲染程序接收到資料之後,又傳送IPC訊息給瀏覽器程序,告訴瀏覽器程序導航已經提交了,頁面開始載入。
這個時候導航欄會更新,安全指示符更新(地址前面的小鎖),訪問歷史列表(history tab)更新,即可以透過前進後退來切換該頁面。
第六步:初始化載入完成
當導航提交完成後,渲染程序開始載入資源及渲染頁面(詳細內容下文介紹),當頁面渲染完成後(頁面及內部的iframe都觸發了onload事件),會向瀏覽器程序傳送IPC訊息,告知瀏覽器程序,這個時候UI thread會停止展示tab中的載入中圖示。
網頁渲染原理
導航過程完成之後,瀏覽器程序把資料交給了渲染程序,渲染程序負責tab內的所有事情,核心目的就是將HTML/CSS/JS程式碼,轉化為使用者可進行互動的web頁面。那麼渲染程序是如何工作的呢?
渲染程序中,包含執行緒分別是:
一個主執行緒(main thread)
多個工作執行緒(work thread)
一個合成器執行緒(compositor thread)
多個光柵化執行緒(raster thread)
不同的執行緒,有著不同的工作職責。
構建DOM
當渲染程序接受到導航的確認資訊後,開始接受來自瀏覽器程序的資料,這個時候,主執行緒會解析資料轉化為DOM(Document Object Model)物件。
DOM為WEB開發人員透過JavaScript與網頁進行互動的資料結構及API。
資源子載入
在構建DOM的過程中,會解析到圖片、CSS、JavaScript指令碼等資源,這些資源是需要從網路或者快取中獲取的,主執行緒在構建DOM過程中如果遇到了這些資源,逐一發起請求去獲取,而為了提升效率,瀏覽器也會執行預載入掃描(preload scanner)程式,如果如果HTML中存在img、link等標籤,預載入掃描程式會把這些請求傳遞給Browser Process的network thread進行資源下載。
JavaScript的下載與執行
構建DOM過程中,如果遇到