Mojo 简要介绍

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

Mojo 的分层

从图中看 Mojo 分 4 层:
1. Mojo Core: Mojo 的实现层,不能独立使用,由 C++ 实现;

2. Mojo System API(C): Mojo 的 C API 层,它和 Mojo Core 对接,可以在程序中独立使用;

3. Mojo System API(C++/Java/JS): Mojo 的各种语言包装层,它将 Mojo C API 包装成多种语言的库,让其他语言可以使用。这一层也可以在程序中独立使用;

4. Mojo Bindings: 这一层引入一种称为 Mojom 的 IDL(接口定义)语言,通过它可以定义通信接口,这些接口会生成接口类,使用户只要实现这些接口就可以使用 Mojo 进行通信,这一层使得 IPC 两端不需要通过原始字节流进行通信,而是通过接口进行通信,有点类似 Protobuf 和 Thrift 。

除了上面提到的那些层之外,在 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 在 Chromium 中的分层


在 Chromium 中,还有两个基础模块使用 Mojo,分别是 Services 和 IPC::Channel。

Mojo 的设计

在使用 Mojo 之前,先来看一下 Mojo 的设计,这对理解后面的使用至关重要。

Mojo 支持在多个进程之间互相通信,这一点和其他的 IPC 有很大不同,其他大多只支持 2 个进程之间进行通信。由 Mojo 组成的这些可以互相通信的进程就形成了一个网络,在这个网络内的任意两个进程都可以进行通信,并且每个进程只能处于一个 Mojo 网络中,在这个网络内每一个进程内部有且只有一个 Node,每一个 Node 可以提供多个 Port,每个 Port 对应一种服务,这点类似 TCP/IP 中的 IP 地址和端口的关系。一个 Node:Port 对可以唯一确定一个服务。Node 和 Node 之间通过 Channel 来实现通信,在不同平台上 Channel 有不同的实现方式,在 Linux 上是 domain socket,在 windows 上是 name pipe,在 MAC OS 平台上是 Mach Port。在 Port 上一层,Mojo 封装了 3 个“应用层协议”,分别为 MessagePipe,DataPipe 和 SharedBuffer(类似在 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 来包装,有了这个 Handle 就可以对它们进行读写操作。还可以通过 MessagePipe 将 MojoHandle 发送到网络中的任意进程。

8. PlatformHandle: 用来包装系统的句柄或文件描述符,可以将它转换为 MojoHandle 然后发送到网络中的任意进程。

MessagePipe

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

Mojo 保证同一个 MessagePipe 中数据的发送顺序和接收顺序一致,但是不保证多个 MessagePipe 之间的数据的有序。

Mojo 模糊了进程边界

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