Categories
程式開發

Chrome浏览器架构


背景

这篇文章被收录在我的前端技术系列文章中浏览器工作原理栏目中,做为这个栏目的第一篇文章,先从页面是如何在浏览器中被显示出来这一最常见的问题做为开始,整体介绍浏览器的工作原理。这篇文章取材于Mariko Kosaka“在2018年9月发表在Chrome开发者文档中的Inside look at modern web browser“系列文章,同时结合我自己的理解。如果你对浏览器如何将代码转化成页面,这正适合于你。

CPU与GPU

CPU和GPU作为计算机中最重要的两个计算单元直接决定了计算性能。

CPU

Chrome浏览器架构 1

CPU是计算机的大脑,负责处理各种不同的任务。在过去,大多数CPU是单芯片的,核心被安置在同一个芯片上。更新的CPU可以支持多核心,运算能力大大加强。而最新的的cpu已经达到10核心20线程数的能力了。

GPU

Chrome浏览器架构 2

GPU是另一个计算机的组成部分,与CPU不同,GPU更擅长利用多核心同时处理单一的任务。像命名那样,GPU最初被用于处理图像。这就是为什么使用GPU可以更快、更顺畅的渲染页面内容。随着GPU的发展,越来越多的计算任务也可以使用GPU来处理。甚至有人说GPU是人工智能的大功臣,可见GPU已经不再仅用于图像处理上了。

计算机架构

Chrome浏览器架构 3

我们可以把计算机自下而上分成三层:硬件、操作系统和应用。有了操作系统的存在,上层运行的应用可以使用操作系统提供的能力使用硬件资源而不会直接访问硬件资源。

进程与线程

Chrome浏览器架构 4

一个进程是应用正在运行的程序。而线程是进程中更小的一部分。当应用被启动,进程就被创建出来。程序可以创建线程来帮助其工作。操作系统会为进程分配私有的内存空间以供使用,当关闭程序时,这段私有的内存也会被释放。其实还有比线程更小的存在就是协程,而协成是运行在线程中更小的单位。async/await就是基于协程实现的。

进程间通信(IPC)

Chrome浏览器架构 5

一个进程可以让操作系统开启另一个进程处理不同的任务。当两个进程需要通信时,可以时用IPC(Inter Process Communication)。

多数程序被设计成使用IPC来进行进程间的通信,好处在于当一个进程给另一个进程发消息而没有回应时,并不影响当前的进程继续工作。

浏览器架构

借助进程和线程,浏览器可以被设计成单进程、多线程架构,或者利用IPC实现多进程、多线程架构。

Chrome浏览器架构 6

这里我们以Chrome多进程架构介绍,在Chrome中存在这不同种类型的进程,它们各司其职。

Chrome浏览器架构 7

浏览器进程做为Chrome中最核心的进程管理着Chrome中的其他进程,而Renderer则负责渲染不同的站点。

进程工作内容

Chrome浏览器架构 8

浏览器进程(Browser process)

浏览器进程负责管理Chrome应用本身,包括地址栏、书签、前进和后退按钮。同时也负责可不见的功能,比如网络请求、文件按访问等,也负责其他进程的调度。

渲染进程(Renderer process)

渲染进程负责站点的渲染,其中也包括JavaScript代码的运行,web worker的管理等。

插件进程(Plugin process)

插件进程负责为浏览器提供各种额外的插件功能,例如flash。

GPU进程(GPU process)

GPU进程负责提供成像的功能。

当然还有其他像扩展进程或工具进程等其他进程,可以在Chrome的Task Manager面板中查看,面板中列出了运行的进程和其占用的CPU、内存情况。

多进程架构的好处

当我们访问一个站点时,渲染进程会负责运行站点的代码,渲染站点的页面,同时响应用户的交互动作,当我们在Chrome中打开三个页签同时访问三个站点时,如果其中一个没有响应,我们可以关闭它然后使用其他的页签,这是因为Chrome为每个站点创建一个独立的渲染进程,专门处理当前站点的渲染工作。如果所有的页面运行在同一个进程中,当有一个页面没有响应时,所有的页面就都卡住了。

Chrome浏览器架构 9

另一个好处是,借助操作系统对进程安全的控制,浏览器可以将页面放置在沙箱中,站点的代码可以运行在隔离的环境中,保证核心进程的安全。

