想像一下 2007 年,畫面上開著十個分頁在趕報告。其中一個分頁的廣告,跑了一行寫爛的程式碼。下一秒——整個瀏覽器連同那十個分頁,一起消失。 還沒存的東西,蒸發了。

那個年代,瀏覽器是「一個程式」,全家共用一條命,一個人踩雷全隊陪葬。

今天不會了。某個分頁吃滿記憶體、卡到當掉,只要關掉那一個,其他分頁眨都不眨一下。這中間到底發生了什麼事?答案一句話就能講完,而且它會改變一般人看瀏覽器的方式:瀏覽器從「一個程式」,變成了一支分工的隊伍。

按下 Enter 那一刻,螢幕後面動起來的不是「一個瀏覽器」,而是一整支隊——有人指揮、有人去野區刷資源、有人守線、有人負責把畫面打出來。它們各自是獨立的進程,各自有自己的記憶體、自己的生死,彼此之間只能隔著一道牆喊話。開五個分頁,工作管理員裡可能跳出十幾個 chrome 進程——那不是壞掉,那是設計。

這篇(上集)就用一局 MOBA 的角度,把這支隊伍的選角表攤開來,順便回答一個一般人每天都在受惠、卻沒想過的問題:為什麼某個分頁跑到當掉、吃滿記憶體,只要關掉那一個分頁,其他分頁跟整個瀏覽器都還活得好好的?

(下集會接著講這支隊伍怎麼打「團戰」——也就是一次畫面更新背後的繪製流程。先把隊伍認熟。)


🏰 第 0 層:全景圖 — 瀏覽器是一支隊,不是一個人

⚔️ Level 0 / 5 瀏覽器這支五人隊(上)
0% 完成

先看全貌。一個現代 Chrome 瀏覽器,至少拆成這幾種角色,每一種都是一個獨立進程:

瀏覽器的多進程架構,用一支 MOBA 隊伍來看

注意所有箭頭都從「隊長」出去——它是唯一的中央協調者,是它生出其他所有進程的。其他角色彼此不能直接講話,要溝通只能透過隊長轉達(這個轉達機制叫跨進程通訊,IPC,下面會提到)。

Mogu 內心戲:

你可能會問:好好一個瀏覽器,幹嘛搞得像在打電動分位置這麼麻煩?一個進程把全部事情做完不就好了?

問得好,而這正是 2008 年以前所有瀏覽器的做法——全隊共用一條命。結果就是下面第 2 層要講的慘案。先賣個關子:那個年代你開十個分頁,其中一個網頁寫了爛 code 崩潰,會發生什麼事?答案不是「那個分頁掛掉」,是「你十個分頁一起陪葬,整個瀏覽器當場關閉」(⁠╯⁠°⁠□⁠°⁠)⁠╯


🏰 第 1 層:選角表 — 五個角色,各司其職

⚔️ Level 1 / 5 瀏覽器這支五人隊(上)
20% 完成

把每個角色拆開來看。每個角色後面括號裡是它在瀏覽器架構裡的正式名字,認一次就好,之後整篇都用中文角色名喊它。

