春花秋月何时了 浏览器进程知多少?

3,304 阅读12分钟

23/8/10更新
23/11/17更新
内容稍微有点多,缝缝补补又三年

进程概念

CPU、进程、线程之间的关系

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
  • 单线程多线程,都是指在一个进程内的单和多

进程/线程特点

  1. 进程中的任意一线程执行出错,都会导致整个进程的崩溃
  2. 线程之间共享进程中的数据
  3. 当一个进程关闭之后,操作系统会回收进程所占用的内存
    当一个进程退出时,操作系统会回收该进程所申请的所有资源;即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收。
  4. 进程之间的内容相互隔离
    每一个进程只能访问自己的数据,也就避免出现进程A写入数据到进程B的情况。所以一个进程如果崩溃了是不会影响到其他进程的。如果进程之间需要进行数据的通信,需要使用用于进程间通信(IPC)的机制。

单进程浏览器

单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JavaScript运行环境、渲染引擎和页面等。2007年之前,市面上浏览器基本都是单进程的。

问题

  1. 不稳定
    早期浏览器需要借助于插件来实现诸如Web视频、Web游戏等各种强大的功能,但是插件是最容易出问题的模块,并且还运行在浏览器进程之中,所以一个插件的意外崩溃会引起整个浏览器的崩溃

    除了插件之外,渲染引擎模块也是不稳定的,通常一些复杂的JavaScript代码就有可能引起渲染引擎模块的崩溃。和插件一样,渲染引擎的崩溃也会导致整个浏览器的崩溃。
  2. 性能差
    所有页面的渲染模块、JavaScript执行环境以及插件都是运行在同一个线程中的,这就意味着同一时刻只能有一个模块可以执行,会卡顿
  3. 不安全
    插件可以使用C/C++等代码编写,通过插件可以获取到操作系统的任意资源,当你在页面运行一个插件时也就意味着这个插件能完全操作你的电脑。如果是个恶意插件,那么它就可以释放病毒、窃取你的账号密码,引发安全性问题。

    至于页面脚本,它可以通过浏览器的漏洞来获取系统权限,这些脚本获取系统权限之后也可以对你的电脑做一些恶意的事情,同样也会引发安全问题。

多进程浏览器

多进程浏览器(chrome为例)

image.png

  • 浏览器是多进程的
  • 每一个Tab页,就是一个独立的进程
  • 进程之间通过IPC通信

如何解决单进程问题的?

  • 不稳定:
    由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面
  • 安全问题:
    采用多进程架构的额外好处是可以使用安全沙箱,你可以把沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据
  • 性能:
    渲染进程间互不影响

image.png

浏览器进程

  • 浏览器主进程
    • 具有访问系统资源的权限
    • 协调控制其他子进程(创建、销毁)
    • 浏览器界面显示,用户交互,前进、后退、收藏
    • 将渲染进程得到的内存中的Bitmap,绘制到用户界面上,它才是最终绘制的进程
    • 存储功能等
  • 第三方插件进程
    • 每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU进程
    • 用于3D绘制等
  • 渲染进程,就是我们说的浏览器内核
    • 排版引擎Blink和JavaScript引擎V8都是运行在该进程中,将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,
    • 将 HTML、CSS 和 JavaScript 转换为可视化的页面内容,处理布局计算、样式计算、绘制计算等任务,但并不进行真正的绘制操作
    • 每个tab页一个渲染进程
    • 出于安全考虑,渲染进程都是运行在沙箱模式下
  • 网络进程
    • 负责页面的网络资源加载、下载,之前作为一个模块运行在浏览器主进程里面,最近才独立成为一个单独的进程
  • 备用渲染程序进程
    • 渲染进程池里提前准备的备用渲染进程,新开一个tab页的时候直接使用,减少加载时长

我们来看一下实际情况:

