浏览器工作原理学习笔记 - 浏览器整体概览

简介: 浏览器工作原理学习笔记 - 浏览器整体概览

浏览器的多进程架构

可以通过 Chrome 浏览器中的 选项->更多工具->任务管理器,打开 Chrome 的任务管理器窗口,来查看 Chrome 打开一个页面,需要启动多少进程:

可以看到,在 Chrome (版本 101.0.4951.67(正式版本))中只打开了一个标签页,启动了 6 个进程。

进程和线程

在计算机中,并行处理就是同一时刻处理多个任务,如果使用单线程处理的任务采取多线程方式,通过使用并行处理能大大提升性能

多线程可以处理并行任务,但是线程不能单独存在,线程是由进程来启动和管理的,进程是由系统来启动和管理的

一个进程就是一个程序的运行实例。在启动程序时,操作系统会为程序创建一块内存,用于存放代码、运行过程中产生的数据和一个执行任务的主线程,把这样的一个运行环境称为进程

可以看出,线程是依附于进程的,通过在进程中使用多线程并行处理能提升处理效率

进程和线程之间的关系有以下特点:

  1. 进程中的任一线程执行出错,会导致这个进程的崩溃
  2. 线程之间共享进程的内存,所以线程之间的数据共享
  3. 当一个进程关闭之后,操作系统会回收进程占用的内存

    • 当一个进程退出时,操作系统会回收该进程所申请的所有资源
    • 即使由于其中线程操作不当,导致内存泄漏,当进程退出时,这些内存也会被正确回收
  4. 进程之间的内容相互隔离

    • 进程隔离是为保护操作系统中的数据安全,防止数据被其他进程获取
    • 如果进程之间需要进行数据通信,需要使用进程间通信(IPC)的机制

单进程浏览器时代

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

将很多功能模块都运行在一个进程中,导致了单进程浏览器不稳定、不流畅和不安全:

  • 不稳定

    • 早期浏览器要借助插件实现 Web 视频等功能,但是插件式极其容易出问题的模块,因为其运行在浏览器进程中,所以一个插件的意外崩溃会导致整个浏览器进程崩溃
    • 渲染引擎模块也是不稳定的,一些复杂的 JavaScript 代码就有可能引起渲染引擎模块的崩溃,同样,也会导致整个浏览器进程崩溃
  • 不流畅

    • 所有页面的渲染模块、JavaScript 执行环境和插件都是运行在同一个线程中,这意味着同一时刻只能有一个模块可以执行,当某一模块独占线程后,其他页面就没有机会执行,会导致整个浏览器失去响应,变卡顿
    • 页面的内存泄露也会导致单进程浏览器变卡顿,通常运行完复杂页面,页面关闭后可能有内存不能完全回收的情况,这会导致使用时间越长,内存占用越高,浏览器越慢
  • 不安全

    • 插件可以使用 C/C++ 等代码编写,通过插件可以获取操作系统的任意资源,当页面运行一个插件时插件完全可以操作系统,引发安全性问题
    • 页面脚本也可以通过浏览器的漏洞获取系统权限

多进程浏览器

早期多进程架构

  • 进程隔离解决不稳定问题

    • 由于进程相互隔离,当一个页面或者插件崩溃时,影响的只是当前的页面进程或者插件进程,不会影响浏览器和其他页面
  • 按页面划分进程,解决不流畅和内存泄露问题

    • JavaScript 运行在渲染进程中,即使 JavaScript 阻塞了渲染进程,影响的也只是当前的渲染页面,不会影响浏览器和其他页面
    • 当关闭一个页面时,对应的整个渲染进程也会被关闭,所占资源会被回收,解决了浏览器页面的内存泄露问题
  • 使用安全沙箱解决安全问题

    • 安全沙箱是一种提供给 Web 浏览器的安全机制,它可以防止恶意代码被运行

当前的多进程架构

  • 浏览器进程

    • 界面显示
    • 用户交互
    • 子进程管理
    • 存储等
  • 渲染进程

    • 将 HTML、CSS、JavaScript 转换为用户可以与之交互的网页
    • 排版引擎 Blink 和 JavaScript 引擎 V8 都运行在渲染进程中
    • 默认情况下 Chrome 会为每个 Tab 页创建一个渲染进程
    • 处于安全考虑,渲染进程都是运行在沙箱模式下
  • GPU 进程

    • GPU 使用的初衷是为了实现 3D CSS 的效果,不过后来网页、Chrome 的 UI 界面都选择采用 GPU 绘制,这使得 GPU 成了浏览器普遍的需求
  • 网络进程

    • 负责页面的网络资源加载
  • 插件进程

    • 负责插件运行,因为插件容易崩溃,所以需要通过插件进程来隔离,保证插件崩溃不会影响浏览器和其他页面

