Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(2)

news2024/11/29 13:52:45

我们继续分析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

这四个类展开来说:

  1. CompositorFrameSink: 这是由 .mojom 文件定义的接口转换而成的一个抽象基类。它定义了接口的方法,但不提供具体的实现。服务端(Impl 端)需要提供一个派生自
    CompositorFrameSink 的具体类,实现所有的虚拟方法。客户端(Proxy 端)会使用这个接口与服务端通信。

  2. CompositorFrameSinkStub: 这是一个存根类,其作用是接收传入的消息并将它们转换为对应的 CompositorFrameSink 方法调用。在服务端,每当一个消息到达时,CompositorFrameSinkStub
    会将消息反序列化,然后调用在 CompositorFrameSink 实现类中对应的方法。Stub 类通常由 Mojo
    工具链自动生成,并与一个 Mojo 句柄关联,以便监听进入的消息。

  3. CompositorFrameSinkProxy: 这是客户端的代理类,它实现了 CompositorFrameSink 接口。客户端通过调用 CompositorFrameSinkProxy
    的方法,将方法调用转换为消息,并将这些消息发送到服务端。Proxy 类负责序列化方法调用的参数,创建消息,并通过 Mojo
    消息管道将其发送出去。Proxy 类也是由 Mojo 工具链自动生成的。

  4. 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) 创建实例的过程:

  1. 定义接口:首先,需要在 .mojom 文件中定义一个 Mojo 接口,这个接口包含了可供调用的方法。

  2. 生成代理和存根:使用 mojom_bindings_generator.py 脚本根据 .mojom 文件生成代理(Proxy)和存根(Stub)的源代码。

  3. 创建代理实例:在客户端代码中,使用生成的 CompositorFrameSinkProxy 类创建一个代理实例。这通常涉及到创建一个 InterfacePtr<CompositorFrameSink>,它是一个智能指针,管理对
    CompositorFrameSinkProxy 实例的引用。

  4. 建立连接:客户端通过 Mojo 的绑定机制建立与服务端的连接。这通常涉及到调用一些形式的 Bind 方法,它接收一个 InterfaceRequest,这是一个未完成的连接请求。以CompositorFrameSink为例,客户端持有一个 mojo::AssociatedRemote<viz::mojom::CompositorFrameSink> compositor_frame_sink_associated_; 指针,这个指针通过绑定 mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink> compositor_frame_sink_associated_remote; 之后,即可开始调用。代码如下: 在这里插入图片描述

  5. 发送消息:客户端通过代理实例调用 CompositorFrameSink 接口的方法。代理会序列化这些方法调用和参数,并通过消息管道发送到服务端。

服务端 (Impl) 绑定存根的过程:

  1. 实现接口:服务端实现了在 .mojom 文件中定义的 CompositorFrameSink 接口。这个实现通常是 CompositorFrameSinkImpl 类。

  2. 创建实现实例并绑定Impl:服务端创建 CompositorFrameSinkImpl 的实例,这个实例包含了接口方法的具体逻辑。其构造函数完成了于CompositorFrameSinkImpl与CompositorFrameSink的绑定在这里插入图片描述
    绑定的本质是响应来自客户端的请求,请求里带了两个关键参数:
    mojo::PendingReceiver<mojom::CompositorFrameSink> receiver, mojo::PendingRemote<mojom::CompositorFrameSinkClient> client

  3. 创建并绑定存根:服务端使用生成的 CompositorFrameSinkStub 类,并将其与 InterfaceRequest 绑定。这个 Stub 对象负责监听进入的消息,并将它们分派到
    CompositorFrameSinkImpl 实例的方法中。在这里插入图片描述

  4. 处理和分派消息:每当服务端的 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 在这种用例中可能面临的局限性:

  1. 零拷贝传输:
    Mojo 专注于支持大数据量的高效传输,包括对共享内存和跨进程直接内存访问的支持。这种零拷贝传输方式对于浏览器中图形和媒体相关的数据非常重要,可以最小化延迟和CPU开销。

  2. 句柄和资源的传递:
    Mojo 允许在进程间传递操作系统句柄(如文件、共享内存段和同步原语)。Protobuf 不支持这种复杂的句柄传递,而这对于浏览器中的很多操作是必需的。

  3. 同步和异步消息的支持:
    Mojo 支持同步和异步消息模式。在某些情况下,同步调用对于保持状态的一致性和避免竞态条件是必要的,而 Protobuf 本身并不处理消息的传递机制。

  4. 接口定义和版本控制:
    Mojo 允许通过 .mojom 文件定义清晰的接口和它们的方法,这些定义非常适合用于生成各种语言的绑定和接口实现。Mojo 还支持接口的版本控制,允许向后兼容的接口演进。

  5. 性能和资源利用:
    Mojo 为高性能 IPC 而设计,特别考虑了低延迟和高吞吐量的需求。在浏览器环境中,性能和资源利用是关键考量因素。

  6. 定制化需求:
    Chromium 项目有特定的需求,无法通过使用通用的序列化库(如 Protobuf)来满足。Mojo 设计为可以与 Chromium 的其他部分紧密集成,提供了更多的灵活性和控制。

  7. 安全性:
    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要绑定的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1815576.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工智能在肿瘤细胞分类中的应用|顶刊速递·24-06-06

