Chromium 如何实现 IPC

2025-08-06 pv

Chromium 最具标识的架构特点,便是:多进程架构。

浏览器进程、渲染进程、GPU 进程等,各司其职,同时借助操作系统提供的进程隔离性(Isolation),确保浏览器的健壮与安全。

每个进程都有独立的虚拟内存空间,数据共享要借助 IPC(进程间通信,Inter-process Communication)

这很符合 Golang 哲学:

Do not communicate by sharing memory; instead, share memory by communicating.

不要通过共享内存来通信,而应该通过通信来共享内存。

作为经典面试题,常见的 IPC 有以下几种:

  • 信号(Signal)
  • 信号量(Semaphores)
  • 管道(Pipe)
  • 共享内存(Share Memory)
  • 消息队列(Message Queue)
  • 套接字(Socket)

应用在工程上需要一定程度的封装(Encapsulation)。Chromium 构建的 IPC 框架,就是 Mojo

Mojo 的应用范围相对较窄,只在 Chromium 中用到,因此非 Chromium 开发者不必花太多时间钻研。简单了解其产生背景,实现,及设计哲学即可。

1. 背景

在 Chromium 发展早期,使用的是一套叫 IPC::Channel 的 IPC 框架。

这套方案存在以下三个问题:

  1. 性能不佳,序列化和反序列化的效率低下
  2. 缺乏类型系统,容易出错
  3. 缺少跨平台和跨语言的支持

Mojo 的诞生,就是为了解决这些问题。它更现代、类型安全、且方便扩展。

2. 使用

在 Chromium 代码库中,经常会看到 .mojom 为后缀的文件,这便是 Mojo 的核心:接口定义语言(Interface Definition Language, IDL)🔗

类似 Protocol Buffers 的 .proto 文件,它可以定义接口、结构体、枚举和类型,并生成对应的跨平台、跨语言的代码。

举个例子:

example.mojom
module example.mojom;
interface Greeter {
Greet(string name) => (string message);
};

这里定义了一个 Greeter 接口,指定传参和返回值。

基于这份 mojom 定义,Mojo 可将其翻译为 C++(或 JS、Java、Rust 等)可调用的代码,并生成进程间通信所需要的序列化、路由方法等。

Chromium 的 GN 构建系统内置了一套 mojom 的处理方案。可以这样在 BUILD.gn 中声明:

mojom("interfaces") {
sources = [ "example.mojom" ]
}

生成以下文件:

  • example.mojom.h – 接口定义的 C++ 头文件
  • example.mojom.cc – 消息的打包、解包实现
  • example.mojom-shared.h/cc – 公共类型定义
  • example.mojom-forward.h – 前向声明

这些代码中包含客户端 Proxy、服务端 Stub 以及中间的数据传输结构体

如果开发者想使用,只需引入头文件,调用相关方法,即可与其他进程通信,就像是调用本地方法一般。Mojo 屏蔽掉了内部的复杂性。

3. 实现

Mojo 的实现,可以分为 编译 + Runtime 两个部分。

通过 mojom_bindings_generator🔗 工具链将 mojom 文件翻译成接口文件后,真正负责通信的是 Runtime

核心组件如下:

组件作用
mojo::MessagePipe双向、全双工、异步的消息管道(进程内外均可用)
mojo::Connector把 MessagePipe 和接口连接起来(路由)
mojo::InterfacePtr客户端接口,负责发送请求
mojo::Binding / mojo::Receiver服务端绑定,用于接收请求
mojo::PendingReceiver/RemoteModern Mojo 使用的延迟绑定结构
mojo::Dispatcher管理句柄(Handle)和其生命周期

看一下完整的调用过程。

greeter_remote_->Greet("Alice", callback);
  1. 编译生成的 GreeterProxy::Greet() 被调用

    void GreeterProxy::Greet(const std::string& name, GreetCallback callback) {
    mojo::Message message;
    SerializeParams(name, &message);
    receiver_->AcceptWithResponder(message, std::move(callback));
    }
    • 参数被序列化为 mojo::Message 对象
    • receiver_ 是绑定到 MessagePipe 的通信接口
  2. 消息通过 MessagePipe 传输

    mojo::MessagePipe 是一个轻量的、全双工的、异步的消息传输通道,消息通过 Socket、共享内存、平台句柄(如 Windows HANDLE)等方式传输。

  3. 服务端的 Stub::AcceptWithResponder() 被调用

    Mojo 运行时将消息路由到绑定的 GreeterStub

    bool GreeterStub::AcceptWithResponder(Message* message, Responder* responder) {
    std::string name;
    DeserializeParams(message, &name);
    impl_->Greet(name, BindResponder(responder));
    }
    • 参数反序列化为 std::string
    • 调用真正实现类 impl_Greet() 方法
  4. 响应给客户端并调用

    callback.Run("Hello, Alice");

    消息被封装成 mojo::Message 并写入 MessagePipe,再由客户端 Proxy 解包,调用用户的 Callback。

Mojo Core 是 IPC 的关键:

  • 一个用户态 C++ 库,Chromium 在启动时会初始化(位于 mojo/core/
  • 每个进程有一个全局 NodeController 实例,管理连接
  • 通过平台 Channel(如 Unix Socket、Windows Named Pipe)将 MessagePipe 映射到对端进程
  • 句柄(如文件描述符)通过平台特定方法传递(如 sendmsg

4. 对比 Protocol Buffers

既然都是 IDL,就不得不提及 Protocol Buffers。

mojom 和 Protocol Buffers 既有诸多相似,又有很多差异。

首先,它们都是接口定义语言,都支持类型、嵌套,都有代码生成器,可生成跨平台、跨语言代码,而且都实现了异步调用。

两者最大的不同之处在于应用场景:

  • mojom 是用于模块/进程间通信
  • protobuf 则是通用的序列化协议,主要用于网络通信、持久化等

更详细的区别:

项目mojomprotobuf
设计目标Chromium IPC(跨线程、跨进程通信)高效序列化 & 跨系统 RPC(gRPC)
适用范围Chromium 内部广泛应用于各种系统、服务
是否必须运行时支持是,必须依赖 Mojo runtime否,可嵌入到任意项目
支持的调用模式单一方向(客户端-服务端),无服务发现支持服务注册、发现(gRPC)
序列化机制自定义 mojo message 序列化Protocol Buffers 序列化协议
传输通道MessagePipe(socket、shared memory)TCP/HTTP/QUIC 等
二进制兼容性较差(字段增删会破坏 IPC)很强(支持字段编号,前向兼容)
版本控制基本不支持(需谨慎修改)支持良好版本升级和字段变更
可持久化❌ 仅用于 IPC,不能存磁盘✅ 可存为二进制或文本配置

因此,除了在 Chromium 中会使用 mojom 外,其他几乎所有场景,protobuf 都是更优的选择

5. 总结

Mojo 看起来有些复杂,其实目标很清晰:简化并优化进程间通信

简单,一方面意味着使用门槛低,从旧的方案迁移会更容易,另一方面意味着不易出错,即便出错也方便调试。

当然,简单只是表象,背后的封装和实现十分复杂。

这就是这个世界的常态。

你看到的所有现象,看似直白,运作机制也许相当繁琐

小到一个函数、App,大到企业、国家,莫不如是。

我们要做的,就是从看到,到看懂

(完)

参考

  1. Share Memory By Communicating - The Go Programming Language🔗
  2. chromium/docs/mojo_ipc_conversion.md at main · chromium/chromium · GitHub🔗
  3. Protocol Buffers Documentation🔗
在 GitHub 上编辑本页面

最后更新于: 2025-08-07T02:13:26+08:00