Chromium采用是多进程架构,为什么采用多层架构呢?
因为构建一个从不会挂起或崩溃的渲染引擎几乎是不可能的。构建一个完全安全的渲染引擎也是几乎不可能的。web浏览器类似一个与过去的多任务操作系统合作的单独的用户,一个错误的web页面能让整个浏览器和所有正在运行的标签页停止运行。采用彼此隔离的独立进程,这样程序中的crash通常不会影响其他进程或整个浏览器,但是进程间数据的访问会受到一些限制。
浏览器的标签页使用的是独立的进程,以此保护整个应用程序免受渲染引擎中的bug和故障的伤害。同时限制每个渲染引擎进程的相互访问,以及他们与系统其他部分的访问。某些程度上,这为web浏览提供了内存保护,为操作系统提供了访问控制。
Browser(浏览器进程或浏览器):运行UI 和管理 Tab/Plugin 的主进程。
Renderer(渲染进程或渲染器):标签页相关的进程,Renderer使用webkit开源引擎来实现中断与html的布局。
每个Renderer都有一个全局的RenderProcess对象,管理它与Browser之间的通信,维护全局的状态。Browser为每个Renderer维护一个对应的RenderViewHost,用来管理浏览器状态,并与渲染器交流。Browser与Renderer使用IPC进行交流。
每个渲染进程有一个以上的RenderView对象,由RenderProcess管理(它与标签页的内容相关)。对应的RenderProcessHost维护一个与渲染器中每个view相关的RenderViewHost。每个view被赋予一个view ID,以区分同一个渲染器中的不同view。这些ID在每个渲染器内是唯一的,但在浏览器中不是,所以区分一个view需要一个RenderProcessHost和一个view ID。
浏览器与一个包含内容的特定标签页之间的交流是通过这些RenderViewHost对象来完成的,它们知道如何通过他们的RenderProcessHost向RenderProcess和RenderView发送消息。
在渲染进程中:
RenderProcess处理与浏览器中对应的RenderProcessHost的通信。每个渲染进程就有唯一的一个RenderProcess对象。这就是所有浏览器-渲染器之间的交互发生的方式。
RenderView对象与它在浏览器进程中对应的RenderViewHost和webkit嵌入层通信(通过RenderProcess)。这个对象代表了一个网页在标签页或一个弹出窗口的内容。
在浏览器进程中:
Browser对象代表了顶级浏览器窗口
RenderProcessHost对象代表了浏览器端浏览器的与渲染器的IPC连接。 在浏览器进程里,每个渲染进程有一个RenderProcessHost对象。
RenderViewHost对象封装了与远端浏览器的交流,RenderWidgetHost处理输入并在浏览器中为RenderWidget进行绘制。
通常每个新的window或标签页是在一个新进程里打开的。浏览器会生成一个新的进程,然后指导它去创建一个RenderView。有时候,希望在标签页或窗口间共享渲染进程。一个web应用程序会在期望同步交流时,打开一个新的窗口,比如,在javascript里使用window.open。这种情况下,创建一个新的window或标签页时,需要重用打开这个window的进程。
每个到浏览器进程的IPC连接会观察进程句柄。如果这些句柄是signaled(有信号的),那么渲染进程已经挂了,标签页会得到一个通知。从这时开始,会展示一个“sad tab”画面来通知用户渲染器已经挂掉了。这个页面可以按刷新按钮或者通过打开一个新的导航来重新加载。这时,如果没有对应的进程,那么就创建一个新的进程。
WebKit是运行在独立的进程中,可以限制它对系统资源的访问。例如:可以确保渲染器唯一的网络权限是通过它的父浏览器进程实现。也可以限制它对文件系统的访问权限来使用host操作系统内置的权限。除了限制渲染器对文件系统和网络的访问权限,还可以限制它对用户的显示器以及相关的东西的一些权限。
让渲染器运行在独立的进程中,赋予隐藏的标签页更低的优先级会更加直接。通常,Windows平台上的最小化的进程会把它们的内存自动放到一个“可用内存”池里。在低内存的情况下,Windows会在交换这部分内存到更高优先级内存前,把它们交换到磁盘,以保证用户可见的程序更易响应。可以对隐藏的标签页使用相同的策略。当渲染器进程没有顶层标签页时,可以释放进程的“工作集”空间,作为一个给系统的信号,让它如果必要的话,优先把这些内存交换到磁盘。
Firefox风格的NPAPI插件运行在他们自己的进程里,与渲染器隔离。
如上图,每一层都做了高度的抽象,分离,进行了解耦。每个矩形代表了一个应用概念层,每一层都不了解上一层,也对上一层没有依赖。
1、WebKit:Safari,Chromium和其他所有基于WebKit的浏览器共享的渲染引擎。WebKit
Port是WebKit的一部分,用来集成平台独立的系统服务,比如资源加载与图像。2、Glue:将WebKit的类型转为Chromium的类型。一般称为“WebKit嵌入层”。这是两个browser,Chromium,和test_shell(用来测试WebKit)的基础。
3、Renderer / Render host: 这是Chromium的“多进程嵌入层”。它代理通知,并执行跨过进程指令。
4、WebContents:一个可重用的组件,是内容模块的主类。易于嵌入,允许多进程将HTML绘制成View。
5、Browser: 代表浏览器窗口,包含多个WebContent。
6、Tab Helpers:可以被绑定到WebContent的独立对象(通过WebContentsUserData混杂)。浏览器将这些独立对象中的一种绑定到WebContent给它持有,一个给网站图标,一个给信息栏等等。
Chromium使用WebKit开源工程来布局web页面。这部分代码是从Apple中pull过来的,存储在/third_party/WebKit目录。WebKit主要由“WebCore”+“JavaScriptCore”组成,WebCore代表了核心的布局功能,JavaScriptCore用来运行JavaScript。一般只在测试时运行JavaScriptCore,通常情况下,使用高性能的V8 Javascript引擎来代替它。The WebKit port在最低层,是平台相关功能的实现,它们与平台无关的WebCore代码交互。大部分与操作系统无关的,可以把它认为WebCore的“Chromium port”。但某些方面,比如字体渲染,必须在不同平台上做不同的处理。
Chromium应用程序使用不同的类型,编码风格,以及代码布局和第三方的WebKit代码。WebKitGlue使Google编码传统与类型为WebKit提供了一个更加方便的嵌入式API(例如,我们使用std::string而非WebCore::String,使用GURL而非KURL)。代码位于/webkit/glue。glue对象通常有与WebKit对象相似的命名,但在开头有Web前缀。例如, WebCore::Frame变成了WebFrame。WebKitGlue层将Chromium代码的其他部分与WebCore数据类型隔离开,以帮助减少WebCore的改变对Chromium代码基础的影响。因此,WebCore数据类型从不直接被Chromium使用。为了Chromium的便利,需要一些WebCore对象时,会把API加入WebKit的Glue层。
test shell应用程序是一个为测试我们的WebKit port和Glue代码的裸web浏览器。它在与WebKit交流时,像Chromium那样使用一样的Glue接口。它为开发者提供了简单的方式去测试新的代码,而不用理会许多复杂的浏览器特性,线程和进程。这个应用程序也被用于运行自动化WebKit测试。然而,test shell的缺点在于,它不像Chromium那样用多进程方式实践WebKit。内容模块嵌入在一个被称为“content shell”的应用程序,使测试工作更便利。
Chromium的浏览器进程使用Glue接口嵌入在WebKit port中,它的工作主要是作为渲染器端到浏览器的IPC通道。渲染器中最重要的类是RenderView,位于/content/renderer/render_view_impl.cc。这个对象代表一个web页面。处理与浏览器之间所有导航相关的命令。驱动RenderWidget提供绘图和输入事件处理。RenderView与浏览器进程通过全局(每个渲染器进程)RenderProcess对象与浏览器进程交流。
每个渲染器有两个线程。渲染线程是主要的对象,比如RenderView和所有的WebKit代码运行的地方。当它与浏览器交流时,消息一开始发送到主线程,主线程轮流分发消息给浏览器进程。在其他情况里,允许从渲染器同步发送消息到浏览器。当一个来自浏览器的结果是用于后续操作时,这可以用于小量的操作。例如:JavaScript从网页请求cookie。渲染器线程会阻塞,主线程会让所有的接收到的消息排队,直到得到正确的响应。此时任何接收到的消息会突然发送给渲染器线程以执行普通的处理。
底层浏览器进程对象
所有的与渲染器进程交流的IPC是在浏览器的I/O线程完成的。这个线程也处理所有的网络交流,使得它不受用户界面的干扰。当一个RenderProcessHost对象在主线程完成初始化(当用户界面运行时),它会创造新的渲染器进程和一个通道代理IPC对象(有一个命名了的管道通向渲染器),自动转发所有的消息回给UI线程的RenderProcessHost。一个ResourceMessageFilter会安装在这个通道,它会过滤指定的消息,以直接在I/O线程处理(比如网络请求)。这个过滤器发生在ResourceMessageFilter::OnMessageReceived里。UI线程中的RenderProcessHost负责分发所有view相关消息给合适的RenderViewHost(它自己处理有限数量的与View相关的消息)。这种分发发生在RenderProcessHost::OnMessageReceived中。
上层浏览器进程对象
View相关消息出现在RenderViewHost::OnMessageReceived。这里处理的大部分消息,剩下的部分转发给RenderWidgetHost基类。这两个对象在渲染器里里映射到RenderView和RenderWidget。每个平台有一个view类(RenderWidgetHostView[Aura|Gtk|Mac|Win])以集成到native view系统。
在RenderView/Widget上面是WebContents对象,大部分的消息事实上结束于这个对象的函数调用。一个WebContent代表网页的内容。它是内容模块的顶层对象,并且负责在一个矩形的view中展示网页。WebContents对象包含在一个TabContentsWrapper中,它位于chrome/。负责标签页。
插件是浏览器不稳定的主要来源。插件也会在渲染器没有实际运行时,让进程沙箱化。因为进程是第三方编写的,所有无法控制他们对操作系统的访问。解决方案是:让插件在各自独立的进程中运行。
Chromium有着在进程内运行插件的能力(对测试来讲非常方便),也可以在进程外运行插件。它们都始于非多进程WebKit嵌入层,嵌入层实现WebKit::WebPlugin接口。这实际由WebPluginImpl实现。WebPluginImpl在图中的虚线以上,与WebPluginDelegate接口交流,对进程内插件而言,这个接口由WebPluginDelegateImpl实现,它会与NPAPI包装层通信。
在还没有WebKit嵌入层的时候,WebPluginImpl是对应的嵌入层。它会与“嵌入应用程序”通过WebPluginDelegate抽象接口交流,通过切换这个接口的实现,服务与进程内插件与进程外插件,在有了额外的Chromium WebKit API之后,增加了新的WebKit::WebPlugin抽象接口,它与旧的WebPluginDelegate接口有着相同的功能。这个接口合并WebPluginImpl和WebPluginDelegateImpl,在WebKit::WebPlugin层做进程划分。
Chromium通过切换上面的图中,虚线以上几层的实现来支持跨进程插件。这干预了WebPluginImpl层和WebPluginDelegateImpl之间的IPC层,让每个模式之间共享所有的NPAPI代码。所有旧的WebPluginDelegateImpl代码,以及与它通信的NPAPI层,现在是在独立的插件进程中执行了。
渲染器/插件通信通道的两端分别由PluginChannel和PluginChannelHost代表。对于每种它所使用的插件都持有一个PluginChannelHost对象(例如,Adobe Flash和Windows Media Player)。在每个插件进程中,每个渲染器进程会有一个PluginChannel,它们各自持有一个那种插件的实例。接着,channel的每个端点,对应许多不同的插件实例。例如,如果网页中嵌有两个Adobe Flash视频,渲染器端就会有两个WebPluginDelegateProxies对象,插件端就会有两个WebPluginDelegateStubs。channel用一个IPC连接管理这些对象直接复数的通信。在下图中,可以看到上面的进程内图表的类(用灰色表示),以及中间彩色的新的进程外代码层。
使用一个stub(存根)/proxy(代理)模型进行通信,每个IPC通道的端点有一个stub和一个proxy,分别接收和发送消息给对应的插件。这会导致许多类变得迷乱。因此,WebPluginStub被合并到WebPluginDelegateProxy,现在它处理渲染器端与一个插件实例的所有IPC通信。插件端还没有合并,还剩两个类WebPluginDelegateStub和WebPluginProxy,概念上他们是相同的对象,只是代表了通信的不同方向。
下图展示了整个系统,有浏览器和两个渲染进程,它们都与一个共享的进程外Flash进程交流。总共有三个插件实例。WebPluginStub已经合并到WebPluginDelegateProxy中了。