IPC Overview

由于 Chromium 是多进程架构,这就意味着各个进程之间需要互相通信进行消息同步,主要的原始通信手段便是管道。每个renderer 进程都有一个管道,用来和 borwser 进行通信。管道以异步模式使用,以确保任何一端在等待另一端时都不会被阻塞。

注意:所有的网络通信都是由浏览器的主进程来处理的。

在 chromium 的多进程架构下可以分为 3 层看,最底层是 Blink,是负责渲染页面的引擎。中间层是Renderer,每个标签页都含有一个 renderer,每个 renderer 进程都有一个 Blink 实例。最顶层是浏览器进程,管理所有的 renderer,控制所有的网络访问。

IPC in the browser

  • 浏览器与渲染器的通信是在一个单独的 I/O 线程中完成的。

  • 浏览器与 views 的通信,必须由ChannelProxy代理转发到主线程。如此,例如网页资源请求这种最常见、关系性能的信息可以在 I/O 线程上处理,而不会阻塞用户界面。

  • 这些是通过使用由 RenderProcessHost 插入到通道中的 ChannelProxy::MessageFilter 来完成的

  • ChannelProxy::MessageFilter在 I/O 线程中运行,拦截资源请求消息,并将其直接转发给资源调度程序主机(resource dispatcher host)

IPC in the renderer

  • 每个 renderer 都有一个单独的线程来管理通信,另一个线程来完成渲染和其他操作处理。

  • 大多数消息通过主渲染器线程从浏览器发送到WebKit线程,反之亦然。这个额外的线程是为了支持同步 renderer-to-browser 消息。

Types of messages

Chromium 的消息类别主要有两种: routed control

Control messages 由创建管道的类处理。有时,该类将允许其他listeners通过 MessageRouter 对象接收消息,其他 listeners 可以注册该对象,并接收使用其唯一id发送的 routed 消息。

Routed messages 用于将消息发送到特定的 RenderViewHost。不过从技术上讲,任何类都可以通过使用 RenderProcessHost::GetNextRoutingID 接收路由消息,并通过调用RenderProcessHost::AddRoute 注册自己。目前,RenderViewHost 和 RenderFrameHost 实例都有自己的路由 id。

浏览器和渲染器之间的通信是和消息类型无关的,从浏览器发送到渲染器的与 document’s frame 相关的消息称为 frame 消息,它们被发送到 RenderFrame。类似的,从渲染器发送到浏览器的消息称为FrameHost消息,它们被发送到 RenderFrameHost。

插件也有独立的进程。像渲染消息一样,有 PluginProcess 消息(从浏览器发送到插件进程)和 PluginProcessHost 消息(从插件进程发送到浏览器)。

Mojo

在新的文档?发现这样一句话, **Legacy IPC is deprecated.**所以原文档后面的一些关于 messages 的处理和声明部分便没有继续观看。在网上找到一篇总结很好的文章,这里引用一下。

Overview

Mojo 是一个跨平台 IPC 框架,它诞生于 chromium ,用来实现 chromium 进程内/进程间的通信。目前,它也被用于 ChromeOS。

Mojo 系统 API 提供了一套低等级的 IPC 原语:message pipes, data pipes, shared buffers.

关于message pipes:消息管道是一种轻量级原语,用于双向传输相对较小的数据包。该管道有两个端点,任意一个端点都可以通过另一个消息管道进行传输。因为浏览器进程和每个子进程之间都存在一个原始的消息管道,用户同样可以创建一个新管道并且发送数据到任意进程的任意的管道的任意一端,并且该通道两端可以无缝独占互相通信。

mojo 术语

  • Message Pipe: 每个消息管道都有两个端点,一对端点和任一端点可以通过另一个消息管道来传输信息。因为浏览器进程和每个子进程之间引导了一个原始消息管道,这意味着我们创建的新管道将任一端发送到任何进程,并且两端仍然能够无缝且排他地相互通信。
  • Mojom file: 定义接口,它们是强类型的消息集合。 每个接口消息大致类似于单个原型消息
  • Remote: 通过接口发送信息数据
  • Receiver: 用于接收Remote发送的接口消息
  • PendingRemote: 用于容纳 Receiver 管道另一端的类型化容器。
  • PendingReceiver: 用于容纳 Remote 管道另一端的类型化容器。
  • AssociatedRemote/Receiver: 类似于遥控器和接收器。 但是,它们在单个消息管道上的多个接口上运行,同时保留消息顺序,因为 AssociatedRemote/Receiver 是通过使用传统 IPC 消息使用的 IPC::Channel 实现的。

