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

本文主要是介绍Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Mojo简介

Mojo 是一个运行时库的集合,提供与平台无关的通用 IPC 原语抽象、消息 IDL 格式以及具有针对多种目标语言的代码生成的绑定库,以便于跨任意进程间和进程内边界传递消息。
Mojo 分为清晰分离的层,子组件的基本层次结构如下:

请添加图片描述

分析Mojo之前,我们的思考

笔者在阅读源码前,喜欢会去思考,“如果让我来设计一个类似的功能的模块,我会怎么设计?”。然后对比文档去思考为什么会出现思路的差异,这种方式可以让我快速掌握一个开源库的设计精髓。
这次也是一样,我们想想,如果是我们自己设计Mojo,这会是什么样的架构和过程。

  1. 首先,Mojo是跨平台的,那么必然有一层platform的平台差异屏蔽层
  2. 其次,跨进程通信在不同平台上的最佳实现方案可能不一样,例如有的平台是管道,有的平台是共享内存,具体如何选择取决于不同平台的性能差异,因此,我必须将跨平台通信细节进行抽象,提取出一些概念,用于描述通信的应用层细节
  3. 之后,两个进程之间的通信之前,需要先建立连接,因此必然也需要定义一组规则和概念,来描述连接的应用层细节
  4. 为了增加通信的灵活性,我们可以定义一组观察者或者过滤器的规则,可以实现对数据流的监测和量化,也能实现更灵活的扩展。因此我们需要定义一组规则和概念来实现这个目标
  5. 为了让跨平台通信框架更加易于使用,我们需要提供一套序列化和反序列化的框架,这样可以让通信以自定义结构体的形式进行,而非数据流。
  6. 如果存在大量的通信消息,那么我们需要解决不同进程共享头文件,使用自定义结构体的形式;另外,Mojo除了跨平台,还需要夸语言,那么,我们必然不能使用某种语言的结构体定义形式(例如C的结构体或者JavaScript友好的Json),而是需要定义一种新的规则,通过工具自动生成不同语言友好的结构体源码

上面的5和6本质上可以看作是同一个问题,并且我首先想到的是protobuffer库可以实现这两个问题的解法。

带着我们自己的设计思路,再去看看Mojo的设计方案。

的Mojo方案:

源码目录体积一览:

在这里插入图片描述
源码重要文件一览(排除test源文件,按大小排序,top的文件如下图):
在这里插入图片描述
一般来说,体积大的源文件表示的功能都比较核心,因此这些文件里面对应的功能和概念,很大概率是Mojo框架的核心,值得每一项进行标注理解。

Mojo的底层Channel

在Mojo中的源码中,可以很明显的发现关键字Channel是最底层跨进程通信的关键概念,并且能找到平台相关的抽象和实现,直接搜索文件名关键字就一览无余了:
在这里插入图片描述

channel.h

看了一眼mojo\core\channel.h可以发现,抽象基类是mojo::core::Channel, 并且定义了许多核心概念,包括Message(A message to be written to a channel.)、Delegate( // Delegate methods are called from the I/O task runner with which the Channel
was created (see Channel::Create). 关键回调OnChannelMessage)等。
不同平台通过继承抽象基类mojo::core::Channel实现平台相关的读写,以mojo\core\channel_win.cc的ChannelWin为例,使用Win32API的ReadFile、WriteFile从base::win::ScopedHandle句柄中读写数据,这个句柄是构造函数的参数ConnectionParams(TakeEndpoint().TakePlatformHandle().TakeHandle())传入的获取的。

