浏览器架构 -- 浏览器系列(1)

1,378 阅读7分钟

一些概念

在正式开始介绍浏览器架构之前,我们先看看后续会用到的一些概念。

CPU vs GPU

CPU全称是Cental Processing Unit,即中央处理器。是电脑的核心部件,它承担了所有的计算任务。并且一个CPU同时只能处理一件事情,而现在的电脑大多采用多核CPU,这就意味着电脑能同时进行多个任务,具有更强的计算性能。 CPU

GPU全称是Graphics Processing Unit,即图像处理器。顾名思义,它主要用于处理与图像相关的简单但量大的任务。如图形的三维变换,光照计算等等。 GPU

CPU虽然大多采用多核架构,但总数没有超过两位数,每个核都有足够大的缓存和足够多的数字和逻辑运算单元,并辅助有很多加速分支判断甚至更复杂的逻辑判断的硬件。GPU的核数远超CPU,被称为众核(多达数百个)。每个核拥有的缓存大小相对小,数字逻辑运算单元也少而简单。

结果上,CPU更擅长处理具有复杂计算步骤和复杂数据依赖的计算任务,而GPU更擅长对海量数据进行相同的操作。

我们可以把CPU看成一个老教授,高数微积分丝毫不在话下。而GPU则是只会加减乘除的小学生。如果只是计算几万次一百以内的加减乘除,自然是一群小学生来的更快,单纯的体力活。但处理复杂任务还得老教授出马才行。

进程 vs 线程

前面我们提到CPU同一时间段只能处理一件事情,那么进程和线程是什么呢?进程和线程都是对CPU时间段的描述。教科书上的解释大概是:“进程是资源分配的最小单位,线程是CPU调度的最小单位”。

我们可以用一个简单的比喻帮助理解:

  1. CPU是一座工厂,它有很多的车间(进程)但是由于电力有限,所以一个车间在工作时,其他车间都得停工(CPU同一时间段只能处理一件事情
  2. 一个车间里面有很多的工人(线程),他们协同一起完成任务(一个进程可以包含多个线程
  3. 车间里的所有资源都是被工人们共享的(一个进程的内存空间是共享的,每个线程多可以使用这些共享内存
  4. 由于车间的资源是有限的,比如只有一个榔头,当有工人使用了该榔头,其他人只能等到他用完才能使用(资源互斥process & thread 进程和线程的关系有以下几个特点:
  • 线程依附于进程,进程中使用多线程并行处理可以提升运算效率
  • 进程中任一线程出错,会导致整个进程奔溃
  • 当进程关闭后,操作系统会回收进程所占用的内存
  • 进程之间相互隔离,互不干扰

多进程时代

如今的chrome采用多进程架构,进程与进程之间使用IPC进行通信:

multi process architecture

进程功能
浏览器主进程(Browser)负责浏览器界面显示,用户交互,子进程管理等功能
GPU进程负责图形绘制以及3D CSS效果
插件进程负责插件的运行
渲染进程控制tab内页面的显示,核心任务是将HTML,CSS和Javascript转换为可交互的网页
工具进程包括多种工具进程,如Network进程,Audio进程等等

以上只列举了常见的进程,浏览器中的进程还远不于此,可通过chrome的任务管理器查看浏览器当前具体运行了哪些进程。

多进程架构使得浏览器更加稳定流畅;由于进程是相互隔离的,所以一个页面或者插件崩溃只会影响当前页面进程和插件进程,并不会影响到其他页面。并且还能使用安全沙箱隔离程序,chrome将插件进程和渲染进程锁在沙箱里,如此插件进程和渲染进程中的恶意程序就无法获取系统权限访问系统资源。

凡事有利就有弊,多进程架构在带来稳定性,流畅性和安全性的同时,也带来了更高的资源占用和更复杂的体系架构。

面向服务的架构

chrome在2016年提出了“面向服务的架构(Services Oriented Architecture)”,chrome架构正逐步向这个方向发展。原本的各种模块会被重构为独立的服务,每个服务都可以在独立进程中运行,也可以整合在同一个进程中一起运行。

chrome最终要把UI,数据库,文件,设备,网络等模块重构为基础服务,架构图如下:

SOA 当chrome运行在性能强大的设备上时,会将各个服务拆分到不同的进程中运行以获得更稳定流畅的效果;当设备性能不够,则把多个服务整合运行在一个进程上,减少内存的开销。

当打开一个页面时,会有几个进程产生?

当我们只打开一个网页(如B站)时,打开任务管理器查看:

task manager 可以看到chrome启动了4个进程,因为浏览器启动本身会启动主进程(Browser)和GPU进程,而打开一个页面还需要发送网络请求获取数据,所以还需要一个网络进程;页面本身渲染需要一个渲染进程。如果页面还需要播放音频功能,还会有其他的音频进程(Audio)等等;如果chrome装有其他插件,还会运行相应的插件进程。

我们重点看看chrome的渲染进程分配原则,从版本67开始,chrome默认采用的策略是process per site instance。默认情况下,每个标签页对应一个渲染进程。如果:

  • 当前父页面包含iframe,如果iframe和父页面属于同一站点,则iframe复用父页面的渲染进程;如果还存在其他属于同一站点的iframe,则复用其他同站点iframe的渲染进程;否则为该iframe单独分配一个新的渲染进程
  • 通过当前父页面打开另一个子页面,并且父子页面属于同一站点,则子页面复用父页面的渲染进程,否则为该子页面单独分配一个新的渲染进程

同一站点为根域名和协议相同的站点,包括该根域名下所有子域名和端口。与同源策略相似,而同源策略限制了协议,域名和端口都必须相同。同一站点策略比同源策略更宽松。

http://mail.google.comhttp://docs.google.comhttp://example.google.com:8080都属于同一站点,因为它们协议都是http,根域名都是google.com

site isolation 假设一个页面的url为https://localhost:3000,该页面内嵌两个iframe,一个指向B站,一个指向百度,那么最终这一个标签页会产生3个渲染进程:

<!-- ...... --> 
<body>
    <iframe src="https://www.bilibili.com/" frameborder="1"></iframe>
    <iframe src="https://www.baidu.com/" frameborder="1"></iframe>
</body>
<!-- ...... -->

iframes

假设一个页面的url为https://localhost:3000,该页面有两个a标签,一个指向B站,一个指向相同站点的另一个地址,分别点击两个a标签后:

<body>
    <a href="https://www.bilibili.com/" target="_blank">jump to bilibili</a>
    <a href="http://localhost:3006/a.html" target="_blank">jump to a page</a>
</body>

same site

可以看到chrome为b站新建了一个渲染进程,而a.html则复用父页面的渲染进程,所以它们俩进程ID一致。

通过父页面打开子页面,即使父子页面属于同一站点,子页面也不一定就会复用父页面的渲染进程,如a标签可以设置rel="noopener"属性,强制打开的子页面使用新的渲染进程。

如果对本文有什么意见和建议,欢迎讨论和指正!