1. 隊長 / 指揮(Browser Process

這是整支隊的中央大腦,也是一般人「以為的那個瀏覽器」。它負責所有網頁以外的東西:網址列、書籤、選單、分頁標籤、上一頁下一頁這些按鈕。它不負責畫網頁內容,但它指揮所有負責畫的人,並且管理導航、協調資源載入。在工作管理員看到的「那一個 Chrome」,就是它。

2. 打野(Network Service

當守線英雄需要資源(HTML、CSS、JS、圖片)時,它不會自己跑去網路上抓。它得拜託打野。打野專門處理所有網路請求、DNS、socket 連線這些底層髒活,刷回來再交給對應的英雄。

這個分工有個關鍵的資安意義:守線英雄碰不到網路,只能跟打野要。 為什麼這很重要,第 3、4 層會講。

3. 守線英雄(Renderer Process,主角)

這是整篇的主角。每一個分頁(更精確地說,每一個網站)都派一個守線英雄。 它跑 Blink 排版引擎跟 V8 這個 JavaScript 引擎,負責把網頁的 HTML 解析成 DOM、跑網頁的 JS、算排版、把畫面準備好。

它是最辛苦也最危險的角色——因為它跑的是「來自網路、完全不受信任的程式碼」。隨便哪個網站塞給它的 JS,它都得乖乖執行。所以守線英雄被關得最緊(第 4 層的沙箱)。

4. 輸出核心(GPU Process

守線英雄把畫面「準備」好之後,最後一哩——真正把一個個像素點到螢幕上——是輸出核心幹的。它是唯一一個直接跟顯卡(GPU)講話的角色,所有英雄的畫面、連同隊長那層外殼介面,最後都送到它這裡合成、輸出。

5. 工具人 / 輔助(Utility Process

剩下的雜活:解碼影音、跑擴充功能、處理儲存等等,各自再拆成獨立的小進程。原則是:只要那件事有風險(譬如解一個來路不明的影片檔),就把它隔離出去單獨關一間。

Mogu 想補充:

有沒有發現一個 pattern:危險的事,全部外包給「可以隨時被開除、開除了也不影響大局」的獨立進程。

這其實是寫任何系統都通用的智慧——把不受信任、容易出事的部分,跟你的核心隔離開。瀏覽器只是把這個原則做到極致:連「解一個影片檔」這種小事都怕,寧可多開一個進程關起來解。聽起來很神經質?是有點。但你想想瀏覽器每天要吞多少來路不明的東西,神經質剛剛好 ʕ⁠•⁠ᴥ⁠•⁠ʔ

小測驗

守線英雄想抓一張網路上的圖片,它會怎麼做?


🏰 第 2 層:為什麼要拆成一支隊 — 單進程時代的「共一條命」慘案

⚔️ Level 2 / 5 瀏覽器這支五人隊(上)
40% 完成

回到 Mogu 剛剛賣的關子。2008 年以前,瀏覽器是「單進程」的——整支隊共用一條命。

那是什麼概念?想像一局 MOBA,五個人共用同一條血條。中路的隊友手殘踩到一個地雷,結果不是他一個人死,是全隊瞬間一起倒地、直接 GG 投降。聽起來很荒謬,但這就是舊瀏覽器的日常:任何一個分頁的網頁寫了爛 code、跑進無窮迴圈、或踩到一個會崩潰的 bug,整個瀏覽器連同其他開著的所有分頁,一起當場關閉。 正在打的那篇還沒存的長文?沒了。

2008 年,Chrome 第一個把這件事翻轉過來:多進程架構。每個分頁派自己的守線英雄、住在自己的進程、有自己獨立的一條命。

這就是為什麼,現在某個分頁卡死、或跳出那個 Chrome 經典的「Aw, Snap!」哭臉錯誤頁,只要關掉或重新整理那一個分頁就好,其他分頁完全不受影響。在 2008 年以前,這是天方夜譚。

原文把多進程的好處整理成三點,用這套隊伍語言講就是:

  • 穩定:一個英雄崩潰,不會帶走全隊。
  • 資安:每個英雄關在自己的籠子裡,出事波及範圍有限(第 3、4 層的重點)。
  • 效能隔離:某條線在打一場硬仗(某個分頁跑超重的運算),不會卡住其他線——因為作業系統可以把不同進程排到不同的 CPU 核心上跑。
Mogu OS:

「Aw, Snap!」那隻哭臉,本質上是 Chrome 在跟你說:「報告隊長,中路那個守線的英雄陣亡了,但別緊張,其他人都還在,要不要我幫你把他買活(重新整理)?」

而在單進程的遠古時代,對應的訊息只有一種——整個視窗直接消失,連道別都沒有。所以下次看到那隻哭臉,別罵它,那其實是進步的象徵:它代表你的災情被控制在一條線,而不是全隊團滅 (⁠ ̄⁠▽⁠ ̄⁠)⁠/

小測驗

在單進程的舊瀏覽器年代,某一個分頁的網頁崩潰了,會發生什麼事?


🏰 第 3 層:為什麼每個英雄被鎖死在自己線上 — 網站隔離(Site Isolation

⚔️ Level 3 / 5 瀏覽器這支五人隊(上)
60% 完成

多進程解決了「穩定」,但還有一個更陰險的問題:資安

設想一個情境:同時開了兩個分頁,一個是網路銀行,一個是某個隨手點開、其實藏了惡意程式的爛網站。如果這兩個網站的守線英雄住在同一個進程、共用同一塊記憶體,那個惡意網站理論上就有機會偷看到隔壁銀行分頁的記憶體內容——餘額、登入狀態、一切。

對面那個臥底,就站在銀行帳號旁邊。

Chrome 的解法叫 Site Isolation(網站隔離):不同的網站,強制住在不同的進程裡,連塞在同一個頁面裡、來自不同網域的 iframe 都要拆出去單獨關一間(這個機制叫 OOPIF,把跨網站的 iframe 拆到進程外)。兩個網站的記憶體從此是兩塊完全分開的空間,臥底再怎麼翻自己的口袋,也翻不到隔壁那一間。

為什麼 Chrome 要把這件事做到這麼絕?因為 2018 年的 Spectre 漏洞

Spectre 證明了一件可怕的事:惡意的 JavaScript 可以利用 CPU 的「預測執行」這個底層機制,偷讀到它本來不該讀到的記憶體。換句話說,只要兩個網站還共用同一個進程,再多軟體層的防護都可能被這種 CPU 等級的攻擊繞過。唯一真正穩的解,就是從根本上不讓它們共用進程。 於是 Chrome 把網站隔離設成預設(大約在 Chrome 67),到 2024 年,桌面版 Chrome 有 99% 的使用者都開著它。

Mogu murmur:

Spectre 這個漏洞屌的地方在於:它不是某個瀏覽器寫爛了,是整個世代的 CPU 為了跑快一點,都內建了會漏資訊的機制。不分廠牌,主流 CPU 幾乎全中。

這就像你發現整個聯盟的英雄,因為某個底層機制設計,全部都有一個共通的破綻可以被偷家。你沒辦法靠「修某一個英雄」解決,只能改規則——把每個人關進獨立的房間,從架構上根除「偷看隔壁」的可能性。網站隔離就是這個「改規則」。代價是記憶體變多(第 4 層會算這筆帳),但比起你的銀行帳號被 CPU 級的攻擊偷走,這錢花得超值 (⁠⌐⁠■⁠_⁠■⁠)

小測驗

Chrome 為什麼要把『不同網站住在不同進程』設成預設(網站隔離)?


🏰 第 4 層:就算英雄被攻破,也偷不走東西 — 沙箱(與這支隊的伙食費)

⚔️ Level 4 / 5 瀏覽器這支五人隊(上)
80% 完成

網站隔離解決了「網站之間互相偷看」。但還有最後一道防線要處理:萬一某個守線英雄真的被惡意網站攻破、被對面破了門——它能對這台電腦幹嘛?

答案是:幾乎什麼都幹不了。因為每個守線英雄從一出生就被關在 沙箱(sandbox 裡。

沙箱是一個由作業系統層級強制執行的低權限籠子(Linux 上用 namespaces + seccomp 限制系統呼叫,Windows 上用 job object 砍權限)。被關在裡面的守線英雄可以盡情運算、排版、畫畫面,但一旦它想讀硬碟、開相機麥克風、或亂連網路,全部會被擋下來。要做這些「特權操作」,它只能乖乖跟隊長報備,由隊長審核、必要時跳出權限視窗問使用者同意。

這套「網站隔離 + 沙箱」的雙層防線,是現代瀏覽器資安的標準配備。Firefox、Safari 後來都收斂到差不多的設計——把每個網頁的程式碼,關進自己獨立的沙箱。

那這支豪華隊伍的伙食費呢? 天下沒有白吃的午餐。每多一個進程,就是多一份記憶體開銷——每個英雄都要自己一整套裝備。這就是 Chrome 被嫌「吃記憶體」的根本原因:開的不是分頁,是一支又一支的隊伍。光是網站隔離這一項,根據 Chrome 團隊的數字,大約就會多吃**一成出頭(約 10–13%)**的記憶體。

但這是一筆划算的交易:用更多記憶體,換來「一個分頁崩了不拖累全家」+「惡意網站偷不到使用者的資料」+「就算被攻破也出不了籠子」。穩定、資安、效能隔離,三個每天在享受卻沒感覺的好處,成本就是那幾根多出來的記憶體條。

Mogu 認真說:

每次有人在那邊「Chrome 怎麼這麼吃記憶體,垃圾」,我都很想把這篇甩過去。

它吃記憶體,是因為它把你開的每個網站都當成潛在的臥底,各自關進獨立的房間、配獨立的守衛、築獨立的牆。你嫌它胖,但它胖的每一克都在保護你不被隔壁分頁偷家。

要省記憶體很簡單——回到 2008 年的單進程,全家共一條命,一個分頁崩潰全部一起死,順便讓惡意網站能偷看你的銀行。要嗎?不要嘛。那就讓它胖 ┐⁠(⁠ ̄⁠ヘ⁠ ̄⁠)⁠┌

不過平心而論,Chrome 近幾年自己也在偷偷減肥——推出像 Memory Saver 這種功能,把你很久沒看的背景分頁先凍結、省下記憶體,要用再喚醒。所以它不是不知道自己胖,是在「保護你」跟「別吃這麼兇」之間找平衡點。胖得有道理,但也沒打算胖一輩子。

小測驗

沙箱(`sandbox`)對一個被攻破的守線英雄,主要做了什麼?


🎯 最終魔王:串起來

好,把這支隊伍從頭認一遍。

畫面上開著兩個分頁——左邊網銀,右邊一個隨手點開的爛網站。

隊長站在後排,管著網址列、分頁標籤、那層外殼介面,並且一聲令下,幫每個網站各派一個守線英雄。網銀的英雄住在自己的進程、自己的野區;爛網站的英雄住在另一個——網站隔離把牆築死,兩塊記憶體永不相見,就算右邊藏了 Spectre 等級的臥底,也偷不到左邊網銀的一分一毫。

每個守線英雄想要資源,不能自己亂跑,得喊打野去網路刷回來。它們本身被關在沙箱裡,碰不到硬碟、相機、網路——想越界就得先逃出籠子,門檻翻倍。等畫面準備好,全部交給輸出核心打到螢幕上。旁邊還有一票工具人默默處理解碼、擴充功能這些有風險的髒活,各自關一間。

於是,當右邊那個爛網站終於踩到自己的 bug、跳出「Aw, Snap!」哭臉——左邊的網銀分頁,連眨都沒眨一下。關掉右邊,重開,全隊繼續打。

這,就是一般人每天在用、卻從沒想過的那支隊伍。它胖、它吃記憶體——但它每一克肥油,都是那十個分頁不用一起陪葬的理由。

下集預告:認完了人,下一篇來看這支隊伍怎麼打一場「團戰」——也就是從一坨 HTML 字串,到眼睛看到的畫面,這支隊伍要在短短 16 毫秒內完成的一套連招。錯過那個時間視窗會怎樣?滑頁面時那個卡卡的「掉幀」就是答案。

延伸閱讀

Mogu 歪樓一下:

認完這支隊,你之後打開 Chrome 的工作管理員,看到那一長串 chrome 進程,腦子裡會自動浮現一張選角表:喔,這個是隊長、那幾個是守線的、這個吃 GPU 的是輸出核心。

恭喜,你再也回不去「瀏覽器是一個程式」那個純真的年代了。這個詛咒,跟 Lv-10 那個「按下網址之後的 0.2 秒」一樣,解不掉的 ٩⁠(⁠◕⁠‿⁠◕⁠。⁠)⁠۶