多进程架构

  • Chromium 的每个标签页都是一个独立的进程,为了确保安全,Chromium 会限制每个渲染引擎彼此之间的访问权限,以及他们访问系统其他资源的权限。

  • 运行 UI 和管理 Tab/Plugin 的主进程称为”浏览器进程” 或 “浏览器(Browser)”,标签页相关的进程被称作”渲染线程”或”渲染器(renderer)”

  • renderer 使用 Blink 开源引擎来实现解析和 HTML 布局

Managing render processes

每个渲染进程有一个全局的 RenderProcess 对象,该对象管理渲染进程与父浏览器进程之间的通信,并维护全局状态。浏览器为每个渲染进程维护一个对应的RenderViewHost,用来管理浏览器状态,并与渲染器通信。浏览器与渲染器使用Chromium’s IPC system进行通信。

Managing views

每个渲染进程都有一个或多个 RenderView 对象,由 RenderProcess 管理(它与标签页的内容相关)。相应的RenderProcessHost维护一个与渲染器中每个view相关的RenderViewHost。每个view被赋予一个view ID,以区分同一个渲染器中的不同view。这些 ID 在一个渲染器中是唯一的,但在浏览器中不是唯一的,所以区分一个view需要一个RenderProcessHost和一个view ID。

浏览器与特定标签页之间的通信是通过RenderViewHost对象来完成的。

组件与接口

In the render process:

  • RenderProcess通过浏览器中和其对应的RenderProcessHost来处理 IPC。每个渲染进程只有一个RenderProcess对象。所有浏览器-渲染器之间都采用这种方式通信。
  • RenderView 对象与浏览器进程中相应的 RenderView 和 WebKit 嵌入层通信(通过RenderProcess)。这个对象代表了一个网页在标签页或一个弹出窗口的内容。

In the browser process:

  • Browser 对象代表了顶级浏览器窗口
  • RenderProcessHost 对象代表了浏览器端的浏览器与渲染器的IPC连接。在浏览器进程里,每个渲染进程有一个RenderProcessHost对象。
  • RenderViewHost 对象封装与远程 RenderView 的通信,RenderWidgetHost 在浏览器中处理输入和 RenderWidget的绘制。

共享渲染过程

通常,每个新的 window 或标签页是在一个新进程里打开的。浏览器会生成一个新的进程,然后指导它去创建一个RenderView。但有时候在标签页或窗口之间共享渲染进程是有必要的。

这些策略在Process Models里有阐述。

检测 Crash OR 异常渲染

每个和浏览器进程通信的 IPC 都会检测进程的句柄,如果句柄是 signaled,则代表渲染进程 Crashed 相应的标签页也会收到 Crash 的信息。这是 Chromium 将显示一个 sad tab 屏幕,通知用户渲染器已崩溃,该页面可以通过按重新加载按钮或启动新的导航来重新加载。

Sandboxing the renderer

renderer的进程被沙箱化,其权限进行了如下的限制:

  • 渲染器只能通过其父浏览器进程访问网络
  • 主机操作系统的内置权限限制了它对文件系统的访问
  • rederer 进程运行在单独的Windows桌面上,该桌面对用户是不可见的。
  • 限制对用户的display 和 related对象的访问

参考

[1] https://www.cntofu.com/book/101/zh/Start_Here_Background_Reading/Multi-process_Architecture.md

[2] https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/Start_Here_Background_Reading/Multi-process_Architecture.html

如何显示网页

应用概念层

每个矩形代表一个应用概念层,每层之间互不相通,层与层之间也没有任何依赖关系。

  • WebKit:Safari,Chromium和其他所有基于 WebKit 的浏览器共享的渲染引擎。WebKit Port是WebKit的一个部分,用来集成平台独立的系统服务,比如资源加载与图像。
  • Glue: 将WebKit类型转换为Chromium类型。该层是 WebKit 的嵌入层。
  • Renderer / Render host: Chromium 多进程架构的嵌入层,代理通知,跨进程边界执行命令。
  • WebContents:一个可重用的组件,它是 Content 模块的主类。它易于嵌入,允许多进程将HTML绘制成View。
  • Browser: 浏览器窗口,它包含多个 WebContentses。
  • Tab Helpers: 可以附加到WebContents的各个对象(通过WebContentsUserData Mixin)。浏览器将这些独立对象中的一种绑定到WebContent给它持有(一个用于Favicons,一个用于infobars)

