Chrome源码阅读系列Chrome多进程架构

12次阅读

共计 2656 个字符,预计需要花费 7 分钟才能阅读完成。

这个文档描述了 Chromium 的顶层架构设计

问题背景

构建一个没有 crash 和挂起的渲染引擎基本是不可能,构建一个完全安全的渲染引擎也基本是不可能的。

在某些程度上,2006 年左右的 web 浏览器就像过去单用户、多任务合作的操作系统一样。和一些操作系统里发生故障的应用可能搞挂整个系统一样,一个操作不当的 web 页面也会搞挂整个浏览器。一个页面或者插件的 bug 就可能把整个浏览器搞崩溃以至于所有页面都不可能用了。

现代操作系统要相对更健壮一些,因为他们把应用都拆分进了独立的进程来相互分隔。一个应用的 crash 通常不会影响其他应用或者操作系统,并且每个用户和其他用户的数据访问也是有限制的。

架构概述

我们给每个浏览器 tab 页都使用了独立的进程来保护大部分的应用不受渲染引擎的 bug 和故障影响。我们也限制来每个渲染引擎进程访问其他渲染引擎进程或者系统的权限。这样在某种程度上就可以让浏览器受益于内存保护对操作系统的访问权限。

我们把运行 UI、管理 tab 和插件进程的主进程称为“browser process”或者“broswer”。同样地,具体的 tab 进程被称为“render process”或者“renderer”。renderer 用 Blink 这个开源布局引擎来解析 HTML 布局。

管理渲染进程

每个渲染进程都有一个全局的 RenderProcess 对象来管理与父 browser 进程的通信,并且维护全局状态。browser 维护每个渲染进程对应的 RenderProcessHost,来管理 browser 状态和渲染器的通信。browser 和 renderers 通过 Chromium IPC 系统进行通信。

管理视图

每个渲染进程都有一个或者更多的 RenderView 对象,由 RenderProcess 管理,对应每个 tab 的内容。对应的 RenderProcessHost 维护一个 RenderViewHost 对应每个 renderer 的视图。每个视图都被分配到一个 ID 来在同一个 renderer 里区分多个 view。这些 ID 在同一个 renderer 里都是唯一的,但是在 browser 里面不一定唯一,所以识别一个 view 同时需要 RenderProcessHost 和 view ID。browser 和某个具体的 tab 内容之间通过 RenderViewHost 对象,通过 RenderProcessHost 给 RenderProcess 再定位到 RenderView 发送信息来进行通信。

组件和接口

在渲染进程中:

  • RenderProcess 处理 IPC 和 browser 进程中对应 RenderProcessHost 之间的通信。每个渲染进程中都明确地有一个 RenderProcess 对象。这就是 browser ↔ renderer 之间通信的方式。
  • RenderView 对象和 browser 进程中对应的 RenderProcessHost 进行通信(通过 RenderProcess),和我们嵌入式的 WebKit 层。这个对象代表来一个 web 页面的内容或者一个弹出的窗口。

在浏览器进程中:

  • Browser 对象代表一个顶层的浏览器窗口
  • RenderProcessHost 对象代表浏览器侧的一个 browser ↔ renderer 通信 IPC 连接。每个渲染进程和浏览器进程之间都有一个 RenderProcessHost 对象。
  • RenderViewHost 对象包含了和远端 RenderView 之间的通信,RenderWidgetHost 处理输入并绘制浏览器中的 RenderWidget

内部更多详细的工作原理可以,可以查看 How Chromium displays web pages) 设计文档

共享渲染进程

通常,每个新的窗口或者 tab 都会在一个新的进程中打开。浏览器会打开一个新的进程并且创建一个单独的 RenderView。
但是有的时候,在 tab 和 window 之间共享渲染进程也是必要的。一个 web 饮用会打开一个新的窗口并且希望能同步的进行通信,比如,在 Javascript 中使用 window.open。在这种情况下,当我们创建一个新的 window 或者 tab 的时候,我们就需要重复利用之前 window 已经打开过的进程。当进程数太多的时候我们也有一些策略来分配已经打开的渲染进程给新的 tab,或者如果这个用户已经有打开过这个域名下的页面创建的进程的时候。这个策略在 Process Models 中。

检测 crash 或者故障的 renderer

每个和 browser 进程通信的 IPC 连接都会监听 process handler。如果这些 handler 收到消息,renderer 进程有 crash,那么这些 tab 就会被通知到 crash。当前,我们会展示一个“sad tab”到屏幕上来告诉用户渲染进程 crash 了。这个页面可以通过按下刷新按钮重新加载。这个时候,我们也会通知没有进程了,需要重新创建一个进程。

渲染沙箱

针对一个独立的进程,我们通过沙箱去限制访问系统资源的权限。比如,我们可以通过父 browser 进程来保证渲染器只能访问网络。同样的,我们也可以用操作系统内置的权限来限制它访问文件系统的权限。
除了限制渲染器访问文件系统和网络的权限,我们还可以限制访问用户展示相关的权限。我们运行渲染进程在一个用户不可见的独立 Windows Desktop 上。这防止了渲染进程打开一个新的 window 或者采集按键。

回收内存

让渲染器在一个独立的进程中运行,很自然的就会给隐藏的 tab 更低一点的优先级。通常,被降低内存的进程会自动地把内存分配到一个“可用内存”的池子里。在低内存模式下,Windows 会在要切换到高优先级模块内存之前把这些内存交换到硬盘上,来使用户可见的程序能更快的响应。我们可以应用同样的策略到隐藏的 tab 里。当一个渲染进程没有最高优先级的 tab 时,需要时我们可以把这个进程的“working set”大小从系统内存先释放到硬盘上。因为我们发现减少工作区域大小也会降低用户从 2 个 tab 之间切换的切换性能。但是当用户有足够的内存来运行他们的程序的时候就不会通知这个进程:Windows 只有在确实需要的时候才会回收这些数据,所以当有足够的内存时不会有性能的损失。

插件和扩展

Firefox 风格的 NPAPI 插件运行在他们自己的进程里,和 renderer 分离开,详细的描述在 plugin architecture 里。
site isolation 项目提供了 renderer 之间更多的隔离,包括了 Chrome 的 HTML/Javascript 在独立进程中的上下文扩展。

正文完
 0