小罗碎碎念 推文主题——人工智能在肿瘤细胞分类中的应用。 重点关注 临床方向的同学/老师建议重点关注第四篇&第六篇文章&#xff0c;最近DNA甲基化和蛋白组学与AI的结合&#xff0c;在顶刊中出现的频率很高&#xff0c;建议思考一下能否和自己的课题结合。 工科的同学重…

第6章 应用层

考纲内容 &#xff08;一&#xff09;网络应用模型 客户/服务器模型&#xff1b;P2P模型 &#xff08;二&#xff09;域名系统(DNS) 层次域名空间&#xff1b;域名服务器&#xff1b;域名解析过程 &#xff08;三&#xff09;文件传输协议(FTP) …

【人工智能】文本提取技术的算法延伸

✍&#x1f3fb;记录学习过程中的输出&#xff0c;坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;指点&#x1f64f; 文本提取技术中用到的算法 TF-IDF&#xff08;Term Frequency-Inverse Document Frequency…

【C语言】联合(共用体)

目录 一、什么是联合体 二、联合类型的声明 三、联合变量的创建 四、联合的特点 五、联合体大小的计算 六、联合的应用&#xff08;判断大小端&#xff09; 七、联合体的优缺点 7.1 优点 7.2 缺点 一、什么是联合体 联合也是一种特殊的自定义类型。由多个不同类型的数…

【多元统计】期末复习必备!按题型分类

一&#xff0c;简答题 二&#xff0c;证明题 三&#xff0c;计算题

vue之一键部署的shell脚本和它的点.bat文件、海螺AI、ChatGPT

MENU 前言vite.config.ts的配置deploy文件夹的其他内容remote.shpwd.txtdeploy.bat 前言 1、在src同级新建deploy.bat文件&#xff1b; 2、在src同级新建deploy文件夹&#xff0c;文件夹中新建pwd.txt和remote.sh文件&#xff1b; 3、配置好后&#xff0c;直接双击deploy.bat文…

AI大模型-LangChain基础知识入门

1 什么是LangChain LangChain由 Harrison Chase 创建于2022年10月&#xff0c;它是围绕LLMs&#xff08;大语言模型&#xff09;建立的一个框架&#xff0c;LLMs使用机器学习算法和海量数据来分析和理解自然语言&#xff0c;GPT3.5、GPT4是LLMs最先进的代表&#xff0c;国内百度…

10分钟搞定分布式应用缓存

本文深入探讨了分布式应用缓存的概念、实现方式、策略以及最佳实践&#xff0c;详细介绍了主要的缓存模式&#xff0c;并讨论了缓存驱逐策略及今后的发展。原文: Mastering Caching in Distributed Applications 缓存似乎是一种你觉得可以做对&#xff0c;但却永远做不对的东西…

赛氪网受邀参加上海闵行区翻译协会年会,共探科技翻译创新之路

