前言
本文是进击的大葱对Mario Kosaka写的inside look at modern web browser系列文章的翻译。这里的翻译不是指直译,而是结合个人的理解将作者想表达的意思表达出来,而且会尽量补充一些相关的内容来帮助大家更好地理解。
CPU,GPU,内存和多进程架构
在本篇文章中,我将会从Chrome
浏览器的高层次架构(high-level architecture
)开始说起,一直深入讲到页面渲染流水线(rendering pipeline
)的具体细节。如果你想知道浏览器是怎么把你编写的代码转变成一个可用的网站,或者你不知道为什么一些特定的代码写法可以提高网站的性能的,那你就来对地方了,这篇文章就是为你准备的。
首先我们先了解一些关键的计算机术语以及Chrome
浏览器的多进程架构。
计算机的核心 - CPU和GPU
要想理解浏览器的运行环境,我们先要搞明白一些计算机组件以及它们的作用。
CPU
首先我们要说的是计算机的大脑 - CPU
(Central Processing Unit)。CPU
是计算机里面的一块芯片,上面有一个或者多个核心(core
)。我们可以把CPU
的一个核心(core
)比喻成一个办公室工人,他功能强大,上知天文下知地理,琴棋书画无所不能,它可以串行地一件接着一件处理交给它的任务。很久之前的时候大多数CPU
只有一个核心,不过在现在的硬件设备上CPU
通常会有多个核心,因为多核CPU
可以大大提高手机和电脑的运算能力。如何理解处理器、CPU、多处理器、内核、多核?
图 1:四个CPU
核心愉快地在各自工位上一个接着一个地处理交给它们的任务
GPU
图形处理器 - 或者说GPU
(Graphics Processing Unit)是计算机的另外一个重要组成部分。和功能强大的CPU
核心不一样的是,单个GPU
核心只能处理一些简单的任务,不过它胜在数量多,单片GPU
上会有很多很多的核心可以同时工作,也就是说它的并行计算能力是非常强的。图形处理器(GPU
)顾名思义一开始就是专门用来处理图形的,所以在说到图形使用GPU
(using
)或者GPU
支持(backed
)时,人们就会联想到图形快速渲染或者流畅的用户体验相关的概念。最近几年来,随着GPU
加速概念的流行,在GPU
上单独进行的计算也变得越来越多了。
图 2:每个GPU
核心手里只有一个扳手,这就说明它的能力是非常有限的,可是它们人多啊!
当你在手机或者电脑上打开某个应用程序的时候,背后其实是CPU
和GPU
支撑着这个应用程序的运行。通常来说,你的应用要通过操作系统提供的一些机制才能跑在CPU
和GPU
上面。
图 3:计算机的三层架构,最下层是硬件机器,操作系统夹在中间,最上层是运行的应用
在进程和线程上执行程序
在深入到浏览器的架构之前我们还得了解一下进程(process
)和线程(thread
)的相关概念。进程可以看成正在被执行的应用程序(executing program
)。而线程是跑在进程里面的,一个进程里面可能有一个或者多个线程,这些线程可以执行任何一部分应用程序的代码。
图 4:进程就像一个大鱼缸,而线程就是浴缸里面畅游的鱼儿
当你启动一个应用程序的时候,操作系统会为这个程序创建一个进程同时还为这个进程分配一片私有的内存空间,这片空间会被用来存储所有程序相关的数据和状态。当你关闭这个程序的时候,这个程序对应的进程也会随之消失,进程对应的内存空间也会被操作系统释放掉。
图 5:进程使用系统分配的内存空间去存储应用的数据
有时候为了满足功能的需要,创建的进程会叫系统创建另外一些进程去处理其它任务,不过新建的进程会拥有全新的独立的内存空间而不是和原来的进程共用内存空间。如果这些进程需要通信,它们要通过IPC
机制(Inter Process Communication
)来进行。很多应用程序都会采取这种多进程的方式来工作,因为进程和进程之间是互相独立的它们互不影响,换句话来说,如果其中一个工作进程(worker process
)挂掉了其他进程不会受到影响,而且挂掉的进程还可以重启。
图 6:不同的进程通过IPC
来通信
浏览器架构
那么浏览器是怎么使用进程和线程来工作的呢?其实大概可以分为两种架构,一种是单进程架构,也就是只启动一个进程,这个进程里面有多个线程工作。第二种是多进程架构,浏览器会启动多个进程,每个进程里面有多个线程,不同进程通过IPC
进行通信。
图 7:单进程和多进程浏览器的架构图
上面的图表架构其实包含了浏览器架构的具体实现了,在现实中其实并没有一个大家都遵循的浏览器实现标准,所以不同浏览器的实现方式可能会完全不一样。
为了更好地在本系列文章中展开论述,我们主要讨论最新的Chrome
浏览器架构,它采用的是多进程架构,以下是架构图:
Chrome
浏览器会有一个浏览器进程(browser process
),这个进程会和其他进程一起协作来实现浏览器的功能。对于渲染进程(renderer process
),Chrome
会尽可能为每一个tab
甚至是页面里面的每一个iframe
都分配一个单独的进程。
图 8:Chrome
的多进程架构图,多个渲染进程的卡片(render process
)是用来表明Chrome
会为每一个tab
创建一个渲染进程。
各个进程如何分工合作呢?
以下是各个进程具体负责的工作内容:
图 9:不同的进程负责浏览器不同部分的界面内容
除了上面列出来的进程,Chrome
还有很多其他进程在工作,例如扩展进程(Extension Process
)和工具进程(utility process
)。如果你想看一下你的Chrome
浏览器现在有多少个进程在跑可以点击浏览器右上角的更多按钮,选择更多工具和任务管理器:
在弹出的窗口里面你会看到正在工作的进程列表,以及每个进程使用的CPU
和内存状况。
Chrome多进程架构的好处
那么为什么Chrome
会采取多进程架构工作呢?
其中一个好处是多进程可以使浏览器具有很好的容错性。对于大多数简单的情景来说,Chrome
会为每个tab
单独分配一个属于它们的渲染进程(render process
)。举个例子,假如你有三个tab
,你就会有三个独立的渲染进程。当其中一个tab
的崩溃时,你可以随时关闭这个tab
并且其他tab
不受到影响。可是如果所有的tab
都跑在同一个进程的话,它们就会有连带关系,一个挂全部挂。
图 10:不同的tab
会有不同的渲染进程来负责
Chrome
采用多进程架构的另外一个好处就是可以提供安全性和沙盒性(sanboxing
)。因为操作系统可以提供方法让你限制每个进程拥有的能力,所以浏览器可以让某些进程不具备某些特定的功能。例如,由于tab渲染进程可能会处理来自用户的随机输入,所以Chrome
限制了它们对系统文件随机读写的能力。
不过多进程架构也有它不好的地方,那就是进程的内存消耗。由于每个进程都有各自独立的内存空间,所以它们不能像存在于同一个进程的线程那样共用内存空间,这就造成了一些基础的架构(例如V8 JavaScript
引擎)会在不同进程的内存空间同时存在的问题,这些重复的内容会消耗更多的内存。所以为了节省内存,Chrome
会限制被启动的进程数目,当进程数达到一定的界限后,Chrome
会将访问同一个网站的tab
都放在一个进程里面跑。
节省更多的内存 - Chrome的服务化
同样的优化方法也可以被使用在浏览器进程(browser process
)上面。Chrome
浏览器的架构正在发生一些改变,目的是将和浏览器本身(Chrome
)相关的部分拆分为一个个不同的服务,服务化之后,这些功能既可以放在不同的进程里面运行也可以合并为一个单独的进程运行。
这样做的主要原因是让Chrome
在不同性能的硬件上有不同的表现。当Chrome
运行在一些性能比较好的硬件时,浏览器进程相关的服务会被放在不同的进程运行以提高系统的稳定性。相反如果硬件性能不好,这些服务就会被放在同一个进程里面执行来减少内存的占用。其实在这次架构变化之前,Chrome
在Android
上面已经开始采取类似的做法了。
图 11:Chrome
将浏览器相关的服务放在同一个进程里面运行和放在不同的进程运行的区别
单帧渲染进程 - 网站隔离(Site Isolation)
网站隔离(Site Isolation
)是最近Chrome
浏览器启动的功能,这个功能会为网站内不同站点的iframe
分配一个独立的渲染进程。之前说过Chrome
会为每个tab
分配一个单独的渲染进程,可是如果一个tab
只有一个进程的话不同站点的iframe
都会跑在这个进程里面,这也意味着它们会共享内存,这就有可能会破坏同源策略。同源策略是浏览器最核心的安全模型,它可以禁止网站在未经同意的情况下去获取另外一个站点的数据,因此绕过同源策略是很多安全攻击的主要目的。而进程隔离(proces isolation
)是隔离网站最好最有效的办法了。再加上CPU
存在Meltdown和Spectre的隐患,网站隔离变得势在必行。因此在Chrome 67
版本之后,桌面版的Chrome
会默认开启网站隔离功能,这样每一个跨站点的iframe
都会拥有一个独立的渲染进程。
在
2018
年,Project Zero
团队和其他安全人员相继发现了一个影响世界上绝大多数CPU
的安全漏洞,Spectre
[1]。这个漏洞允许一个进程读取其本不允许读取的内存数据。这也使得浏览器变得不安全,因为在浏览器中,不同网站的不同文档可以在同一进程中运行[2]。恶意网站可能会利用这个漏洞读取用户在其他网站上的信息。为了防止类似的漏洞,
WHATWG
工作组先后新增了Cross-Origin-Resource-Policy
、Cross-Origin-Opener-Policy
和Cross-Origin-Embedder-Policy
3个HTTP
响应首部,服务器使用这些首部告诉浏览器保护站点数据。此外,Chromium
实现了站点隔离(Site Isolation
)机制,保证不同站点独立运行在不同进程中,并阻止敏感数据的传播[3]。该机制从Chrome 67
开始默认启用。相应地,Firefox
也正在测试类似机制(Fission
)[4],Safari
则正在跟进[5]。
图 12:网站隔离功能会让跨站的iframe
拥有独立的进程
网站隔离技术汇聚了我们工程师好几年的研发努力,它其实远远没有想象中那样只是为不同站点的iframe
分配一个独立的渲染进程那么简单,因为它从根本上改变了各个iframe
之间的通信方式。网站隔离后,对于有iframe
的网站,当用户打开右边的devtool
时,Chrome
浏览器其实要做很多幕后工作才能让开发者感觉不出这和之前的有什么区别,这其实是很难实现的。对于一些很简单的功能,例如在devtool
里面用Ctrl + F
键在页面搜索某个关键词,Chrome
都要遍历多个渲染进程去完成。所以我们的浏览器工程师在网站隔离这个功能发布后都感叹这是一个里程碑式的成就。
总结
在本篇文章中,我们探讨了浏览器高层次的架构设计以及多进程架构的带来的好处。同时我们还讨论了服务化和网站隔离这些和浏览器多进程架构息息相关的技术。在下一篇文章中我们要开始深入了解这些进程和线程是如何呈现我们的网站页面的了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。