mojo 架构

  • Mojo Core:mojo 的核心层(由 C++ 实现).每个使用Mojo进行进程间通信的进程被称之为Mojo embedder,这个进程需要链接 Core代码。Mojo Core 针对不同的系统实现具体IPC通信机制。
  • Mojo System API(C): Mojo 的公共的 C 语言 API 合集,mojo 初始化完成后,任意进程可调用。但该API几乎不会直接调用,它是构建更高级别Mojo API的基础。它提供了消息管道,数据管道,共享buffer等创建及交互的Mojo API,以及进程间的引导连接API。
  • Mojo System API(C++/Java/JS):Mojo 的各种语言包装层,它将 Mojo C API 包装成多种语言的库,让其他语言可以使用。
  • Mojo Bindings:这一层引入一种称为 Mojom 的 IDL(接口定义)语言,通过它可以定义通信接口,这些接口会生成接口类,使用户只要实现这些接口就可以使用 Mojo 进行通信,这一层使得IPC两端不需要通过原始字节流进行通信,而是通过接口进行通信。

除了上面提到的那些层之外,在 Chromium 中还有2个模块对 Mojo 进行了包装,分别是 Services(//services) 模块和 IPC(//ipc) 模块。

  1. Services: 一种更高层次的IPC机制,构建于Mojo之上,以Service的级别来进行IPC通信,Chromium大量使用这种IPC机制来包装各种服务,用来取代 Legacy Chrome IPC,比如device服务,preferences服务,audio服务,viz服务等。
  2. Legacy Chrome IPC: 已经不推荐使用的Chrome IPC机制,提供 IPC::Channel 接口以及大量的使用宏来定义的 messages 类。目前它底层也是基于 Mojo 来实现的,但是上层接口和旧的 Chrome IPC 保持一致。chromium 中还有很多IPC使用这种方式,但是不应该在新的服务中使用这种机制。可以在ipc/ipc_message_start.h中查看还有哪些部分在使用这种IPC机制。

设计模式

Mojo 支持在多个进程之间互相通信,这一点和其他的IPC有很大不同,其他大多只支持2个进程之间进行通信。由Mojo组成的这些可以互相通信的进程就形成了一个网络,在这个网络内的任意两个进程都可以进行通信,并且每个进程只能处于一个Mojo网络中,在这个网络内每一个进程内部有且只有一个Node,每一个Node可以提供多个Port,每个Port对应一种服务,这点类似TCP/IP中的IP地址和端口的关系。一个Node:Port对可以唯一确定一个服务。NodeNode之间通过Channel来实现通信,在不同平台上Channel有不同的实现方式,在Linux上是domain socket,在windows上是name pipe,在MAC OS平台上是 Mach Port。在Port上一层,Mojo封装了3个“应用层协议”,分别为MessagePipeDataPipeSharedBuffer(类似在TCP上封装了HTTP,SMTP等)。整体结构如下图:

上图展示了在两个进程间使用Mojo的数据流。它有以下几个特点:

  1. Channel: Mojo内部的实现细节,对外不可见,用于包装系统底层的通信通道,在Linux下是domain socket,Windows下是name pipe,MAC OS下是mach port;
  2. Node: 每个进程只有一个Node,它在Mojo中的作用相当于TCP/IP中的IP地址,同样是内部实现细节,对外不可见;
  3. Port: 每个进程可以有上百万个Port,它在Mojo中的作用相当于TCP/IP中的端口,同样是内部实现细节,对外不可见,每个Port都必定会对应一种应用层接口,目前Mojo支持三种应用层接口;
  4. MessagePipe: 应用层接口,用于进程间的双向通信,类似UDP,消息是基于数据包的,底层使用Channel通道;
  5. DataPipe: 应用层接口,用于进程间单向块数据传递,类似TCP,消息是基于数据流的,底层使用系统的Shared Memory实现;
  6. SharedBuffer: 应用层接口,支持双向块数据传递,底层使用系统Shared Memory实现;
  7. MojoHandle: 所有的 MessagePipe,DataPipe,SharedBuffer 都使用MojoHandle来包装,有了这个Hanle就可以对它们进行读写操作。还可以通过MessagePipe将MojoHandle发送到网络中的任意进程。
  8. PlatformHanle: 用来包装系统的句柄或文件描述符,可以将它转换为MojoHandle然后发送到网络中的任意进程

MessagePipe

一个进程中可以有N多个MessagePipe,所有的MessagePipe都共享底层的一条通信通道,就像下图这样:

需要特别说明的是,Mojo不是只能在不同进程间使用,它从一开始就考虑了在单进程中使用的场景,并且有专门的优化,因此,使用Mojo带来的一个额外好处是,在Mojo的一端进行读写不必知道另一端是运行在当前进程还是外部进程,这非常有利于将单进程程序逐步的使用Mojo拆分为多进程程序,并且可以在调试的时候使用单进程方便调试,在正式环境中使用多进程缩小程序崩溃时的影响范围

参考

[1] http://dev.chromium.org/developers/design-documents/multi-process-resource-loading

[2] https://chromium.googlesource.com/chromium/src.git/+/51.0.2704.48/docs/mojo_in_chromium.md

[3] https://keyou.github.io/blog/2020/01/03/Chromium-Mojo&IPC/

[4] https://gclxry.com/article/chromium-new-inter-process-communication-system-mojo-and-servicification/