# 多进程架构

这个文档描述了Chromium的高层架构

## 问题

构建一个从不会挂起或崩溃的渲染引擎几乎是不可能的。构建一个完全安全的渲染引擎也是几乎不可能的。

在某种程度上，web浏览器当前状态就像一个与过去的多任务操作系统合作的单独的用户。正如在一个这样的操作系统中的错误程序会让整个系统挂掉，所以一个错误的web页面也可以让一个现代浏览器挂掉。仅仅需要一个浏览器或插件的bug，就能让整个浏览器和所有正在运行的标签页停止运行。

现代操作系统更加鲁棒，因为他们把应用程序分成了彼此隔离的独立进程。一个程序中的crash通常不会影响其他程序或整个操作系统，每个用户对用户数据的访问也是有限制的。

## 架构概览

我们为浏览器的标签页使用独立的进程，以此保护整个应用程序免受渲染引擎中的bug和故障的伤害。我们也会限制每个渲染引擎进程的相互访问，以及他们与系统其他部分的访问。某些程度上，这为web浏览提供了内存保护，为操作系统提供了访问控制。

我们把运行UI 和管理 Tab/Plugin 的主进程称为“浏览器进程”或“浏览器（Browser）”。相似的，标签页相关的进程被称作“渲染线程”或“渲染器（renderer）”。渲染器使用 [Blink](https://www.chromium.org/blink) 开源引擎来实现中断与html的布局。

![img](https://3167484443-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2duiuWWOPDiqkkg8J83t%2Fuploads%2Fgit-blob-2678f4c06b7beb392e48fdd35f8e530c8b1f30d2%2Farch.png?alt=media)

### 管理渲染进程

每个渲染进程有一个全局的`RenderProcess`对象，管理它与父浏览器进程之间的通信，维护全局的状态。浏览器为每个渲染进程维护一个对应的`RenderViewHost`，用来管理浏览器状态，并与渲染器交流。浏览器与渲染器使用 [Mojo](https://chromium.googlesource.com/chromium/src/+/HEAD/mojo/README.md) 或者 [Chromium's Legacy IPC system](https://nan03s-organization.gitbook.io/chromium/zh/general_architecture/inter-process_communication) 进行交流。

### 管理框架（frames）和文档（documents）

每个渲染进程有一个以上的`RenderFrame`对象，对应于包含内容的文档中的框架。浏览器进程中的相应`RenderFrameHost`管理与该文档相关联的状态。每个`RenderFrame`都有一个路由ID，用于区分同一渲染器中的多个文档或帧。这些ID在一个渲染器内是唯一的，但在浏览器内不是唯一的，因此区分不同的框架需要`RenderProcessHost`和路由ID。浏览器到渲染器中特定文档的通信是通过这些`RenderFrameHost`对象完成的，它们知道如何通过Mojo或遗留IPC发送消息。

## 组件与接口

在渲染进程中：

* *RenderProcess*处理与浏览器中对应的*RenderProcessHost*的通信。每个渲染进程就有唯一的一个RenderProcess对象。这就是所有浏览器-渲染器之间的交互发生的方式。
* RenderView对象与它在浏览器进程中对应的RenderViewHost和我们的webkit嵌入层通信（通过RenderProcess）。这个对象代表了一个网页在标签页或一个弹出窗口的内容。

在浏览器进程中:

* Browser对象代表了顶级浏览器窗口
* RenderProcessHost对象代表了浏览器端浏览器的与渲染器的IPC连接。在浏览器进程里，每个渲染进程有一个RenderProcessHost对象。
* RenderViewHost对象封装了与远端浏览器的交流，RenderWidgetHost处理输入并在浏览器中为RenderWidget进行绘制。

想要得到更多关于这种嵌入是如何工作的详细信息，可以查看[How Chromium displays web pages design document](https://github.com/hatrd/Chromium_doc_zh/blob/master/zh/Start_Here_Background_Reading/How_Chromium_displays_web_pages_design_document/README.md)。

## 共享绘制器进程

通常，每个新的window或标签页是在一个新进程里打开的。浏览器会生成一个新的进程，然后指导它去创建一个*RenderView*。

有时候，有这样一种必要或欲望在标签页或窗口间共享渲染进程。一个web应用程序会在期望同步交流时，打开一个新的窗口，比如，在javascript里使用window\.open。这种情况下，当我们创建一个新的window或标签页时，我们需要重用打开这个window的进程。我们也有一些策略来把新的标签页分配给已有的进程（如果总的进程数太大的话，或者如果用户已经为这个域名打开了一个进程）。这些策略在[Process Models](https://nan03s-organization.gitbook.io/chromium/zh/general_architecture/process_models)里也有阐述。

## 检测crash或者失误的渲染

每个到浏览器进程的Mojo或者IPC连接会观察进程句柄。如果这些句柄是signaled（有信号的），那么渲染进程已经挂了，标签页和框架会得到一个通知。从这时开始，我们会展示一个“sad tab”或者“sad frame”画面来通知用户渲染器已经挂掉了。这个页面可以按刷新按钮或者通过打开一个新的导航来重新加载。这时，我们会注意到没有对应的进程，然后创建一个新的。

## 渲染器中的沙箱

由于渲染器在单独的进程中运行，我们可以通过[沙箱技术](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox.md)限制其对系统资源的访问。例如，我们可以确保渲染器仅通过 Chromium 的网络服务访问网络。同样，我们可以使用主机操作系统的内置权限限制其对文件系统的访问，或限制其对用户的显示和输入的访问。这些限制显著限制了受到攻击的渲染器进程所能完成的任务

## 归还内存

让渲染器运行在独立的进程中，赋予隐藏的标签页更低的优先级会更加直接。通常，Windows平台上的最小化的进程会把它们的内存自动放到一个“可用内存”池里。在低内存的情况下，Windows会在交换这部分内存到更高优先级内存前，把它们交换到磁盘，以保证用户可见的程序更易响应。我们可以对隐藏的标签页使用相同的策略。当渲染器进程没有顶层标签页时，我们可以释放进程的“工作集”空间，作为一个给系统的信号，让它如果必要的话，优先把这些内存交换到磁盘。因为我们发现，当用户在两个标签页间切换时，减少工作集大小也会减少标签页切换性能，所以我们是逐渐释放这部分内存的。这意味着如果用户切换回最近使用的标签页，这个标签页的内存比最近较少访问的标签页更可能被换入。有着足够内存的用户运行他们所有的程序时根本不会注意到这个进程：事实上Windows只会在需要的时候重新声明这块数据，所以在有充分内存时，不会有性能瓶颈。

这能帮助我们在低内存的情况下得到最佳的内存轨迹。几乎不被使用的后台标签页相关的内存可以被完全交换掉，前台标签页的数据可以被完全加载进内存。相反的，一个单进程浏览器会在它的内存里随机分配所有标签页的数据，并且不可能如此清晰地隔离已使用的和未使用的数据，导致了内存和性能上的浪费。

### 额外的进程类型

Chromium 还将许多其他组件分离到单独的进程中，有时以特定于平台的方式。例如，它现在有一个单独的 GPU 进程、网络服务和存储服务。沙箱实用程序进程也可以用于小型或风险较高的任务，作为满足[安全性规则](https://chromium.googlesource.com/chromium/src/+/master/docs/security/rule-of-2.md)的一种方式。
