浏览器架构(梳理、总结)

1,989 阅读10分钟

前言

最近看了些浏览器工作原理相关的资料,受益匪浅。聊起如何优化网站,提升性能和体验,相信很多的前端工程师都能够答出一二,譬如减少网络请求、内容压缩、尽量避免操作 DOM 等等。然而,很多的前端工程师知其然却不知其所以然,甚至在很多场景下不知道如何去优化,譬如为什么 HTTP 1.x 推荐使用雪碧图, 而 HTTP 2.0 就不用。生搬硬套式的使用这些优化手段,有时候会适得其反,甚至连自己为何这样做都不知道,仅仅因为优化公式里面有这么一条,我就用了。在我看来,学习及了解底层知识是很有必要的,技术的东西万变不离其宗,懂的原理,上层的东西一通百通了。

出于“知其然知其所以然”的态度,我将会把我对于浏览器工作原理的相关学习进行梳理、提炼和总结,主要目的在于验证自己的学习成果以及理清脉络。相关的参考内容或者学习资料会在文末给出。本文会从 chrome 的分层架构讲起,了解一下浏览器每一层的职责划分。

导读

本文会从这两个方面讲:

  1. 进程和线程
  2. 每个时期的浏览器分层。

PART1 进程与线程

为什么说起进程和线程呢?这是因为,浏览器的分层架构和进程及线程原理分不开。关于进程和线程的相关知识点,学习过计算机的都应该了解,这里做一个简单的回顾。

进程作为资源分配的最小单位,简单来说,进程就是一个程序的运行实例,当你运行一个程序的时候,系统会为这个程序分配一个单独的内存空间,存放程序代码、运行数据、包括一个主线程,我们把这样一个环境成为进程。

线程,是不可以脱离进程而存在的。线程是 CPU 调度的基本单位,它是比进程更小的能够独立运行的基本单位。一个进程中可以包含 N 多个线程。

1.1 联系及区别

  • 线程不可脱离进程而存在,一个进程可以包含多个线程。
  • 进程拥有独立的内存空间,而线程共享当前进程的内存空间。
  • 一个线程可以创建及销毁另外一个线程,同一个进程中可以存在多个线程并发执行。
  • 进程相互隔离,数据共享相对困难,进程间的通信成为 ICP 通信。而线程可以访问同一进程的数据,因此需要考虑上锁情况。
  • 不同进程间,一个进程的崩溃不会影响其他进程。一个线程的奔溃会导致该进程的奔溃。
  • 进程比线程消耗更多的计算机资源。
  • 进程关闭时,系统会回收整块内存。

PART 2 浏览器分层

简单的回顾了下进程与线程,接下来进入了本文的正菜,浏览器的分层架构。

2.1 青铜时代(单进程浏览器)

早在 2007 年以前,市面上的浏览器几乎都是单进程浏览器。单进程如何理解呢?讲白了就是一个浏览器就一个进程,所有的功能模块都跑在单一一个进程中,包括网络、插件、渲染引擎等。图示如下:

在单进程浏览器中,只有一个进程,内部包含程序的数据及文件,以及网络线程、页面线程等各种线程。其中,页面线程任务繁重,不仅要负责页面的渲染和展示,还包含了 javaScript 的执行以及插件的运行。

那么,这样的简单分层架构会有什么问题呢?

  • 不稳定

    早期的网页可能需要借助各种插件来配合,比如 FLASH。但是插件是很容易出问题的,而插件一旦崩溃,正如前面所说的进程和线程的关系,页面线程的奔溃会导致整个浏览器的奔溃;其次,渲染引擎模块也同样不稳定,比如运行一些 javaScript,程序的出错也会导致线程的奔溃。

  • 不流畅

    正如上文所说,页面线程任务繁重,不仅仅负责页面的渲染工作,就连插件的脏活累活也都要揽下。由于页面线程只是一个单一线程,同一时间内无法运行多个任务,这就会导致,其中任何一个任务运行所需占用时间直接影响其他任务的执行。举个极端的例子,我们写一段死循环:

    while(true){}
    

    这段 javaScript 代码的执行,导致了其他的任务只能等待,并且遥遥无期。可想而知,页面线程任务如此繁重,是非常容易造成卡顿的。

  • 不安全

    单进程浏览器没有沙箱一说,插件是可以使用 C/C++ 等代码编写的,并且可以随意操作系统,如果当前页面运行着插件时,插件完全可以通过操作电脑,进行释放病毒、盗取账号等攻击;其次,页面脚本也可以通过浏览器的漏洞来获取系统权限,之后对我们的电脑进行恶意的攻击。因此,单进程浏览器存在着不安全的因素。

2.2 白银时代(早期多进程浏览器)

既然单进程浏览器带来了这样那样的各种问题,那么,我们就要去解决它、优化它。而从分层架构方面,解决的方案就是采用多进程的方案。2008年发布的 chrome 就采用了多进程的分层架构。如下图:

