这是我们理解浏览器工作原理的系列文章的第一篇,本篇内容我们会从顶层简单介绍下浏览器 的进程架构,理解它能够为我们后续更深入探索浏览器工作原理打下坚实的基础。
chrome 浏览器架构
如果你是一个前端开发人员,可能你或多或少的会听说过浏览器中的 main thread
。前端人员写下的绝大多数代码都运行在这个 主线程
之中,
你肯定也听说过前端 js 的执行以及页面的渲染都是在主线程这个单一线程上执行的,那么浏览器到底是如何工作的,浏览器中是否所有的操作都是运行在
这个单一主线程之上的呢?
如果简单回答上述问题,那答案一定是否定的。
话不多说,我们直接先上一个进程架构的图。
通过以上的浏览器架构图,我们可以得出如下信息:
软件、操作系统、硬件三层架构
本架构图首先自上而下分为三个层次
- 最上层为软件层(或者叫做应用程序层): 我们的前端程序(主要运行在渲染进程中的 main thread 之中)和浏览器本身一起运行在这个层次
- 中间层为操作系统层: 操作系统作为应用程序和硬件层次的中间层,其为我们的应用程序提供操作底层硬件的接口
- 最下面的一层为硬件层: 本身硬件层可能有很多东西,但是这里面最核心的是计算的部分,通常包含 CPU(通用处理单元) 和 GPU(专用处理单元: 在处理某些简单计算任务上异常高效)
浏览器中主要进程的职责
Browser Process
- UI Thread: 控制 chrome 浏览器部分的 ui 展现(比如: 地址栏、书签、前进后退按钮等等)
- Network Thread: 处理底层的网络请求,渲染进程中的请求最终都要交给 Network Thread 来处理,可以理解为每个请求都会让浏览器进程启动一个新的 Network Thread 来具体处理请求。请求不会无限制的增长,因此并发能力是受到限制的,通常同一个域名最多只能 6 个并发。
- Storage Thread: 比如处理本地文件系统访问等等存储相关的事项。
- 事实上,浏览器进程中不止这么多种类的线程,我们只在这里列举出来了常用的线程。
- 当硬件条件(比如内存充足)允许的情况下,某些线程可能会运行在独立的进程中,比如 Network Thread 可能运行在独立的 Network Process 之中。
Renderer Process
Renderer Process(渲染进程)是我们的 web 应用程序具体运行的进程,在 chrome 中每个 tab 都会启动一个渲染进程。一个进程(tab)的崩溃不会影响其他进程(tab)。
- Main Thread: 我们前端开发人员最熟悉的线程,我们写下的绝大多数代码都运行在这个线程之上。
- Worker Threads: 如果我们的应用程序使用了 web worker 或者 service worker。那么就会有这些线程。
- Compositor Thread: 主线程会将渲染出来的层次和绘制顺序信息给到 Compositor Thread。Compositor Thread 将这些层切分成更小的块,然后将这些可绘制块交付给 Raster Thread, Raster Thread 将这些小块一一栅格化后存储到 GPU 内存里,最后 Compositor Thread 再拿到这些信息,将其合并成一个 compositor frame。这个 compositor frame 最后再通过 Browser Process 转交给 GPU 去显示到屏幕上。
- Raster Thread: 将 Compositor Thread 切分的小块使用 GPU 进行处理一一栅格化。
Compositor Thread 和 Raster Thread 的主要作用是为了提高渲染的性能。
总结
- 尽管我们前端代码主要运行在主线程上,但是通常用户的输入的直接接受者并不是渲染进程的主线程。而是浏览器进程。
- 比如:当我们在地址栏中输入 url,这个用户输入的接受者与开始的很多处理都是浏览器进程在做,浏览器进程相当于一个核心的调度者
- 再比如: 当用户在操作我们的应用程序时,比如进行了一次点击操作,这个操作的直接接受者也还是浏览器进程,是浏览器进程把这个用户输入触发给我们的应用程序。
- 浏览器进程(Browser Process)和渲染进程(Renderer Process)的协调工作确保了一个应用程序的高效运行。
用户的一次输入后,到底发生了什么
让我们以用户在地址栏输入 "https://www.example.com" 为例,详细描述一下这个过程中浏览器各个部分是如何协同工作的:
- 用户输入阶段
- 用户在浏览器的地址栏中输入 URL,这个输入事件首先被 Browser Process 的 UI Thread 捕获
- UI Thread 会实时处理用户的输入,比如显示 URL 建议、自动补全等功能
- 初始化导航阶段
- 当用户按下回车键,UI Thread 会启动一个新的导航流程
- UI Thread 将控制权移交给 Network Thread,开始处理 URL
- 网络请求阶段
- Network Thread 首先检查本地缓存是否有响应资源
- 如果需要从网络获取,Network Thread 会进行 DNS 查询,建立 TLS 连接(如果是 HTTPS)
- Network Thread 发起实际的 HTTP 请求
- 响应处理阶段
- Network Thread 接收到响应后,首先检查响应的 Content-Type 等信息,确定如何处理这个响应
- 如果是 HTML 响应,Network Thread 会将控制权交还给 UI Thread,表明需要渲染页面
- 如果是下载文件,则会将控制权交给下载管理器
- 渲染进程初始化阶段
- UI Thread 确定需要渲染页面后,会启动一个新的 Renderer Process
- 这时 Browser Process 会给这个新的渲染进程分配一个沙箱环境,确保安全性
- 导航确认阶段
- 一旦 Renderer Process 准备就绪,Browser Process 通过 IPC(进程间通信)将数据传输给 Renderer Process
- 此时 Browser Process 会更新浏览器 UI,比如:
- 更新地址栏
- 更新导航历史
- 更新前进/后退按钮状态
- 显示加载进度指示器
- 渲染阶段(在 Renderer Process 的 Main Thread 中进行)
- Main Thread 开始解析 HTML
- 构建 DOM 树
- 执行 JavaScript
- 构建 CSSOM
- 进行布局计算 (layout)
- 生成图层树 (paint to layer tree)
- 合成和显示阶段
- Compositor Thread 接收到主线程的图层信息
- 将图层分成小块(tiles)
- Raster Thread 将这些小块栅格化到 GPU 内存中
- Compositor Thread 生成合成帧(Compositor Frame)
- 通过 Browser Process 将合成帧发送给 GPU 进行显示
这整个过程展示了浏览器的多进程架构如何协同工作:
- Browser Process 作为总指挥,负责协调整个导航过程
- Network Thread 处理所有网络相关的操作
- Renderer Process 负责页面的实际渲染
- GPU 进程负责最终的显示(我们未在上图展示)
这种架构设计不仅保证了浏览器的稳定性(一个标签页崩溃不会影响其他标签),还通过任务分离提高了性能和安全性。
- 这其中的 7 & 8 两个步骤是和我们的应用程序性能直接相关的,而且步骤 7 是在我们的主线程这一个单线程中工作的, 所以这也是为什么我们常说 js 的执行以及页面的渲染都是在主线程这一个单线程中执行的。
- 步骤 7 & 8 整体在一起,我们通常称之为 render pipeline 或者 pixel pipeline 我们的前端性能优化,核心就是将这个 pipeline 给优化好