在科技飞速发展的时代背景下&#xff0c;翻译行业正面临着前所未有的机遇与挑战。作为连接高校、企业与社会的桥梁&#xff0c;赛氪网在推动翻译创新、促进学术交流方面展现出了独特的魅力。2024年6月9日&#xff0c;在华东师范大学外语学院举办的第十三届上海市闵行区翻译协会…

【perl】基本语法 /备忘录/

分享 perl 语言学习资源 Perl 教程|极客教程 (geek-docs.com) Perl [zh] (runebook.dev) Perl 运算符 | 菜鸟教程 (runoob.com) Perl Documentation - Perldoc Browser Search the CPAN - metacpan.org 当然还有一些经典书籍&#xff0c;不再列举。 1、数字 1.1、数字表…

深入理解Qt多线程编程(QThreadPool)

多线程编程在现代软件开发中变得越来越重要&#xff0c;它能够提高应用程序的响应速度和处理性能。在Qt框架中&#xff0c;QThreadPool作为线程池管理工具&#xff0c;被频繁的使用。 目录 概述 接口介绍 底层原理解析 使用方法 概述 QThreadPool是Qt提供的一个线程池实现&a…

钢结构厂房降温方案

钢结构厂房降温方案 这种钢结构的厂房的车间大&#xff0c;天就晒透了&#xff0c;然后你的工作往往外温度又高&#xff0c;你前面又弄个大火炉烤你身上&#xff0c;你能不热吗&#xff1f;你热怎么办&#xff1f;风扇你就是在25左右的时候吹着挺舒服的&#xff0c;因为它毕竟…

179.二叉树:合并二叉树(力扣)

代码解决 /*** 二叉树节点的定义。* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, Tre…

Pytorch--Convolution Layers

文章目录 1.nn.Conv1d2.torch.nn.Conv2d()3.torch.nn.ConvTranspose1d()3.torch.nn.ConvTranspose2d() 1.nn.Conv1d torch.nn.Conv1d() 是 PyTorch 中用于定义一维卷积层的类。一维卷积层常用于处理时间序列数据或具有一维结构的数据。 构造函数 torch.nn.Conv1d() 的语法如…

如何用Vue3构建一个交互式音乐播放器

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 Vue.js 开发音乐播放器卡片 应用场景 这款音乐播放器卡片旨在为音乐应用程序提供一个现代而交互式的用户界面。它包含诸如歌曲信息、播放进度条和控制按钮等关键功能。 基本功能 **歌曲信息显示&#xff1a…

Xinstall:解决App安装跳转难题,让你的用户流畅体验不再受阻

在互联网流量红利逐渐衰退的今天&#xff0c;App推广和运营面临着前所未有的挑战。如何确保在多变的互联网环境下&#xff0c;迅速搭建起能时刻满足用户需求的运营体系&#xff0c;已经成为众多企业急待解决的问题。今天&#xff0c;我们就来探讨一下如何通过Xinstall的安装跳转…

软件工程期末复习题

目录 选择 判断 选择 下列说法中正确的是 ( B )。 A、20 世纪50 年代提出了软件工程的概念摇 B、20 世纪60 年代提出了软件工程的概念 C、20 世纪70 年代出现了客户端/ 服务器技术 D、20 世纪80 年代软件工程学科达到成熟 软件危机的主要原因是 ( D )。 A、软件工具落后…

电子画册制作与传统画册相比,有哪些优势?

在当今数字化时代&#xff0c;电子画册作为一种新兴的媒体形式&#xff0c;其制作与传统画册相比具有显著的优势。以下是对这些优势的详细探讨。 首先&#xff0c;电子画册的制作过程通常更加便捷和经济。相较于传统画册需要经历的繁琐的印刷过程&#xff0c;电子画册的制作大多…

RocketMQ教程(八):RocketMQ的集群搭建

集群架构 RocketMQ 的各个组件都可以搭建成集群部署,Broker 还可以搭建成主从架构,下面介绍的主要是 Broker 集群。 数据复制策略 复制策略是Broker的Master与Slave间的数据同步方式。分为同步复制与异步复制: 同步复制 消息写入master后,master会等待slave同步数据成…

【C++】——继承(详解)

一 继承的定义和概念 1.1 继承的定义 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类&#xff0c;被继承的称为基类…