channel相关的重点类解析

  • PlatformChannel
    是一个封装了两个交织在一起的端点的类或结构,这些端点属于特定平台的基本通信原语,比如在Windows上是管道,在Unix系统上是域套接字,在macOS上是Mach端口对。其中一个端点被指定为“本地”端点,由创建它的进程保留;另一个端点被指定为“远程”端点,应当传递给外部的进程。

  • PlatformChannel 可以用来在两个进程之间启动Mojo IPC(一种进程间通信机制)。通常情况下另一个进程是当前进程的子进程,PlatformChannel
    提供辅助方法来将端点以这种方式传递给子进程;但这种设置在所有平台上并不是强制性的。
    如果需要一个允许客户端通过名称来连接的通道(比如一个命名管道或者套接字服务器,这种类型仅在Windows和POSIX系统上被支持),那么可以参考
    NamedPlatformChannel。

  • PlatformChannelServer 这个类负责持有一个 PlatformChannelServerEndpoint 实例,并监听一个单一的来自客户端的连接请求。这个类不是线程安全的,必须在运行I/O消息泵(Message Pump)的线程上使用。

  • PlatformChannelServer 和PlatformChannel 对比: 简而言之,PlatformChannel 负责创建和管理进程间通信的通道,而 PlatformChannelServer
    则是在服务端监听和接受这些通道上的连接请求。PlatformChannel 可以看作是连接的“管道”,而 PlatformChannelServer 是“水龙头”,控制着连接的开启。

  • PlatformHandle平台句柄类,它带有一些额外的类型信息,用来表明它是一个通道端点(channel endpoint)。也就是说,它是一种句柄,可以被用来作为 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL
    发送或接收邀请到一个远程的 PlatformChannelEndpoint

结合调用堆栈:

Channel的创建:
在这里插入图片描述
Channel的发送消息:
在这里插入图片描述
通过调用堆栈可以发现,Channel的概念几乎是Mojo最底层的概念了,往上走有Router、InterfaceEndpointClient、Message、ChannelMojo概念等。以下是从最底层到更高层的一些核心概念及其作用的介绍:

  1. Channel:
    Channel 是 Mojo IPC 系统中最底层的抽象。它代表了一个底层的通信通道,负责在两个进程之间传输原始的字节数据。Channel 封装了操作系统级别的 IPC 机制(如套接字或共享内存),以便在不同的平台上提供一致的 API。它负责序列化和反序列化消息,保证数据的完整性,并处理底层的传输细节。

  2. Router:
    Router 位于 Channel 之上,是一个稍高层次的抽象。它负责将发出的消息路由到正确的 Channel,并从 Channel 接收消息。Router 还管理消息的生命周期,确保消息按照正确的顺序发送和接收,并可能处理流控制和重试逻辑。

  3. InterfaceEndpointClient:
    InterfaceEndpointClient 是 Mojo IPC 中的一个组件,它代表了 Mojo 接口的端点。它与 Router 配合,以便在 Mojo 接口上发送和接收消息。通常,每个 Mojo 接口都有一个或多个方法,这些方法对应于可以通过该端点发送的消息类型。InterfaceEndpointClient 会序列化这些方法调用为消息,并将它们传递给 Router 进行传输。

  4. Message:
    Message 是代表 IPC 系统中传递的一个消息实体。它通常包含要传输的数据(例如,方法调用的参数),以及可能的元数据(例如,消息类型或优先级)。在 Mojo IPC 中,Message 是发送和接收的基本单位,由 RouterChannel 处理。

  5. ChannelMojo:
    ChannelMojoChannel 的一个具体实现,它利用 Mojo 系统的底层管道(如 MessagePipe)来传输数据。ChannelMojo 提供了一个适应 Mojo 管道特性的 Channel 接口,使得上层的 RouterInterfaceEndpointClient 能够通过 Mojo 管道发送和接收消息。

这些概念共同构成了 Mojo IPC 系统的框架,其中每个层次都建立在下一个层次之上,提供了逐步更高级别的抽象和功能。开发者可以根据需要选择在哪个层次上与 IPC 系统交互,从直接使用 Channel 的字节级操作,到通过 InterfaceEndpointClient 的接口级调用。

Mojo的Node

在源码一览中,我们发现node.cc是最大的源文件,我们以此为线索展开对Node的理解和阅读,Node相关文件有:
在这里插入图片描述
Node.h中,NodeChannel是一个核心,注释只有一句:Wraps a Channel to send and receive Node control messages.