The render process

Chromium 的渲染进程通过 glue接口将 WebKit 端口嵌入。它的工作主要是作为渲染器端到浏览器的IPC通道。

reder 中最重要的类是RenderView,在 /content/renderer/render_view_impl.cc。这个对象代表一个 web 网页,处理浏览器进程之间的所有导航相关命令,派生于RenderWidget(RenderWidget 提供绘图和输入事件处理)。RenderView 通过全局 RenderProcess 对象与浏览器进程通信。

RenderWidget 和 RenderView 之间的区别:

RenderWidget 通过 glue 层(WebWidgetDelegate)的抽象接口映射到一个WebCore::Widget对象。这基本是屏幕上的一个窗口,用于接收输入事件并在其中进行绘制。

RenderView 继承自 RenderWidget,代表标签页或弹窗的内容。除了绘制与组件输入事件外,它还处理导航指令。只有一种情况下,RenderWidget可以在没有RenderView时存在,就是网页中的下拉选择框(select box)。下拉选择框必须用native window来渲染(因为其没有独立 web 网页)。

Threads in the renderer

每个渲染器都有两个线程,The render thread is where the main objects such as the RenderView and all WebKit code run。当渲染器线程与浏览器通信时,消息首先被发送到主线程,然后主线程将消息发送到浏览器进程。除了这种方式,同样还允许我们从渲染器同步地向浏览器发送消息,这种情况一般发生在,需要浏览器返回的结果才能继续执行的事件。例如,在 JavaScript 请求获取页面的cookie时,渲染器线程会阻塞,主线程将对接收到的所有消息排队,直到找到正确的响应。在此期间接收到的任何消息,在这之后都会分配到渲染器线程以进行正常处理。

The browser process

Low-level browser process objects

所有 IPC 与渲染进程的通信都通过浏览器的 I/O线程来完成的,此线程还处理所有网络通信,以防止其干扰用户界面。

当一个 RenderProcessHost 对象在主线程(用户界面运行的地方)完成初始化时,它会创造新的渲染器进程和一个ChannelProxy IPC对象(具有一个命名了的管道通向渲染器),自动转发所有的消息回给UI线程的RenderProcessHost。该对象运行在浏览器的 I/O 线程上,监听渲染器的命名管道,并自动将所有消息转发回 UI 线程上的RenderProcessHost。ResourceMessageFilter 也将安装在此通道中,它将过滤掉某些可以直接在I/O线程上处理的消息,如网络请求。筛选的过程发生在 ResourceMessageFilter::OnMessageReceived

UI线程中的RenderProcessHost负责分发所有view相关消息给合适的 RenderViewHost(它自己处理有限数量的与非View特定的消息)此调度发生在RenderProcessHost::OnMessageReceived中。

High-level browser process objects

View-specific 信息源于RenderViewHost::OnMessageReceived。大多数消息都在这里处理,其余的转发到RenderWidgetHost基类。这两个对象映射到渲染器中的 RenderView 和 RenderWidget,每个平台都有一个View类以集成到native view系统。

在RenderView / Widget上方是 WebContents 对象,大部分的消息结束于这个对象的函数调用。WebContents代表网页的内容,它是内容模块中的顶级对象,负责在一个矩形的view中展示网页。

WebContents 对象包含在 TabContentsWrapper 中。在chrome /中,负责一个标签页。

参考

[1] https://www.chromium.org/developers/design-documents/displaying-a-web-page-in-chrome

Threading and Tasks in Chrome