虽然多进程的架构优于单进程架构,但由于进程独享自己的私有内存,以渲染进程为例,虽然渲染的站点不同,但工作内容大体相似,为了完成渲染工作它们会在自己的内存中包含相同的功能,例如V8引擎(用于解析和运行Javascript),这意味着这部分相同的功能需要占用每个进程的内存空间。为了节省内存,Chrome限制了最大进程数,最大进程数取决于硬件的能力,同时当使用多个页签访问相同的站点时浏览器不会创建新的渲染进程。

面向服务的架构

Chrome将架构从多进程模型转变成面向服务。浏览器将功能以服务的方式提供,以解决多进程架构中的问题。

当Chrome运行在拥有强大硬件的计算机上时,会将一个服务以多个进程的方式实现,提高稳定性,当计算机硬件资源紧张时,则可以将多个服务放在一个进程中节省资源。

Chrome浏览器架构 10

基于站点隔离的渲染进程

利用iframe我们可以在同一个页面访问不同站点的资源,但从安全的角度考虑,同源策略不允许一个站点在未得到同意的情况下访问其他站点的资源,所以从Chrome 67开始每个站点由独立的渲染进程处理被默认启用。

Chrome浏览器架构 11

浏览器进程

Chrome浏览器架构 12

浏览器进程负责处理除了渲染外的大部分工作,浏览器进程包括几个线程:

UI 线程负责绘制工具栏中的按钮、地址栏等。网络线程负责从网络中获取数据。存储线程负责文件等功能。

当我们在地址栏中输入一个地址时,浏览器进程中的UI线程最先得知这个动作,并开始处理。

一次访问

下面我们就从一次常见的访问入手,逐步了解浏览器是如何展示页面的。

Step 1:输入处理

当我们在地址栏中输入时,UI线程会先判断我们输入的内容是要搜索的内容还是要访问一个站点,因为地址栏同时也是一个搜索框。

Chrome浏览器架构 13

Step 2:访问开始

当我们按下回车开始访问时,UI线程将借助网络线程访问站点资源. 浏览器页签的标题上会出现加载中的图标,同时网络线程会根据适当的网络协议,例如DNS lookup和TLS为这次请求建立连接。

Chrome浏览器架构 14

当服务器返回给浏览器重定向请求时,网络线程会通知UI线程需要重定向,然后会以新的地址做开始请求资源。

Step 3:处理响应数据

Chrome浏览器架构 15

当网络线程收到来自服务器的数据时,会试图从数据中的前面的一些字节中得到数据的类型(Content-Type),以试图了解数据的格式。

当返回的数据类型是HTML时,会将数据传递给渲染进程做进一步的渲染工作。但是如果数据类型是zip文件或者其他文件格式时,会将数据传递给下载管理器做进一步的文件预览或者下载工作。

Chrome浏览器架构 16

在开始渲染之前,网络线程要先检查数据的安全性,这里也是浏览器保证安全的地方。如果返回的数据来自一些恶意的站点,网络线程会显示警告的页面。同时,Cross Origin Read Blocking(CORB)策略也会确保跨域的敏感数据不会被传递给渲染进程。

Step 3:渲染过程

当所有的检查结束后,网络线程确信浏览器可以访问站点时,网络线程通知UI线程数据已经准备好了。UI线程会根据当前的站点找到一个渲染进程完成接下来的渲染工作。

Chrome浏览器架构 17

在第二步,UI线程将请求地址传递给网络线程时,UI线程就已经知道了要访问的站点。此时UI线程就可以开始查找或启动一个渲染进程,这个动作与让网络线程下载数据是同时的。如果网络线程按照预期获取到数据,则渲染进程就已经可以开始渲染了,这个动作减少了从网络线程开始请求数据到渲染进程可以开始渲染页面的时间。当然,如果出现重定向的请求时,提前初始化的渲染进程可能就不会被使用了,但相比正常访问站点的场景,重定向往往是少数,在实际工作中,也需要根据特定的场景给出特定的方案,不必追求完美的方案。

Step 4:提交访问

经历前面的步骤,数据和渲染进程都已经准备好了。浏览器进程会通过IPC向渲染进程提交这次访问,同时也会保证渲染进程可以通过网络线程继续获取数据。一旦浏览器进程收到来自渲染进程的确认完毕的消息,就意味着访问的过程结束了,文档渲染的过程就开始了。

这时,地址栏显示出表明安全的图标,同时显示出站点的信息。访问历史中也会加入当前的站点信息。为了能恢复访问历史信息,当页签或窗口被关闭时,访问历史的信息会被存储在硬盘中。

Chrome浏览器架构 18

Extra Step:加载完毕