// Wraps a Channel to send and receive Node control messages.
class MOJO_SYSTEM_IMPL_EXPORT NodeChannel: public base::RefCountedDeleteOnSequence<NodeChannel>,public Channel::Delegate {public:// .... 略

由此可见,Channel 是一种底层概念,用于抽象化和平滑处理不同平台之间的差异,而 Node 概念则在 Channel 的基础上进行了进一步的封装和抽象化。NodeChannel用于定义 Mojo 中与连接、广播、中介(Broker)、消息传输以及错误处理相关的实现细节。如果用计算机网络的术语进行类比,那么 Channel 类似于网络协议栈中的 IP 层,它提供了寻址和路由的能力;而 NodeChannel则相当于应用层的协议,例如 UDP,它在更高层次上处理数据的传输和相关逻辑。

那么,可以预见,Mojo的应用层概念将围绕Node为核心展开。

从上面代码中我们发现,NodeChannel中有个重要的嵌入类:Delegate。Delegate的概念在chromium广泛存在,其实可以理解为Delegate就是一组回调,在宿主对象处理逻辑的关键节点时,通过Delegate回调转移执行绪,以实现行为的定制和扩展的能力。 通过了解Delegate回调的函数组成,可以快速了解宿主类的主要功能和关键流程,是阅读源码的重要技巧。例如NodeChannel的Delegate的类定义如下:

 class Delegate {public:virtual ~Delegate() = default;virtual void OnAcceptInvitee(const ports::NodeName& from_node,const ports::NodeName& inviter_name,const ports::NodeName& token) = 0;virtual void OnAcceptInvitation(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& invitee_name) = 0;virtual void OnAddBrokerClient(const ports::NodeName& from_node,const ports::NodeName& client_name,base::ProcessHandle process_handle) = 0;virtual void OnBrokerClientAdded(const ports::NodeName& from_node,const ports::NodeName& client_name,PlatformHandle broker_channel) = 0;virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,const ports::NodeName& broker_name,PlatformHandle broker_channel,const uint64_t broker_capabilities) = 0;virtual void OnEventMessage(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;virtual void OnRequestPortMerge(const ports::NodeName& from_node,const ports::PortName& connector_port_name,const std::string& token) = 0;virtual void OnRequestIntroduction(const ports::NodeName& from_node,const ports::NodeName& name) = 0;virtual void OnIntroduce(const ports::NodeName& from_node,const ports::NodeName& name,PlatformHandle channel_handle,const uint64_t remote_capabilities) = 0;virtual void OnBroadcast(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;
#if BUILDFLAG(IS_WIN)virtual void OnRelayEventMessage(const ports::NodeName& from_node,base::ProcessHandle from_process,const ports::NodeName& destination,Channel::MessagePtr message) = 0;virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,const ports::NodeName& source_node,Channel::MessagePtr message) = 0;
#endifvirtual void OnAcceptPeer(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& peer_name,const ports::PortName& port_name) = 0;virtual void OnChannelError(const ports::NodeName& node,NodeChannel* channel) = 0;};

通过这个代理类,就能很直观地理解NodeChannel的功能和作用。

mojo的Port

在阅读NodeChannel类的时候,有一个关键字出现了多次,那就是port。在Mojo中,port是一个命名空间,也是一个重要概念,port这个类的头文件注释如下:

在 Mojo IPC 系统中,“Port”本质上是一个地址的循环列表中的一个节点。为了本文档的目的,这样的列表将被称为“路由”(route)。路由是所有 Node 事件流通的基本媒介,因此是所有 Mojo 消息传递的骨干。
每个 Port 都由一个节点(参见 node.h)内的 128 位地址唯一标识。Port 本身并不真正“做”任何事情:它是一系列状态的命名集合,而拥有它的 Node 管理所有事件的产生、传输、路由和处理逻辑。有关 Port 如何被用来传输任意用户消息以及其他 Ports 的更多细节,请参见 Node。
Ports 可以处于几种状态(见下面的 State),这些状态决定了它们如何响应以它们为目标的系统事件。在最简单和最常见的情况下,Ports 最初是作为一对纠缠在一起的状态(即由两个 Ports 组成的简单循环)创建的,都处于 kReceiving 状态。我们这里将这些 Ports 标为 |A| 和 |B|,它们可以使用 Node::CreatePortPair() 创建:

    +-----+          +-----+|     |--------->|     ||  A  |          |  B  ||     |<---------|     |+-----+          +-----+

|A| 通过 |peer_node_name| 和 |peer_port_name| 引用 |B|,同时 |B| 反过来引用
|A|。请注意,一个 Node 永远不会知道是谁向给定的 Port 发送事件;它只知道必须从给定的 Port 路由事件到哪里。
为了方便文档描述,我们将路由中的一个接收端 Port 称为另一个的“共轭”(conjugate)。一个接收端 Port 的共轭在初始创建时也是它的对端,但由于代理,这种关系可能随着时间而改变。 对这个数据结构的所有访问必须通过获取 |lock_|来进行保护,这只能通过 PortLocker 实现。PortLocker 确保在单个线程上重叠的 Port 锁获取总是以全局一致的顺序进行。

通过头文件注释,感觉似懂非懂,看看Port这个类的主要成员和方法吧:

Port 类是 Mojo IPC 系统中的一个核心组件,它代表了消息传递路径上的一个节点。这个类继承自
base::RefCountedThreadSafe<Port>,允许它在多个线程中安全地共享和管理其生命周期。以下是 Port
类的主要功能和特性:

  • State 枚举:定义了 Port 可能处于的状态,包括 kUninitialized(未初始化)、kReceiving(接收中)、kBuffering(缓冲中)、kProxying(代理中)和
    kClosed(已关闭)。
  • state 成员变量:存储当前 Port 的状态。
  • peer_node_namepeer_port_name 成员变量:指定了事件应该从该 Port 路由到哪个节点和端口的地址。
  • prev_node_nameprev_port_name 成员变量:跟踪当前发送消息到这个 Port 的上一个端口,用于验证发送方节点是否有权限发送消息到这个端口,同时保持接收消息的顺序。
  • pending_merge_peer 成员变量:标记这个端口是否准备合并。
  • 一系列的序列号成员变量(next_control_sequence_num_to_sendnext_sequence_num_to_send
    等):用于跟踪控制和用户消息事件的序列号。
  • message_queue 成员变量:存储该 Port 接收到的用户消息队列。此队列只为 kBufferingkReceiving 状态的 Port 提供服务。
  • control_message_queue 成员变量:在 Port 处于 kBuffering 状态时,暂存即将发送的控制消息。
  • send_on_proxy_removal 成员变量:在某些边缘情况下,如果这个(代理中的)Port 被销毁,它可能需要记得路由一个特殊的事件。
  • user_data 成员变量:附加到 Port 的任意用户数据。在 Mojo 中,这通常用于存储通知有关 Port 状态变化的观察者接口。
  • remove_proxy_on_last_messagepeer_closed 成员变量:标志位,用于指示 Port 的一些状态,如代理何时可以移除,以及它的对端 Port 是否已关闭。
  • Port 构造函数:用于初始化 Port,设置初始的序列号。
  • AssertLockAcquired 方法:用于调试中检查是否已获取 Port 的锁。
  • IsNextEvent 方法:检查给定的事件是否应该根据序列号和发送方节点接下来处理。
  • NextEvent 方法:获取下一个要处理的缓冲事件。
  • BufferEvent 方法:将事件缓存以供后续处理。>
  • TakePendingMessages 方法:清空等待节点验证的事件队列,并返回所有用户事件。
  • 私有析构函数 ~Port:确保 Port 只能通过引用计数安全地销毁。
  • 私有成员 lock_:用于确保对 Port 数据结构的线程安全访问。
  • PortLocker 友元类:用于确保在单个线程上以全局一致的顺序获取重叠的 Port 锁。
    该类的设计允许它在 Mojo IPC 系统中作为消息的发送和接收点,管理消息的顺序和状态,并确保消息在正确的路径上流动。

结合其他源码,发现Port和Dispatcher相关逻辑结合紧密,另外,Port存储了Event的序号等数据信息,支持插入事件,并且许多数据成员用于Node.cc中实现消息处理,可见Port这个类做的事情确实很难和已有的概念类比出来,也难怪通过这个类的注释难以一下理解其作用。简而言之,Port这个类即负责一部分事件排序和派发相关的逻辑处理,也承载了一个寻址的功能。

在 Mojo IPC 系统中,Node 通常代表了一个独立的参与者,如一个进程,它是消息传递路径上的一个物理节点。Node 可以是消息的最初发送者或最终接收者。相比之下,Port 是 Node 内部的逻辑上的虚拟节点,它负责管理消息的复杂路由、转发以及过滤等操作。每个 Port 都由其所属的 Node 管理,并且可以与其他 Node 中的 Port 形成连接,从而构成消息传递的网络。
之所以这样设计,是为了让两个Node之间可以出现多个连接,每个连接就是一对“共轭”的Port。这样每个连接各自的序号(seq)就不会互相干扰。所以,序号的数据就存储在Port类里,这也就不奇怪了。正因为Port代表了连接,所以数据的过滤和代理也必须面向连接进行,因此Port也和相关的类紧密联系。

说实话,如果把Port改名为Connection,也许会更直观一些。

这里面出现了NodeName和Port Name,也顺便看看定义:

struct COMPONENT_EXPORT(MOJO_CORE_PORTS) PortName : Name {constexpr PortName() : Name(0, 0) {}constexpr PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};
struct COMPONENT_EXPORT(MOJO_CORE_PORTS) NodeName : Name {constexpr NodeName() : Name(0, 0) {}constexpr NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};

接下来

接下来,我们继续阅读Mojo模块的代码。了解消息的过滤和派发、序列化和反序列化、Mojom、等功能逻辑。(未完待续…)

这篇关于Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1056469

相关文章

#error用法

/* *检查编译此源文件的编译器是不是C++编译器 *如果使用的是C语言编译器则执行#error命令 *如果使用的是 C++ 编译器则跳过#error命令 */ #ifndef __cplusplus #error 亲,您当前使用的不是C++编译器噢! #endif #include <stdio.h> int main() {

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

在线装修管理系统的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,装修队管理,用户管理,装修管理,基础数据管理,论坛管理 前台账户功能包括:系统首页,个人中心,公告信息,论坛,装修,装修队 开发系统:Windows 架构模式:B/S JDK版本:Java JDK1.8 开发工具:IDEA(推荐) 数据库版本: mysql5.7 数据库可视化工具: navicat 服务器:SpringBoot自带 ap

DDei在线设计器-API-DDeiSheet

DDeiSheet   DDeiSheet是代表一个页签,一个页签含有一个DDeiStage用于显示图形。   DDeiSheet实例包含了一个页签的所有数据,在获取后可以通过它访问其他内容。DDeiFile中的sheets属性记录了当前文件的页签列表。   一个DDeiFile实例至少包含一个DDeiSheet实例。   本篇最后提供的示例可以在DDei文档直接预览 属性 属性名说明数

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

springboot家政服务管理平台 LW +PPT+源码+讲解

3系统的可行性研究及需求分析 3.1可行性研究 3.1.1技术可行性分析 经过大学四年的学习,已经掌握了JAVA、Mysql数据库等方面的编程技巧和方法,对于这些技术该有的软硬件配置也是齐全的,能够满足开发的需要。 本家政服务管理平台采用的是Mysql作为数据库,可以绝对地保证用户数据的安全;可以与Mysql数据库进行无缝连接。 所以,家政服务管理平台在技术上是可以实施的。 3.1

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

SQL Server中,isnull()函数以及null的用法

SQL Serve中的isnull()函数:          isnull(value1,value2)         1、value1与value2的数据类型必须一致。         2、如果value1的值不为null,结果返回value1。         3、如果value1为null,结果返回vaule2的值。vaule2是你设定的值。        如

比较学习难度:Adobe Illustrator、Photoshop和新兴在线设计平台

从入门设计开始,几乎没有人不知道 Adobe 公司两大设计软件:Adobe Illustrator和 Photoshop。虽然AI和PS很有名,有一定设计经验的设计师可以在早期探索和使用后大致了解AI和PS的区别,但似乎很少有人会系统地比较AI和PS。目前,设计软件功能多样,轻量级和网页设计软件已成为许多设计师的需求。对于初学者来说,一篇有针对性的AI和PS比较总结文章具有非常重要的指导意义。毕竟