Chrome 是基于多进程架构的,每个进程又是多线程的。多进程架构主要目的是为了让主线程(例如 Browser 进程中的 UI 线程)和 IO 线程(进程中处理 IPC 消息的线程)保持快速响应。Chromium希望尽量保持UI处于响应状态。为此遵循如下设计原则:

  • 不在UI线程上执行任何阻塞I/O操作,以及其它耗时操作。
  • 少用锁和线程安全对象
  • 避免阻塞I/O线程
  • 线程之间不要互相阻塞
  • 在数据准备好更新到共享缓冲时才用锁(在准备数据期间不要用锁)

核心概念

  • Task: task 是一个待处理的工作单元,也可以理解为是具有可选关联状态的函数指针。In Chrome this is base::OnceCallback and base::RepeatingCallback created via base::BindOnce and base::BindRepeating, respectively. (documentation).
  • Task queue: 待处理的任务队列。
  • Physical thread: 操作系统提供的线程 (e.g. pthread on POSIX or CreateThread() on Windows). The Chrome cross-platform abstraction is base::PlatformThread.
  • base::Thread: A physical thread 永远处理来自专用任务队列的消息,直到 Quit()。
  • Thread pool: 具有共享任务队列的物理线程池. In Chrome, this is base::ThreadPoolInstance.
  • Sequence or Virtual thread: chrome 管理的执行线程。与 physical thread 一样,在任何给定时刻只有一个任务可以在给定的 sequence / virtual thread 线程上运行,并且每个任务都可以看到前面任务的副作用。 任务会按顺序执行,但可能会在每个physical thread之间跳转。
  • Task runner: 一个可以发布任务的界面. In Chrome this is base::TaskRunner.
  • Sequenced task runner: 任务运行程序,它保证发布到它的任务将按发布顺序运行。每个这样的任务都能看到它之前的任务的副作用,发布到序列任务运行器的任务通常由单个线程(virtual or physical)处理。 In Chrome this is base::SequencedTaskRunner which is-a base::TaskRunner.
  • Single-thread task runner: 一个有序的任务运行程序,它保证所有任务都由相同的物理(physical)线程处理。 In Chrome this is base::SingleThreadTaskRunner which is-a base::SequencedTaskRunner.

Threads

每个 chrome 的进程都包含如下线程:

  • 主线程
    • in the browser process (BrowserThread::UI): updates the UI
    • in renderer processes (Blink main thread): runs most of Blink
  • IO 线程
    • 在所有进程中:所有IPC消息都到达该线程,处理消息的应用程序逻辑可能位于不同的线程中(IO线程可能将消息路由到绑定到不同线程的Mojo接口)
    • 大多数异步 I/O 都发生在这个线程上 (base::FileDescriptorWatcher).
    • 浏览器进程下是BrowserThread::IO
  • 一些专用线程
  • 一个通用线程池

大多数线程都有一个循环,用以从队列中获取任务并运行它们(队列可以在多个线程之间共享)。

Tasks

task 是用以异步执行的工作单元,由base::OnceClosure添加到队列。base::OnceClosure存储着函数指针和参数,通过 Run()来调用所存储的函数指针。base::OnceClosure通过base::BindOnce

来声明。

1
2
3
4
5
void TaskA() {}
void TaskB(int v) {}

auto task_a = base::BindOnce(&TaskA);
auto task_b = base::BindOnce(&TaskB, 42);

执行方式

  • 并行(Parallel):没有任务执行顺序,可能在任何线程上执行所有任务(通过线程池实现)
  • 顺序执行(Sequenced):在任何线程上,按照提交顺序一次执行一个任务(通过 SequencedTaskRunner 实现)
  • 单线程(Single Threaded):按发送顺序执行的任务,在单个线程上一次执行一个任务 (通过 SingleSuqenceTaskRunner 实现)
    • COM Single Threaded:COM已初始化的单线程的变体。

参考:

[1] https://chromium.googlesource.com/chromium/src/+/lkgr/docs/threading_and_tasks.md