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

我们继续分析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要绑定的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python基于车牌识别的车辆进出管理系统

目录 1、效果图2、具体内容系统流程开发工具和环境项目所需依赖包目录描述&#xff1a;启动Django服务登录账号 3、源码下载技术交流 博主介绍&#xff1a; 计算机科班人&#xff0c;全栈工程师&#xff0c;掌握C、C#、Java、Python、Android等主流编程语言&#xff0c;同时也熟…

JavaWeb5 SpringBoot+HTTP协议

Spring Spring Boot 非常快速构建应用程序&#xff0c;简化开发 &#xff08;1&#xff09;创建Springboot工程&#xff0c;勾选web开发依赖 创建好的目录&#xff0c;并将没用多余的删掉了 &#xff08;2&#xff09;定义请求处理类&#xff0c;并添加方法 创建请求处理类…

华为云DDoS攻击下的应对策略

当华为云上的服务遭遇大规模DDoS攻击导致网络流量异常&#xff0c;触发了华为云的自动防护机制&#xff0c;即所谓的“黑洞”状态时&#xff0c;服务将暂时无法访问&#xff0c;直至攻击停止或流量恢复正常。本文将探讨如何在这一情况下&#xff0c;通过引入第三方安全产品来快…

目标检测——DeepGlobe道路提取数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

Springboot使用webupload大文件分片上传(包含前后端源码)

Springboot使用webupload大文件分片上传&#xff08;包含源码&#xff09; 1. 实现效果1.1 分片上传效果图1.2 分片上传技术介绍 2. 分片上传前端实现2.1 什么是WebUploader&#xff1f;功能特点接口说明事件APIHook 机制 2.2 前端代码实现2.2.1&#xff08;不推荐&#xff09;…

huggingface_hub LocalEntryNotFoundErroringface

报错详细 LocalEntryNotFoundError: An error happened while trying to locate the file on the Hub and we cannot find the requested files in the local cache. Please check your connection and try again or make sure your Internet connection is on.问题说明 在…

【UML用户指南】-15-对高级结构建模-对象图

目录 1、对象图的组成 2、 对象图和类图关系 3、对对象结构建模 4、逆向工程 5、对象图构建要求 对象图对包含在类图中的事物的实例建模。 对象图显示了在某一时间点上一组对象以及它们之间的关系。 对象图用于对系统的静态设计视图或静态交互视图建模。 对某一时刻的系统…

CentOS7下快速升级至OpenSSH9.7p2安全版本

一、CentOS7服务器上编译生成OpenSSH9.3p2的RPM包 1、编译打包的shell脚本来源于该项目 https://github.com/boypt/openssh-rpms解压zip项目包 unzip openssh-rpms-main.zip -d /opt cd /opt/openssh-rpms-main/ vim pullsrc.sh 修改第23行为source ./version.env 2、sh pull…

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

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

全网爆火【MBTI人格测试】是如何实现的?

功能介绍 概述 MBTI人格测试是一款基于Agent Builder框架开发的智能体应用&#xff0c;旨在通过五个精心设计的问题准确分析用户的MBTI性格类型。完成测试后&#xff0c;应用将提供详细的性格分析和建议&#xff0c;帮助用户更好地理解自己的性格特点。 功能详述 1. MBTI测试…

RAG实战4-RAG过程中发生了什么?

RAG实战4-RAG过程中发生了什么&#xff1f; 在RAG实战3中我们介绍了如何追踪哪些文档片段被用于检索增强生成&#xff0c;但我们仍不知道RAG过程中到底发生了什么&#xff0c;为什么大模型能够根据检索出的文档片段进行回复&#xff1f;本文将用一个简单的例子来解释前面的问题…

Linux磁盘管理(MBR、分区表、分区、格式化)

目录 1、简单介绍 2、MBR&#xff1a; 2.1、分区表&#xff1a; 2.2、注意&#xff1a; 2.3、编号问题&#xff1a; 2.4、磁盘的命名&#xff1a; 2.5、格式化分区 1、简单介绍 1.1、track&#xff1a;磁道&#xff0c;就是磁盘上同心圆&#xff0c;从外向里&#xff0c…

Imagic: Text-Based Real Image Editing with Diffusion Models

Imagic: Text-Based Real Image Editing with Diffusion Models Bahjat Kawar, Google Research, CVPR23, Paper, Code 1. 前言 在本文中&#xff0c;我们首次展示了将复杂&#xff08;例如&#xff0c;非刚性&#xff09;基于文本的语义编辑应用于单个真实图像的能力。例如…

[Redis] Redis Desktop Manager 安装包和连接和创建流程

1. 安装流程就是next&#xff0c;就可以。 2. 分别填写好&#xff1a; Name(自定义&#xff0c;redis这个库展示的名字), Host, Port, Auth(Redis 的连接password) 3. 要勾选上Use SSL Protocol 选项&#xff0c; 4. 连接到redis上&#xff0c;展示不同的database&#xff0c;…

为什么电容两端电压不能突变

我们先从RC延时电路说起吧&#xff0c;图1是最简单的RC延时电路&#xff0c;给一个阶跃的电压信号&#xff0c;电压会变成黄色曲线这个样子&#xff0c;这是为什么呢&#xff1f; 图1 电压跳变后&#xff0c;电源负极电子移动到电容下极板&#xff0c;排斥上极板电子流动到电源…

rtl8723du android5.1 6818 (wifi 部分)(第三部分)

这部分主要就是 应用了。具体的详细框架 在 android4.4 部分写的差不多的。 之前板卡依然是使用的 mt6620 ,所以在移植的过程中,需要把之前的 wifi 的驱动一点一点的去掉。 1 kernel 的修改。 将驱动拷贝到 wireless 下。 修改Kconfig 修改Makefile 2 驱动的Makefile 的修改…

C语言经典指针运算笔试题图文解析

指针运算常常出现在面试题中&#xff0c;画图解决是最好的办法。 题目1&#xff1a; #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } //程序的结果是什么&…

深度学习 --- stanford cs231 编程作业(assignment1,Q3: softmax classifier)

stanford cs231 编程作业(assignment1&#xff0c;Q3: softmax classifier softmax classifier和svm classifier的assignment绝大多部分都是重复的&#xff0c;这里只捡几个重点。 1&#xff0c;softmax_loss_naive函数&#xff0c;尤其是dW部分 1&#xff0c;1 正向传递 第i张…

银河麒麟v10 sp3编译制作内核rpm包——筑梦之路

环境信息 下载内核源码包 这里下载4.19版本的内核源码包&#xff0c;当前最新为4.19.315 https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.315.tar.xz 准备编译环境 # 安装编译需要的依赖包yum install rpm-devel rpmdevtools yum groupinstall "Developmen…

AI写作助手:简化你的学术写作流程

在日常工作和生活中&#xff0c;我经常使用各种各样的人工智能工具&#xff0c;如AI写作助手、AI语音助手和AI绘图工具等。这些AI工具显著提升了我的工作效率&#xff0c;并极大地简化了我的日常任务。作为一名AI工具的忠实爱好者&#xff0c;我搜集了许多免费的AI工具&#xf…