当访问被提交给渲染进程,渲染进程会继续加载页面资源并且渲染页面。当渲染进程”结束”渲染工作,会给浏览器进程发送消息,这个消息会在页面中所有子页面(frame)结束加载后发出,也就是onLoad事件触发后发送。当收到”结束”消息后,UI线程会隐藏页签标题上的加载状态图标,表明页面加载完毕。

但这里”结束”并不意味着所有的加载工作都结束了,因为可能还有JavaScript在加载额外的资源或者渲染新的视图。

Chrome浏览器架构 19

访问不同的站点

一次普通的访问到此就结束了。当我们输入另外一个地址时,浏览器进程会重复上面的过程。但是在开始新的访问前,会确认当前的站点是否关心beforeunload事件。

beforeunload事件可以提醒用户是否要访问新的站点或者关闭页签,如果用户拒绝则新的访问或关闭会被阻止。

由于所有的包括渲染、运行Javascript的工作都发生在渲染进程中,浏览器进程需要在新的访问开始前与渲染进程确认当前的站点是否关心unload。

Chrome浏览器架构 20

如果一次访问是从一个渲染进程中发起的,例如用户点击一个链接或者运行JavaScript代码location = ‘http://newsite.com’时,渲染进程首先检查beforeunload。然后再执行和浏览器进程初始化访问同样的步骤,只不过区别在于这样的访问请求是由渲染进程向浏览器进程发起的。

当新的站点请求被创建时,一个独立的渲染进程将被用于处理这个请求。为了支持像unload的事件触发,老的渲染进程需要保持住当前的状态。更详细的生命周期介绍可以参考Page lifecycle“。

Chrome浏览器架构 21

Service worker

Service worker是一种可以web开发者控制缓存的技术。如果Service worker被实现成从本地存储获取数据时,那么原本的请求就不会被浏览器发送给服务器了。

值得注意的是,Service worker中的代码是运行在渲染进程中的。当访问开始时,网络线程会根据域名检查是否有Service worker会处理当前地址的请求,如果有,则UI线程会找到对应的渲染进程去执行Service worker的代码,而Service worker可以让开发者决定这个请求是从本地存储还是从网络中获取数据。

Chrome浏览器架构 22

Chrome浏览器架构 23

访问预加载

如果Service worker最终决定要从网络中获取数据时,我们会发现这种跨进程的通信会造成一些延迟。Navigation Preload“是一种可以在Service worker启动的同时加载资源的优化机制。借助特殊的请求头,服务器可以决定返回什么样的内容给浏览器。

Chrome浏览器架构 24

渲染进程负责页面的内容

渲染进程负责所有发生在浏览器页签中的事情。在一个渲染进程中,主线程负责解析,编译或运行代码等工作,当我们使用Worker时,Worker线程会负责运行一部分代码。合成线程和光栅线程是也是运行在渲染进程中的,负责更高效和顺畅的渲染页面。

渲染进程最重要的工作就是将HTML、CSS和Javascript代码转换成一个可以与用户产生交互的页面。

Chrome浏览器架构 25

解析过程

下面的章节主要介绍渲染进程如何将从网络线程中获取的文本转化成图像的过程。

DOM的创建

当渲染进程接收到来自浏览器进程提交访问的消息后就开始接受HTML数据,主线程开始解析HTML文本字符串,并且将其转化成Document Object Model(DOM)。

DOM是一种浏览器内部用于表达页面结构的数据,同时也为Web开发者提供了操作页面元素的接口,让web开发者可以在Javascript代码中获取和操作页面中的元素。

将HTML文本转化成DOM的标准被HTML Standard“定义。我们会发现在转化过程中浏览器从来不会抛出异常,类似关闭标签的丢失,开始、关闭标签匹配错误等等。这是因为HTML标准中定义了要静默的处理这些错误,如果对此感兴趣可以阅读An introduction to error handling and strange cases in the parser“。

额外资源的加载

一个网站通常还会使用类似图片,样式文件和JavaScript代码等额外的资源。这些资源也需要从网络或缓存中获取。主线程在转化HTML的过程中理应挨个加载它们,但是为了提高效率,预加载扫描(Preload Scanner)与转换过程会同时运行着。当预加载扫描在分析器分析HTML过程中发现了类似img或link这样的标签时,就会发送请求给浏览器进程的网络线程,而主线程会根据这些额外资源是否会阻塞转化过程而决定是否等待资源加载完毕。

Chrome浏览器架构 26

JavaScript会阻塞转化过程

当HTML分析器发现