虽然多进程模型提升了浏览器的稳定性、流畅性和安全性,但是它也带来了一些问题:

  • 更高的资源占用

    • 每个进程都会包含公共基础结构的副本(如 JavaScript 运行环境),会消耗更多的内存资源
  • 更复杂的体系架构

    • 各模块之间耦合性高,扩展性差等

对于上面这两个问题,Chrome 团队一直在寻求一种弹性方案,既可以解决资源占用高的问题,也可以解决复杂的体系架构的问题。

未来面向服务的架构

在 2016 年,Chrome 官方团队使用“ 面向服务的架构”(Services Oriented Architecture,简称SOA)的思想设计了新的 Chrome 架构。即 Chrome 整体架构会朝向现代操作系统所采用的“面向服务的架构” 方向发展,原来的各种模块会被重构成独立的服务(Service),每个服务(Service)都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而 构建一个更内聚、松耦合、易于维护和扩展的系统,更好实现 Chrome 简单、稳定、高速、安全的目标。

同时 Chrome 还提供灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,但是如果在资源受限的设备上,Chrome 会将很多服务整合到一个进程中,从而节省内存占用。

目前 Chrome 正处在老的架构向服务化架构过渡阶段,这将是一个漫长的迭代过程。

Web 中的 TCP/IP

互联网是一套理念和协议组成的体系架构。其中,协议是一套众所周知的规则和标准,如果各方都同意使用,那么它们之间的通信将变得毫无障碍。

互联网中的数据是通过数据包来传输的,如果发送的数据很大,那么该数据就会被拆分为很多小数据包来传输。

IP:把数据包送达目的主机

数据包要在互联网上进行传输,就要符合网际协议(Internet Protocol,简称IP)标准。

计算机的地址就称为 IP 地址,访问任何网站实际上只是你的计算机向另外一台计算机请求信息。

可以将网络简单分为三层结构:

数据包从 主机 A 到 主机 B 的传输过程如下:

  1. 业务层 将含有 “Cellinlab” 的数据包交给 网络层
  2. 网络层 再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给 物理层

    • IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标 IP 地址、生存时间等信息
  3. 物理层 通过物理网络将数据包传送给 主机 B
  4. 数据包被传输到 主机 B 的 网络层,主机 B 拆开数据包的 IP 头信息,并将拆开的数据部分交给 业务层
  5. 最终,含有 “Cellinlab” 的数据包到达 主机 B 的 业务层

UDP:把数据包送达应用程序

IP 是非常底层的协议,只负责把数据包传送到对方电脑,但是对方电脑并不知道把数据包交给哪个程序?因此,需要基于 IP 之上开发能和应用打交道的协议,最常见的是“用户数据包协议(User Datagram Protocol)”,简称 UDP

将前面的三层结构进行拆分,在业务层和网络层之间加上传输层:

重新梳理下 数据包从 主机 A 到 主机 B 的传输过程:

  1. 业务层 将含有 “Cellinlab” 的数据包交给 传输层
  2. 传输层 在数据包前面加上 UDP 头,组成新的 UDP 数据包,再将新的 UDP 数据包交给 网络层

    • UDP 头中除了目的端口,还有源端口号等信息
  3. 网络层 再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给 物理层
  4. 数据包被传输到 主机 B 的网络层,在这里,主机 B 拆开 IP 头信息,并将拆开来的数据部分交给 传输层
  5. 传输层 将数据中的 UDP 头拆开,根据 UDP 头中的目的端口号,找到对应的程序,并将数据包交给对应的程序
  6. 最终,含有 “Cellinlab” 的数据包到达 主机 B 的 业务层

在使用 UDP 发送数据时,有各种因素会导致数据包出错,虽然 UDP 可以校验数据是否正确,但是对于错误的数据包,UDP 并不提供重发机制,只是丢弃当前的包,而且 UDP 在发送之后也无法知道是否能达到目的地。

虽说 UDP 不能保证数据可靠性,但是传输速度却非常快,所以 UDP 会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等。

TCP:把数据包完整地送达应用程序

在要求数据传输可靠性的应用中,UDP 会存在一些问题:

  • 数据包传输过程中易丢失
  • 大文件会被拆包传输,小包不会同时到达,UDP 不知道组合组包还原

为了解决上面的问题,引入了 TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议

TCP 相对 UDP 有了以下特点:

  • 对于数据包丢失的情况,TCP 提供重传机制
  • TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完成的文件

在 TCP 下的数据包传输流程:

TCP 是如何保证重传机制和数据包的排序的:

一个完整的 TCP 连接的生命周期包括了“建立连接”“传输数据”和“断开连接”三个阶段:

  1. 建立连接阶段

    • 通过“三次握手”,发送端和接收端建立了连接
    • TCP 提供面向连接的通信传输,数据通信开始之前先做好两端之间的准备工作
  2. 传输数据阶段

    • 接收端需要对每个数据包进行确认操作,收到数据包之后需要发送确认数据包给发送端
    • 在发送端发送了一个数据包之后,在协定的时间内没有收到接收方的确认消息,会判断为数据包丢失,并触发发送端重发机制
    • 大文件会被拆分为小数据包,每个小数据包到达后,接收端会按照 TCP 头中的序号排序,然后拼接成完整的数据
  3. 断开连接阶段

    • 通过“四次挥手”,来保证双方都能断开连接

TCP 为了保证数据传输的可靠性,牺牲了数据包的传输速度,因为“三次握手”和“数据包校验机制”等把传输过程中的数据包的数量提高了一倍。

HTTP 请求流程

HTTP 协议,正是建立在 TCP 连接基础之上的。HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由浏览器发起请求,用来获取不同类型的文件,例如 HTML 文件、CSS 文件、JavaScript 文件、图片、视频等。此外,HTTP 也是浏览器使用最广的协议。

浏览器端发起 HTTP 请求

在浏览器地址栏输入 https://cellinlab.xyz/index.html 之后,浏览器会完成下面的操作:

  1. 构建请求

    • 浏览器构建请求信息,准备发起网络请求
    GET /index.html HTTP/1.1
  2. 查找缓存

    • 在真正发起网络请求之前,浏览器会在浏览器缓存中查询是否有要请求的文件

      • 浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术
    • 当浏览器发现有缓存副本时,会拦截请求,并返回该资源的缓存副本,直接结束请求,不会再去服务器重新下载

      • 浏览器缓存副本,可以缓解服务器端压力,提升性能
      • 对于网站来说,缓存是实现快速资源加载的重要组件部分
    • 如果缓存没有命中,就会进入网络请求过程
  3. 准备 IP 地址和端口

    • 浏览器使用 HTTP 协议作为应用层协议,用来封装请求的文本信息
    • 使用 TCP/IP 作为传输层协议,将封装的请求文本发送到网络
    • 在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接
    • 浏览器会请求 DNS 返回域名对应的 IP 地址,浏览器也提供 DNS 数据缓存

  4. 等待 TCP 队列

    • Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果一次发 10 个请求到同一域名,其中 4 个会进入排队状态
    • 如果当前请求数量小于 6,会直接进入下一步
  5. 建立 TCP 连接
  6. 发送 HTTP 请求

    • 一旦建立了 TCP 连接,浏览器就可以和服务器通信了

服务器端处理 HTTP 请求

  1. 返回请求

    • 一旦服务器处理结束,就能返回数据给浏览器

    • 服务器会通过请求行的状态码来告诉浏览器它的处理结果
  2. 断开连接

    • 一般情况,服务器向客户端返回了请求数据,就要关闭 TCP 连接
    • 也可以通过在头信息中标记,来保持 TCP 连接
    Connection: keep-alive
    • 保持 TCP 连接可以节省下次请求时建立连接的时间,提升资源加载速度
  3. 重定向

    • 如果服务器返回了重定向,浏览器会自动重新发起请求

从输入 URL 到 页面展示

用户输入地址

  • 在地址栏输入后,判断是搜索内容还是请求的 URL

    • 如果是搜索内容,使用浏览器默认搜索引擎拼接出新的带关键词的 URL
    • 如果是 URL,工具规则给 URL 加上协议,拼接出完整的 URL
  • 回车后,进入加载状态

URL 请求

  • 浏览器进程通过进程间通信(IPC),把 URL 请求发送至网络进程,网络进程收到 URL 请求后,发起真正的 URL 请求流程
  • 具体请求流程

    1. 网络进程查找本地缓存是否缓存了该资源,命中缓存就直接返回资源,没有命中就进入网络请求流程

      • 请求前会进行 DNS 解析,获取 IP 地址
      • 如果请求协议是 HTTPS 还需要建立 TLS 连接
    2. 利用 IP 地址和服务器建立 TCP 连接

      • 连接建立后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等附加到请求头,发送给服务器
    3. 服务器收到请求后,进行相应的处理,生成响应数据,并发给网络进程

      • 重定向,如果服务器返回了重定向,浏览器会自动重新发起请求
      • 响应数据类型处理,会根据 Content-Type 来判断如何显示响应体的内容

准备渲染流程

  • 默认情况下,Chrome 会为每个页面分配一个渲染进程
  • 在某些情况下,浏览器会让多个页面直接运行在同一个渲染进程中

    • Chrome 的默认策略是,每个标签对应一个渲染进程
    • 如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点,那么新页面会复用父页面的渲染进程,这个策略叫 process-per-site-instance
  • 渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数据还在网络进程中,还没有提交给渲染进程

