我们继续分析Chromium的Mojo模块。
Dispatcher
Dispatcher
是 Mojo IPC 系统中的一个关键概念。它是一个虚基类类(或接口),用于实现与特定 MojoHandle
相关联的 Mojo 核心 API 调用。在 Mojo 系统中,应用程序通过这些 API 与各种类型的 IPC 机制进行交互,如消息管道、共享缓冲区、数据管道和事件观察器。
每个 MojoHandle
是系统中某个 Dispatcher
实现的不透明引用。这意味着,当你在应用程序中持有一个 MojoHandle
,实际上你是在引用一个背后具体实现了 Dispatcher
接口的对象。这个对象负责处理与该 MojoHandle
关联的所有操作,例如发送和接收消息、管理共享内存或者控制数据流。
简而言之,Dispatcher
是 Mojo 系统中将句柄抽象化并连接到具体功能的胶合层。
Dispatcher的主要虚函数如下:
class MOJO_SYSTEM_IMPL_EXPORT Dispatcher
: public base::RefCountedThreadSafe<Dispatcher> {
public:
enum class Type {
UNKNOWN = 0,
MESSAGE_PIPE,
DATA_PIPE_PRODUCER,
DATA_PIPE_CONSUMER,
SHARED_BUFFER,
WATCHER,
INVITATION,
// "Private" types (not exposed via the public interface):
PLATFORM_HANDLE = -1,
};
Dispatcher(const Dispatcher&) = delete;
Dispatcher& operator=(const Dispatcher&) = delete;
// TODO(crbug.com/40778522): Remove these and all callers.
//
// The assert is invoked at various points of handle deserialization failure.
// Such failures are expected and innocuous when destroying unread or unsent,
// discarded messages with attachments that may no longer be valid; but they
// are problematic when hit during normal message deserialization for messages
// the application expects to read and dispatch. Both this setter and the
// assertion are concerned only with their calling thread.
static void SetExtractingHandlesFromMessage(bool extracting);
static void AssertNotExtractingHandlesFromMessage();
// All Dispatchers must minimally implement these methods.
virtual Type GetType() const = 0;
virtual MojoResult Close() = 0;
/ Watcher API
// Supports the |MojoAddTrigger()| API if implemented by this Dispatcher.
// |dispatcher| is the resolved Dispatcher implementation from the given
// MojoHandle to watch. The remaining arguments correspond directly to
// arguments on the original |MojoAddTrigger()| API call. See
// |MojoAddTrigger()| documentation.
virtual MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
MojoHandleSignals signals,
MojoTriggerCondition condition,
uintptr_t context);
// Supports the |MojoRemoveTrigger()| API if implemented by this Dispatcher.
// Arguments correspond directly to arguments on the original
// |MojoRemoveTrigger()| API call. See |MojoRemoveTrigger()| documentation.
virtual MojoResult CancelWatch(uintptr_t context);
// Supports the |MojoArmTrap()| API if implemented by this Dispatcher.
// Arguments correspond directly to arguments on the original |MojoArmTrap()|
// API call. See |MojoArmTrap()| documentation.
virtual MojoResult Arm(uint32_t* num_blocking_events,
MojoTrapEvent* blocking_events);
/ Message pipe API /
// Supports the |MojoWriteMessage()| API if implemented by this Dispatcher.
// |message| is the message object referenced by the MojoMessageHandle passed
// to the original API call. See |MojoWriteMessage()| documentation.
virtual MojoResult WriteMessage(
std::unique_ptr<ports::UserMessageEvent> message);
// Supports the |MojoReadMessage()| API if implemented by this Dispatcher.
// If successful, |*message| contains a newly read message object, which will
// be yielded to the API caller as an opaque MojoMessageHandle value. See
// |MojoReadMessage()| documentation.
virtual MojoResult ReadMessage(
std::unique_ptr<ports::UserMessageEvent>* message);
/ Shared buffer API /
// Supports the |MojoDuplicateBufferHandle()| API if implemented by this
// Dispatcher.
//
// |options| may be null. |new_dispatcher| must not be null, but
// |*new_dispatcher| should be null (and will contain the dispatcher for the
// new handle on success).
//
// See |MojoDuplicateBufferHandle()| documentation.
virtual MojoResult DuplicateBufferHandle(
const MojoDuplicateBufferHandleOptions* options,
scoped_refptr<Dispatcher>* new_dispatcher);
// Supports the |MojoMapBuffer()| API if implemented by this Dispatcher.
// |offset| and |num_bytes| correspond to arguments given to the original API
// call. On success, |*mapping| will contain a memory mapping that Mojo Core
// will internally retain until the buffer is unmapped by |MojoUnmapBuffer()|.
// See |MojoMapBuffer()| documentation.
virtual MojoResult MapBuffer(
uint64_t offset,
uint64_t num_bytes,
std::unique_ptr<PlatformSharedMemoryMapping>* mapping);
// Supports the |MojoGetBufferInfo()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoGetBufferInfo()| documentation.
virtual MojoResult GetBufferInfo(MojoSharedBufferInfo* info);
/ Data pipe consumer API /
// Supports the the |MojoReadData()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoReadData()| documentation.
virtual MojoResult ReadData(const MojoReadDataOptions& options,
void* elements,
uint32_t* num_bytes);
// Supports the the |MojoBeginReadData()| API if implemented by this
// Dispatcher. Arguments correspond to the ones given to the original API
// call. See |MojoBeginReadData()| documentation.
virtual MojoResult BeginReadData(const void** buffer,
uint32_t* buffer_num_bytes);
// Supports the the |MojoEndReadData()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoEndReadData()| documentation.
virtual MojoResult EndReadData(uint32_t num_bytes_read);
/ Data pipe producer API /
// Supports the the |MojoWriteData()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoWriteData()| documentation.
virtual MojoResult WriteData(const void* elements,
uint32_t* num_bytes,
const MojoWriteDataOptions& options);
// Supports the the |MojoBeginWriteData()| API if implemented by this
// Dispatcher. Arguments correspond to the ones given to the original API
// call. See |MojoBeginWriteData()| documentation.
virtual MojoResult BeginWriteData(void** buffer,
uint32_t* buffer_num_bytes,
MojoBeginWriteDataFlags flags);
// Supports the the |MojoEndWriteData()| API if implemented by this
// Dispatcher. Arguments correspond to the ones given to the original API
// call. See |MojoEndWriteData()| documentation.
virtual MojoResult EndWriteData(uint32_t num_bytes_written);
// Supports the |MojoAttachMessagePipeToInvitation()| API if implemented by
// this Dispatcher. Arguments correspond to the ones given to the original API
// call. See |MojoAttachMessagePipeToInvitation()| documentation.
virtual MojoResult AttachMessagePipe(std::string_view name,
ports::PortRef remote_peer_port);
// Supports the |MojoExtractMessagePipeFromInvitation()| API if implemented by
// this Dispatcher. Arguments correspond to the ones given to the original API
// call. See |MojoExtractMessagePipeFromInvitation()| documentation.
virtual MojoResult ExtractMessagePipe(std::string_view name,
MojoHandle* message_pipe_handle);
// Supports the |MojoSetQuota()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoSetQuota()| documentation.
virtual MojoResult SetQuota(MojoQuotaType type, uint64_t limit);
// Supports the |MojoQueryQuota()| API if implemented by this Dispatcher.
// Arguments correspond to the ones given to the original API call. See
// |MojoQueryQuota()| documentation.
virtual MojoResult QueryQuota(MojoQuotaType type,
uint64_t* limit,
uint64_t* usage);
/ General-purpose API for all handle types /
// Gets the current handle signals state. (The default implementation simply
// returns a default-constructed |HandleSignalsState|, i.e., no signals
// satisfied or satisfiable.) Note: The state is subject to change from other
// threads.
virtual HandleSignalsState GetHandleSignalsState() const;
// Adds a WatcherDispatcher reference to this dispatcher, to be notified of
// all subsequent changes to handle state including signal changes or closure.
// The reference is associated with a |context| for disambiguation of
// removals.
virtual MojoResult AddWatcherRef(
const scoped_refptr<WatcherDispatcher>& watcher,
uintptr_t context);
// Removes a WatcherDispatcher reference from this dispatcher.
virtual MojoResult RemoveWatcherRef(WatcherDispatcher* watcher,
uintptr_t context);
// Informs the caller of the total serialized size (in bytes) and the total
// number of platform handles and ports needed to transfer this dispatcher
// across a message pipe.
//
// Must eventually be followed by a call to EndSerializeAndClose(). Note that
// StartSerialize() and EndSerialize() are always called in sequence, and
// only between calls to BeginTransit() and either (but not both)
// CompleteTransitAndClose() or CancelTransit().
//
// For this reason it is IMPERATIVE that the implementation ensure a
// consistent serializable state between BeginTransit() and
// CompleteTransitAndClose()/CancelTransit().
virtual void StartSerialize(uint32_t* num_bytes,
uint32_t* num_ports,
uint32_t* num_platform_handles);
// Serializes this dispatcher into |destination|, |ports|, and |handles|.
// Returns true iff successful, false otherwise. In either case the dispatcher
// will close.
//
// NOTE: Transit MAY still fail after this call returns. Implementations
// should not assume PlatformHandle ownership has transferred until
// CompleteTransitAndClose() is called. In other words, if CancelTransit() is
// called, the implementation should retain its PlatformHandles in working
// condition.
virtual bool EndSerialize(void* destination,
ports::PortName* ports,
PlatformHandle* handles);
// Does whatever is necessary to begin transit of the dispatcher. This
// should return |true| if transit is OK, or false if the underlying resource
// is deemed busy by the implementation.
virtual bool BeginTransit();
// Does whatever is necessary to complete transit of the dispatcher, including
// closure. This is only called upon successfully transmitting an outgoing
// message containing this serialized dispatcher.
virtual void CompleteTransitAndClose();
// Does whatever is necessary to cancel transit of the dispatcher. The
// dispatcher should remain in a working state and resume normal operation.
virtual void CancelTransit();
// Deserializes a specific dispatcher type from an incoming message.
static scoped_refptr<Dispatcher> Deserialize(Type type,
const void* bytes,
size_t num_bytes,
const ports::PortName* ports,
size_t num_ports,
PlatformHandle* platform_handles,
size_t platform_handle_count);
protected:
friend class base::RefCountedThreadSafe<Dispatcher>;
Dispatcher();
virtual ~Dispatcher();
看到了ReadMessage、WriteMessage、MapBuffer等高级Mojo的原语,另外看到了一组消息类型:
enum class Type {
UNKNOWN = 0,
MESSAGE_PIPE,
DATA_PIPE_PRODUCER,
DATA_PIPE_CONSUMER,
SHARED_BUFFER,
WATCHER,
INVITATION,
// "Private" types (not exposed via the public interface):
PLATFORM_HANDLE = -1,
};
跟随线索可以发现这些消息的Dispatcher:
MessagePipeDispatcher
: 管理消息管道的Dispatcher
,它允许两个 Mojo 句柄之间传递消息。SharedBufferDispatcher
: 管理共享内存缓冲区的Dispatcher
,允许跨 Mojo 句柄共享内存。DataPipeConsumerDispatcher
: 管理数据管道的消费端的Dispatcher
,它允许从数据管道读取数据。DataPipeProducerDispatcher
: 管理数据管道的生产端的Dispatcher
,它允许向数据管道写入数据。WatcherDispatcher
: 管理事件观察的Dispatcher
,通常用于异步通知某些事件发生。InvitationDispatcher
: 管理进程间邀请的Dispatcher
,用于建立进程间的连接和通信。
通过这些不同的 Dispatcher
实现,Mojo IPC 提供了一个多样化和灵活的方式来处理跨进程通信的各种需求。
Mojo的Message
和Message相关的源文件(主线索):
先看Message.h的Message类:
这个
Message
类在 Mojo IPC 系统中的作用是封装要通过消息管道 (MessagePipe
)
发送的数据和句柄。Message
对象拥有自己的数据和句柄,并且允许消费者(即消息的接收者)更改这些数据和句柄。消息的数据由一个头部和随后的有效载荷组成。下面是
Message
类的主要特点和功能:
标志位 (
kFlagExpectsResponse
,kFlagIsResponse
, 等): 这些常量定义了消息的不同行为,如是否期望响应、是否是响应消息、是否是同步消息等。构造函数:
Message
类提供了多个构造函数,用于创建不同类型的消息。有的构造函数用于创建未初始化的消息,有的用于创建已序列化的消息对象,还有的用于从现有的消息句柄创建消息。移动构造函数和移动赋值运算符 (
Message(Message&& other)
和operator=(Message&& other)
): 允许Message
对象之间的移动语义,这样可以有效地在不同的上下文中传递消息,而不需要复制整个消息内容。Reset 方法: 将
Message
对象重置为未初始化状态,这样它就不再包含任何数据或句柄。IsNull 方法: 检查消息是否未初始化。
IsValid 方法: 检查消息是否处于有效状态。一条消息如果在构建过程中遇到部分反序列化失败,则可能处于无效状态。
is_serialized 方法: 检查消息是否已序列化。
数据访问方法 (
data
,mutable_data
,data_num_bytes
): 提供对消息数据的只读和可写访问,以及查询消息数据的字节大小。
Message
类是 Mojo IPC
的核心组件之一,它允许以一种结构化和类型安全的方式来封装和传输数据。通过序列化和反序列化机制,Message
在进程间的通信中起着桥梁的作用,确保数据和句柄的正确传递和解析。
Message这个类在BindingBase工程中,是为Binding服务的。接下来我们看看Binding原理。
Mojo的Binding
之前分析鼠标消息的时候,已经初步接触到了Binding的一些细节。
为了实现高级跨进程通信抽象,Mojom会通过编译一个.mojom的源文件,生成对应的客户端和服务端代码,使其可以像进程内普通对象一样调用。
我们以compositor_frame_sink.mojom为例。
compositor_frame_sink.mojom的定义如下:
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module viz.mojom;
import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/shared_memory.mojom";
import ... 略
// Tags the frame sink with the type of source producing its content.
enum CompositorFrameSinkType {
kUnspecified,
kVideo,
kMediaStream,
kLayerTree,
};
// A CompositorFrameSink is an interface for receiving CompositorFrame
// structs. A CompositorFrame contains the complete output meant for display.
// Each time a client has a graphical update, and receives an OnBeginFrame, it
// is responsible for creating a CompositorFrame to update its portion of the
// screen.
interface CompositorFrameSink {
// Lets the display compositor know that the client wishes to receive the next
// BeginFrame event.
SetNeedsBeginFrame(bool needs_begin_frame);
//...略
}
//...略
mojom 的语法和C++26非常相似,这也是让Chromium开发者几乎不用任何学习就可以编写mojom的代码。
通过 mojom_bindings_generator.py,mojom 会生成2个源文件,4个关键类:
- CompositorFrameSink
- CompositorFrameSinkStub
- CompositorFrameSinkProxy
- CompositorFrameSinkStubDispatch
这四个类展开来说:
CompositorFrameSink: 这是由
.mojom
文件定义的接口转换而成的一个抽象基类。它定义了接口的方法,但不提供具体的实现。服务端(Impl 端)需要提供一个派生自
CompositorFrameSink
的具体类,实现所有的虚拟方法。客户端(Proxy 端)会使用这个接口与服务端通信。CompositorFrameSinkStub: 这是一个存根类,其作用是接收传入的消息并将它们转换为对应的
CompositorFrameSink
方法调用。在服务端,每当一个消息到达时,CompositorFrameSinkStub
会将消息反序列化,然后调用在CompositorFrameSink
实现类中对应的方法。Stub
类通常由 Mojo
工具链自动生成,并与一个 Mojo 句柄关联,以便监听进入的消息。CompositorFrameSinkProxy: 这是客户端的代理类,它实现了
CompositorFrameSink
接口。客户端通过调用CompositorFrameSinkProxy
的方法,将方法调用转换为消息,并将这些消息发送到服务端。Proxy
类负责序列化方法调用的参数,创建消息,并通过 Mojo
消息管道将其发送出去。Proxy
类也是由 Mojo 工具链自动生成的。CompositorFrameSinkStubDispatch: 这是一个辅助类,其作用是根据接收到的消息确定应该调用
CompositorFrameSink
的哪个具体方法。它通常包含一个静态方法,比如Accept
或者
AcceptWithResponder
,这些方法通过检查消息中的方法 ID 和解析参数来分发调用。StubDispatch
常常在
CompositorFrameSinkStub
内部使用,作为消息分发机制的一部分。总的来说,这些类共同协作,为 Mojo 接口的调用提供了一个完整的生命周期管理:
- 客户端: 通过
CompositorFrameSinkProxy
发送消息。- 服务端: 通过
CompositorFrameSinkStub
接收消息,并通过CompositorFrameSinkStubDispatch
分发到具体的CompositorFrameSink
实现。- 服务端实现: 实现
CompositorFrameSink
接口的具体业务逻辑。这样的设计模式使得开发者可以专注于实现业务逻辑,而无需关心底层的消息传输和序列化细节。
为了验证这个过程,我们接下来可以分别打两个断点。
首先看客户端这边的,在CompositorFrameSinkProxy下断点,堆栈如下
序列化主要涉及参数的序列化,关键代码(生成的代码)如下:
截图中看到了MessageFragment类,也简单翻译一下这个类的说明:
MessageFragment
类在 Mojo IPC (Inter-Process Communication)
系统中的作用是为消息对象 (Message
)中的序列化代码提供一个通用接口,用于分配、初始化,并方便地访问对齐的数据块。MessageFragment
对应于消息中的一个逻辑数据元素,例如结构体(struct)、字段(field)、数组、数组元素等。
MessageFragment
在构造时配置为具有部分序列化的Message
。最初,MessageFragment
是空的,不引用任何有效的内存区域。要使用
data()
或operator->
访问数据,必须首先在消息中分配一块内存。这可以通过调用Allocate()
方法来完成,该方法在消息有效载荷的末尾追加sizeof(T)
字节,并控制这些字节;或者通过调用Claim()
方法来完成,它接受消息有效载荷中的现有指针,并控制该消息偏移处的前sizeof(T)
字节。无论使用哪种方式,都会在声明的字节上构造一个新的T
对象,之后可以使用这个MessageFragment
读取或修改它。对于数组类型,使用这个类的特化版本(在下面定义),并且必须调用
AllocateArrayData()
方法来分配和声明消息中的空间。总结一下,
MessageFragment
的主要用途和功能包括:
- 为消息中的逻辑数据元素提供内存分配和初始化。
- 通过
Allocate()
方法在消息末尾追加数据并控制该数据。- 通过
Claim()
方法来接管消息中已存在的内存区域。- 提供对分配内存的直接访问,允许序列化代码读取和修改数据。
- 对于数组类型数据,提供特化的分配和访问机制。
到了服务端,在Impl对应的函数上断点:
CompositorFrameSinkImpl 是 CompositorFrameSink 接口的服务端具体实现类。在 Mojo IPC 系统中,当服务端需要提供 CompositorFrameSink 接口的具体实现时,它会实现一个类似于 CompositorFrameSinkImpl 的类。这个类的职责包括:
- 实现接口:实现 CompositorFrameSink 定义的所有方法,例如处理客户端的 SubmitCompositorFrame调用。
- 处理逻辑:包含处理提交的合成帧的逻辑,这可能涉及到合成操作、资源管理和与硬件加速图形系统的交互。
- Mojo 绑定:通过 Mojo绑定与客户端通信,接收请求和发送响应。
参数的解包比较简单,通过CompositorFrameSink_SetNeedsBeginFrame_Params_Data和CompositorFrameSink_SetNeedsBeginFrame_ParamsDataView即可从payload中获取参数:
可以印证前面的结论。
实例的创建
那么,在Prox的调用,通过指定方法ID,并将参数序列化,通过Message,将调用信息发送到Impl端,Impl端收到Message后经过Dispatch等一系列的路由,到了xxxStub,最终到了Impl,再到CompositorFrameSinkSupport。
那么,客户端(Prox)是如何创建相应的实例呢?服务端(Impl)又是如何跟xxxStub
绑定的呢?带着疑问,我们在构造函数打断点,并得出以下结论:
在 Mojo IPC
系统中,客户端(Proxy)和服务端(Impl)通过一系列的步骤来创建实例和建立绑定。这些步骤涉及到接口的定义、代理和存根的生成、实例的创建、消息的发送与接收、以及最终的方法调用。下面是如何在客户端创建代理实例和服务端绑定存根的详细步骤:客户端 (Proxy) 创建实例的过程:
定义接口:首先,需要在
.mojom
文件中定义一个 Mojo 接口,这个接口包含了可供调用的方法。生成代理和存根:使用
mojom_bindings_generator.py
脚本根据.mojom
文件生成代理(Proxy)和存根(Stub)的源代码。创建代理实例:在客户端代码中,使用生成的
CompositorFrameSinkProxy
类创建一个代理实例。这通常涉及到创建一个InterfacePtr<CompositorFrameSink>
,它是一个智能指针,管理对
CompositorFrameSinkProxy
实例的引用。建立连接:客户端通过 Mojo 的绑定机制建立与服务端的连接。这通常涉及到调用一些形式的
Bind
方法,它接收一个InterfaceRequest
,这是一个未完成的连接请求。以CompositorFrameSink为例,客户端持有一个mojo::AssociatedRemote<viz::mojom::CompositorFrameSink> compositor_frame_sink_associated_;
指针,这个指针通过绑定mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink> compositor_frame_sink_associated_remote;
之后,即可开始调用。代码如下:发送消息:客户端通过代理实例调用
CompositorFrameSink
接口的方法。代理会序列化这些方法调用和参数,并通过消息管道发送到服务端。服务端 (Impl) 绑定存根的过程:
实现接口:服务端实现了在
.mojom
文件中定义的CompositorFrameSink
接口。这个实现通常是CompositorFrameSinkImpl
类。创建实现实例并绑定Impl:服务端创建
CompositorFrameSinkImpl
的实例,这个实例包含了接口方法的具体逻辑。其构造函数完成了于CompositorFrameSinkImpl与CompositorFrameSink的绑定
绑定的本质是响应来自客户端的请求,请求里带了两个关键参数:
mojo::PendingReceiver<mojom::CompositorFrameSink> receiver, mojo::PendingRemote<mojom::CompositorFrameSinkClient> client
创建并绑定存根:服务端使用生成的
CompositorFrameSinkStub
类,并将其与InterfaceRequest
绑定。这个Stub
对象负责监听进入的消息,并将它们分派到
CompositorFrameSinkImpl
实例的方法中。处理和分派消息:每当服务端的
Stub
收到消息时,它将消息反序列化并使用CompositorFrameSinkStubDispatch
将调用分派到CompositorFrameSinkImpl
的适当方法上。通过上述步骤,客户端和服务端可以建立起一套 IPC 机制,并通过 Mojo
接口进行清晰和高效的跨进程通信。客户端的代理实例可以将方法调用转换为消息并发送,而服务端的存根实例则负责接收消息、分派调用和执行具体的业务逻辑。
对了,Chromium多进程模型对打断点很不友好,只需要增加命令行参数–single-process即可以单进程模式运行,这样就不会错过断点了。
另外,直接跑Chromium.exe会有很多Chromium应用层的逻辑,很重,可以改为直接调试content_shell.exe。
为什么Mojo不用ProtoBuffer,反而要自己设计一套序列化和消息定义?
Mojo 是一个为 Chromium 项目量身定制的 IPC(Inter-Process Communication,跨进程通信)系统。它不仅包含了序列化和反序列化的能力,还提供了一整套用于高效跨进程消息传递和接口定义的机制。Mojo 的设计在满足 Chromium 特定需求的同时,还解决了一些 Protobuf 在这种用例中可能面临的局限性:
-
零拷贝传输:
Mojo 专注于支持大数据量的高效传输,包括对共享内存和跨进程直接内存访问的支持。这种零拷贝传输方式对于浏览器中图形和媒体相关的数据非常重要,可以最小化延迟和CPU开销。 -
句柄和资源的传递:
Mojo 允许在进程间传递操作系统句柄(如文件、共享内存段和同步原语)。Protobuf 不支持这种复杂的句柄传递,而这对于浏览器中的很多操作是必需的。 -
同步和异步消息的支持:
Mojo 支持同步和异步消息模式。在某些情况下,同步调用对于保持状态的一致性和避免竞态条件是必要的,而 Protobuf 本身并不处理消息的传递机制。 -
接口定义和版本控制:
Mojo 允许通过.mojom
文件定义清晰的接口和它们的方法,这些定义非常适合用于生成各种语言的绑定和接口实现。Mojo 还支持接口的版本控制,允许向后兼容的接口演进。 -
性能和资源利用:
Mojo 为高性能 IPC 而设计,特别考虑了低延迟和高吞吐量的需求。在浏览器环境中,性能和资源利用是关键考量因素。 -
定制化需求:
Chromium 项目有特定的需求,无法通过使用通用的序列化库(如 Protobuf)来满足。Mojo 设计为可以与 Chromium 的其他部分紧密集成,提供了更多的灵活性和控制。 -
安全性:
Mojo 在设计时就考虑了沙盒和安全性,这对 Chromium 这样处理大量不可信输入的项目至关重要。
总结来说,虽然 Protobuf 是一个强大的序列化工具,用于多种用途和多种编程语言,但是 Mojo 是专门为 Chromium 这样的大型、性能敏感的项目设计的。Mojo 通过提供一套更为细致的、针对性的 IPC 机制来满足 Chromium 的特定需求,并解决 Protobuf 在某些场景下的局限性。
关于Mojo的更多资料。
建议读者直接阅读mojo的readme文档,了解更多设计细节。
最后,摘录一段ReadMe文档说明建立连接的应用层调用方法:
邀请
邀请是两个进程之间引导 Mojo IPC 的手段。邀请必须通过某些特定于平台的 IPC
原语(例如Windows 命名管道或 UNIX 域套接字)进行传输,公共平台支持库为这些原语提供了一些轻量级、跨平台的抽象。
对于任何两个希望建立连接的进程,一个进程必须发送 ,OutgoingInvitation而另一个进程必须接受IncomingInvitation。发送方可以将命名消息管道句柄附加到OutgoingInvitation,而
接收方可以从其 中提取它们IncomingInvitation。
当一个进程负责启动另一个进程时,基本用法可能看起来像这样。
```cpp
#include "base/command_line.h"
#include "base/process/launch.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "mojo/public/cpp/system/message_pipe.h"
mojo::ScopedMessagePipeHandle LaunchAndConnectSomething() {
// Under the hood, this is essentially always an OS pipe (domain socket pair,
// Windows named pipe, Fuchsia channel, etc).
mojo::PlatformChannel channel;
mojo::OutgoingInvitation invitation;
// Attach a message pipe to be extracted by the receiver. The other end of the
// pipe is returned for us to use locally.
mojo::ScopedMessagePipeHandle pipe =
invitation->AttachMessagePipe("arbitrary pipe name");
base::LaunchOptions options;
base::CommandLine command_line("some_executable")
channel.PrepareToPassRemoteEndpoint(&options, &command_line);
base::Process child_process = base::LaunchProcess(command_line, options);
channel.RemoteProcessLaunchAttempted();
OutgoingInvitation::Send(std::move(invitation), child_process.Handle(),
channel.TakeLocalEndpoint());
return pipe;
}
启动的进程可以依次接受IncomingInvitation:
```cpp
#include "base/command_line.h"
#include "base/threading/thread.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "mojo/public/cpp/system/message_pipe.h"
int main(int argc, char** argv) {
// Basic Mojo initialization for a new process.
mojo::core::Init();
base::Thread ipc_thread("ipc!");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
// Accept an invitation.
mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
*base::CommandLine::ForCurrentProcess()));
mojo::ScopedMessagePipeHandle pipe =
invitation->ExtractMessagePipe("arbitrary pipe name");
// etc...
return GoListenForMessagesAndRunForever(std::move(pipe));
}
现在我们已经在两个进程之间初始化了IPC。
还请记住,绑定接口只是带有一些语义和语法糖的消息管道,因此您可以将这些原始
消息管道句柄用作 mojom 接口。例如:
// Process A
mojo::OutgoingInvitation invitation;
auto pipe = invitation->AttachMessagePipe("x");
mojo::Receiver<foo::mojom::Bar> receiver(
&bar_impl,
mojo::PendingReceiver<foo::mojom::Bar>(std::move(pipe)));
// Process B
auto invitation = mojo::IncomingInvitation::Accept(...);
auto pipe = invitation->ExtractMessagePipe("x");
mojo::Remote<foo::mojom::Bar> bar(
mojo::PendingRemote<foo::mojom::Bar>(std::move(pipe), 0));
// Will asynchronously invoke bar_impl.DoSomething() in process A.
bar->DoSomething();
并且为了确保万无一失,这里的用法可以反过来:邀请发送者可以将其管道端点视为,而Remote接收者将其管道端点视为PendingReceiver要绑定的。