在早期的多进程浏览器中,我们已经将浏览器进行一定的拆分,一些功能模块从原来的线程级别提升到了进程级别分别管理。我们分别划分为 浏览器主进程多个渲染进程以及多个插件进程等,进程之间采用 IPC 的方式进行通信。

其中,浏览器主进程主要负责用户界面交互、下载资源、管理 IPC 以及页面的展示等功能。

插件进程则分别对应每一个插件程序,单独运行互不干扰。

而渲染进程也是相互独立的,一般来说,每一个 Tab 都有可能对应一个渲染进程,渲染进程上进行着页面的解析渲染工作、以及 javaScript 的执行、展示在页面上的图片的合成等等工作。

那么,这么分层有什么好处呢?

事实上,这就解决了先前单进程浏览器的问题

  • 解决不稳定问题

    多进程浏览器采用多插件进程、多渲染进程,进程之间彼此隔离,互不干扰。这样的结构就意味着,任何一个插件或者页面崩溃了,都仅仅只会影响当前的进程,浏览器的其他进程安然无恙,浏览器更不会有奔溃的风险。

  • 解决不流畅问题

    多进程浏览器的渲染进程彼此都跑着自己的程序,互不干扰。也就是说,假设 A、B 页面分别对应着不同渲染进程,A 页面即便是由于各种原因发生了卡顿,B 页面也不会受到影响。并且,关闭页面的时候,对应渲染进程的关闭,浏览器也会回收内存空间,有效避免了内存泄漏的风险。

  • 解决不安全问题

    正如图中所示,多进程浏览器引入了沙箱(sandbox)的概念,渲染进程和插件进程都运行在沙箱中,被限制了直接读取和写入操作系统的能力,这也避免了程序对操作系统进行恶意的攻击,保障了安全性。

2.3 黄金时代(目前多进程浏览器)

目前,多进程浏览器又有了一些改变,分层更加细了。总体来说,在原来基础上,从浏览器主进程中抽离出了网络进程GPU 进程V8 代理解析工具等。

如上图所示,网络进程和 GPU 进程已经独立出来了。事实上,这一点可以通过打开 chrome 浏览器的**“更多工具 -> 任务管理器”**来进行查看,可以看出,打开一个页面,至少需要 4 个进程。

包含一个浏览器主进程、对应标签页的渲染进程、网络进程以及 GPU 进程。其中:

浏览器进程负责用户交互、页面展示、管理子进程等工作。

渲染进程负责界面的渲染解析工作,以及 javaScript 的执行等。

网络进程负责网络请求、下载工作。

GPU 进程最初则是为了 CSS 3D 效果,后面演化为 UI 界面的绘制也都依赖 GPU 进程来进行。

而多出来的一个 V8 代理解析工具,是因为 pac 代理脚本是可以使用 javaScript 进行编写的,那么,解析的工作就放在了浏览器主进程的 V8 上,但是这部分工作放在浏览器主进程上不太好,后面又把这一块单独提了出来,成为一个进程。

多进程浏览器的缺点

多进程浏览器解决了单进程浏览器的三大问题,带来了流畅性、稳定性以及安全性,但同时,缺点也是非常明显的。

首先,每开一个进程都不得不分配公共基础架构的副本,分配一定的内存空间,导致了内存占用非常大,这一点我们也可以通过任务管理器观察到。

其次,这也带来了架构的复杂性。并且各个模块之间耦合程度比较大,容易导致扩展性差。

2.4 钻石时代(面向服务的架构)

为了解决多进程浏览器遗留下来的问题,chrome 在 2016 年使用 SOA (Services Oriented Architecture,即面向服务的架构) 设计了新一代的 chrome 。

面向服务的基础架构,说的是将原本的各个模块都抽象为服务,每个服务可以运行在单独的进程中,定义好对外的接口,使用 IPC 进行通信,为上层提供服务。

如图示,下层会将浏览器的一些基础功能模块抽象为服务,就好像操作系统一般,为上层去进行服务。这样会达到一种 松耦合、维护性强、可扩展性强的效果。

并且,在计算机资源有限的情况下,浏览器也提供了更加弹性的功能,会灵活的将这些服务整合到一个进程中,节省资源开支。

小结

  • 进程是资源分配的最小单位,而线程是 CPU 调度的基本单位。进程包含线程,线程的崩溃影响当前进程,进程的崩溃不影响其他进程,进程间彼此独立。
  • 进程拥有自己的内存空间,线程共享当前进程的内存空间。
  • 进程间使用 IPC 进行通信。
  • 单线程浏览器不稳定、不流畅、不安全。
  • 多进程浏览器将模块进行分层,独立出进程来,又引入了 sandbox ,解决了不稳定、不流畅以及不安全问题。
  • 面向服务的架构将模块抽象为服务,提供接口,通过 IPC 进行通信,达到了松耦合、高可维护性以及高扩展性的效果。在资源有限的情况下,浏览器会灵活的整合服务到一个进程中,避免资源浪费。

参考资料

Out-of-process V8 proxy resolver

Chrome架构:仅仅打开了1个页面,为什么有4个进程?

在线直通车

本文首发于:说说浏览器的架构