提交文档

  • “提交文档”的消息是由浏览器进程发出的,渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”
  • 等文档数据传输完成后,渲染进程会返回“确认提交”的消息给浏览器进程
  • 浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括安全状态、地址栏 URL、前进后退的历史状态,并更新 Web 页面

渲染页面

  • 一旦文档被提交,渲染进程就开始页面解析和子资源加载
  • 页面一旦生成完成,渲染进程就会发送消息给浏览器进程,浏览器进程收到后停止标签图标加载动画

渲染流程

按照渲染的时间顺序,渲染流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

构建 DOM 树

因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。

构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM。

DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。

样式计算

样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,大致可以分三个步骤:

  1. 把 CSS 转换为浏览器能够理解的结构

    • CSS 主要有三个来源:

      • 通过 <link> 标签引用的外部样式
      • 通过 <style> 标签指定的内联样式
      • 元素的 style 属性中指定的样式

    • 浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构 —— styleSheets

      • styleSheets 具备查询和修改功能,为后面的样式操作提供基础
      document.styleSheets; // 查看 styleSheets
  2. 转换样式表中的属性值,使其标准化

    • CSS 文本中有很多属性值,如 2emblueblod 等,这些类型不容易被渲染引擎理解,需要将所有值转换为渲染引擎容易理解的、标准化的计算值

  3. 计算出 DOM 树中每个节点的具体样式

    • CSS 继承是指每个 DOM 节点都包含父节点的样式

      body { font-size: 20px; }
      p { color: blue; }
      span { display: none; }
      div { font-weight: bold; color: red; }
      div p { color: green; }

    • 可以在 Chrome "开发者工具",选择 "Elements",查看 DOM 树,再选择 "Styles",查看样式表

    • User Agent 样式,是浏览器提供的一组默认样式
    • 样式层叠,定义如何合并来自多个源的属性值的算法
    • 最终计算出的每个 DOM 节点的样式,会被保存在 ComputedStyle 的结构内,可以在 “开发者工具” 中 “Computed” 子标签中查看

布局阶段

布局是计算 DOM 树中可见元素的几何位置,主要有两个任务:

  1. 创建布局树

    • DOM 树中还含有很多不可见的元素,比如 <script> 标签,还有使用了 display: none 的元素,这些元素不会被渲染,在显示之前,需要额外构建一棵只包含可见元素布局树

    • 构建布局树大致流程:

      • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中
      • 不可见元素会被布局树忽略掉
  2. 布局计算

    • 计算布局树节点的坐标位置

分层

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动或者 z-index 做 z 轴排序等。为了更方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(Layer Tree)

可以通过 “开发者工具” 中 “Layers” 子标签直观查看页面分层情况。

可以看出,渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层

渲染引擎为特定的节点创建新的层,一般需要满足以下任意条件:

  • 拥有层叠上下文属性的元素会被提升为单独的一层

    • 页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念,HTML 元素按照自身属性的优先级分布在垂直于二维平面的 z 轴上

    • 明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都会拥有层叠上下文
  • 需要裁剪(clip)的地方会被创建为图层

    • 出现裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制。渲染引擎在实习图层绘制时,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。

可以在 “开发者工具-Layers” 中选择 document,观察绘制列表。

栅格化

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

通常一个页面可能很大,但是用户只能看到其中的一部分,把用户可以看到的这个部分叫做视口(viewport)。

在有些情况下,有的图层可以很大,比如有的页面使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

为了优化性能,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512。

然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下:

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

整个渲染流程

整个渲染流程,从 HTML 到 DOM、样式计算、布局、图层、绘制、光栅化、合成和显示。

大致可总结为如下:

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树。
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
相关文章
|
3月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
3月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
54 0
|
3月前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
|
4月前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
|
5月前
|
Web App开发 JavaScript 前端开发
从浏览器原理出发聊聊Chrome插件
本文从浏览器架构演进、插件运行机制、插件基本介绍和一些常见的插件实现思路几个方向聊聊Chrome插件。
778 0
|
8月前
|
安全 算法 网络协议
浏览器基础原理-安全: HTTPS
浏览器基础原理-安全: HTTPS
62 0
|
8月前
|
Web App开发 存储 监控
浏览器基础原理-安全: 渲染进程-安全沙盒
浏览器基础原理-安全: 渲染进程-安全沙盒
38 0
|
8月前
|
安全
浏览器基础原理-安全: CSRF攻击
浏览器基础原理-安全: CSRF攻击
54 0
|
8月前
|
JavaScript 安全 前端开发
浏览器基础原理-安全: 同源策略
浏览器基础原理-安全: 同源策略
38 0
|
8月前
|
存储 编解码 安全
浏览器基础原理-安全: 跨站脚本攻击(XSS)
浏览器基础原理-安全: 跨站脚本攻击(XSS)
34 0

热门文章

最新文章