查看管理任务:\color{#33CC99}{查看管理任务:}浏览器右上角空白处 -> 任务管理器

image.png

如图所示,Chrome浏览器有多个进程(目前打开两个页签)

1. 浏览器主进程
2. GPU进程
3. Network Service进程(Util)
4. Storage Serveice进程(Util)
4. Audio Service进程(Util)
5. Tracing Service进程
6. 备用渲染程序进程
7. 渲染进程1
8. 渲染进程2

这时候呢,如果你再打开一个tab,就会再增加一个渲染进程(也有例外,下面会说到)
这并不是说浏览器只有上述进程,还可能有其他进程,如插件进程(扩展进程)

大家可以尝试关掉这些进程,看看在这种情况下,网页会出现什么情况。
剧透:有的会黑屏一下好起来,有的会直接崩溃

总结

  • 进程分为常驻进程(主进程、GPU进程、工具进程(Network等))、可弹性进程(渲染进程等)
  • 预留“备用渲染进程”,新页面启动时直接使用
  • 新启动一个Tab页会可能会增加一个渲染进程和工具进程
  • 结束必须的工具进程会立马重新启动新的工具进程
  • 结束GPU进程,应用黑屏后立刻重新启动新的GPU进程
  • 结束浏览器进程,整个应用退出
趟坑小知识
如上所述,AB会共用进程。但也有例外o(╥﹏╥)o
在浏览器地址栏中直接输入地址打开的页面,即使是同一站点,也不会共用进程
课堂小知识
同一网页内的iframe,每个iframe都会有自己的渲染进程
课堂小知识
假设现在需要显示一张图片,那么首先是网络进程向服务器发起资源请求,随后获取的资源会通过IPC将其提交给渲染进程。然后渲染进程会对这些资源进行解析、绘制等操作,最终生成一幅图片。但是渲染进程并不负责将图片显示到界面上,而是将最终生成的图片提交给浏览器主进程,由浏览器主进程负责显示这张图片。

共享渲染进程

之前谈到了,大部分时候,新开一个TAB页,就会新建一个渲染进程,但也有例外。
有这么一种说法:从页面A打开了页面B,但A、B属于同一站点时,B会复用A的渲染进程,即A、B共用渲染进程
但我自己尝试了一下,发现有时候并没有这样,有时候打开B的时候还是多了一个进程,那什么时候才会共享进程呢?

通常,我们可以通过两个方式在A网页打开B

  • A里面拥有a标签:打开B
  • A执行JS:window.open("B.com/")

这样,A和B之间也就建立了连接,也就对B来说,它的 window.opener 是A。我们会把A和B这一类具有相互连接关系的标签页称为浏览上下文组

但我们可以在打开网页的时候,通过设置rel="noopener"来主动隐藏opener,这时候前后连接就没有了,A和B也就不在浏览上下文组中,它俩就不能共用渲染进程了

那回答上面的问题

在浏览上下文组中,属于同一站点的网页会共享同一渲染进程。iframe据说也是这样,但没有验证过

另外,以上所述的是否共用进程,是取决于Chrome浏览器的多进程模式的,我们讨论的是默认模式。不同进程模式下,会有不同的处理。可以参考官方文档

渲染进程

渲染进程内有多个线程:

  • GUI渲染线程
    • 负责DOM树、布局树、分层树的解析构建
    • 负责生成绘制列表
    • 页面需要重绘和回流时,该线程就会执行
    • 与js引擎线程互斥,防止渲染结果不可预期
  • JS引擎线程
    • 负责处理解析和执行javascript脚本程序
    • 只有一个JS引擎线程(单线程)
    • 与GUI渲染线程互斥,防止渲染结果不可预期
  • 事件触发线程
    • 用来控制事件循环(鼠标点击、setTimeout、ajax等)
    • 当处理一些不能立即执行的代码时,会将对应的任务在其可以触发的时机,添加到事件队列的末端
    • 事件循环机制会在JS引擎线程空闲时,循环访问事件队列的头部,如果有函数,则会将该函数推到执行栈中并立即执行
  • 定时触发器线程
    • setInterval与setTimeout所在的线程
    • 定时任务并不是由JS引擎计时的,是由定时触发线程来计时的
    • 计时完毕后,将回调事件放入到事件队列中
  • 合成线程
    • 负责将渲染线程生成的绘制列表转化成位图,并送往显示器/浏览器主进程进行显示

异步

这里需要理解下事件触发线程定时触发器线程的作用 我们这里有一个事件 image.png

我们知道,不管是setTimeout/setIntervalXHR/fetch代码,在这些代码执行时, 本身是同步任务,而其中的回调函数才是异步任务。

当代码执行到setTimeout/setInterval时,实际上是JS引擎线程通知定时触发器线程,间隔一个时间后,会触发一个回调事件, 而定时触发器线程在接收到这个消息后,会在等待的时间后,将回调事件放入到由事件触发线程所管理的事件队列中。

当代码执行到XHR/fetch时,实际上是JS引擎线程通知异步http请求线程,发送一个网络请求,并制定请求完成后的回调事件, 而异步http请求线程在接收到这个消息后,会在请求成功后,将回调事件放入到由事件触发线程所管理的事件队列中。

当我们的同步任务执行完,JS引擎线程会询问事件触发线程,在事件队列中是否有待执行的回调函数,如果有就会加入到执行栈中交给JS引擎线程执行

渲染过程

网页渲染的相关介绍,我再另一篇里会详细介绍

  1. 解析HTML构建DOM树
  2. 解析css生成cssom
  3. 结合DOM和CSSOM生成渲染树
  4. 布局阶段(位置计算)
  5. 分层
  6. 生成图层绘制列表
  7. 栅格化操作
  8. 合成与显示
课堂小知识
在控制台输入document可看到完整DOM树
输入document.styleSheets,可以看到样式结构
面试小知识
HTML中出现JS文件的引用,会DOM树的构建吗?
会。后面章节会讲到
面试小知识
HTML中出现CSS文件的引用,会阻塞DOM树的构建吗?
不会。因为HTML转为DOM树的过程,发现文件请求会交给网络进程去请求文件,渲染进程会继续解析HTML
面试小知识
HTML中出现CSS文件的引用,会阻塞页面的渲染吗?
会。因为页面渲染需要CSS样式

宏任务、微任务

宏任务

可以将每次执行栈执行的代码当做是一个宏任务

  • I/O
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
微任务

宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完

  • process.nextTick
  • MutationObserver
  • Promise.then catch finally
整体流程
  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

预告

下一章讲从输入URL到网页渲染发生了什么