Chromium进程间的通信机制
基于chromium的chrome发布于2008年底。从1.0版本开始,chrome就是一个基于多进程结构的浏览器,是业界第一个多进程浏览器,也是业界第一个影响较广的多进程结构app。时至今日,chrome在浏览器市场已经占据统治地位,基于多核的多进程软件开发模型也已经得到广泛应用普及。在chrome的发展壮大过程中,底层的进程通讯机制起到了至关重要的作用,当前chrome 的ipc通信是基于模板和IDL的面向对象编程mojo。
Chrome最主要有三类进程,一类是Browser主进程;各个Render进程;另外还有一类是utility进程,每个utility负责不同的功能,如网络,文件操作等。Render进程和utility进程都与Browser保持进程间的通信,Render进程与utility进程之间也有彼此联系的通路
跨进程的通讯能力
一个最简单的windows平台单页面情形下,进程之间的连接关系:
1 一共8条操作系统层面的ipc连接(3条握手+5条mojo通道)
2 一共5条mojo通道,用于进程间业务通讯
Chrome 启动后的进程:
其中会有一个主进程,主要负责UI展示和多进程管理;一个crashpad-handler进程,负责crash 上报;一个GPU进程,负责GPU硬件加速;n个utility进程,主要处理网络操作等;n个render进程,主要负责网页渲染。
查看其中一个子进程的启动命令行:"C:\chromium\src\out\Default\chrome.exe" --type=renderer --display-capture-permissions-policy-allowed --event-path-policy=0 --video-capture-use-gpu-memory-buffer --lang=zh-CN --device-scale-factor=2.5 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=31 --launch-time-ticks=756123642607 --mojo-platform-channel-handle=7832 --field-trial-handle=2652,i,15636273918938867148,5189539130520857060,131072 /prefetch:1
其中有一个字段是 --mojo-platform-channel-handle=7832 即主进程在启动子进程时设置的与子进程通信的握手通道。
Mojo
是一个跨平台 IPC 框架,它诞生于 chromium ,用来实现 chromium 进程内/进程间的通信。目前,它也被用于 ChromeOS。
Mojo 的分层
![](https://i-blog.csdnimg.cn/blog_migrate/0ccaeb23765569b055043d0d3a1326f4.png)
从图中看 Mojo 分4层:
Mojo Core
: Mojo 的实现层,不能独立使用,由 C++ 实现;Mojo System API(C)
: Mojo 的 C API 层,它和 Mojo Core 对接,可以在程序中独立使用;Mojo System API(C++/Java/JS)
: Mojo 的各种语言包装层,它将 Mojo C API 包装成多种语言的库,让其他语言可以使用。这一层也可以在程序中独立使用;Mojo Bindings
: 这一层引入一种称为Mojom
的 IDL(接口定义)语言,通过它可以定义通信接口,这些接口会生成接口类,使用户只要实现这些接口就可以使用 Mojo 进行通信,这一层使得IPC两端不需要通过原始字节流进行通信,而是通过接口进行通信,有点类似 Protobuf 和 Thrift 。
除了上面提到的那些层之外,在 Chromium 中还有Services(//services) 模块对 Mojo 进行了包装
services
: 一种更高层次的IPC机制,构建于Mojo之上,以 Service
的级别来进行IPC通信,Chromium大量使用这种IPC机制来包装各种服务,比如device服务,preferences服务,audio服务,viz服务等。,
Mojo 的设计
在使用 Mojo 之前,先来看一下 Mojo 的设计,这对理解后面的使用至关重要。
Mojo 支持在 多个 进程之间互相通信,这一点和其他的IPC有很大不同,其他大多只支持2个进程之间进行通信。由Mojo组成的这些可以互相通信的进程就形成了一个网络,在这个网络内的任意两个进程都可以进行通信,并且每个进程只能处于一个Mojo网络中,在这个网络内每一个进程内部有且只有一个 Node
,每一个 Node
可以提供多个 Port
,每个 Port
对应一种服务,这点类似TCP/IP中的IP地址和端口的关系。一个 Node:Port
对可以唯一确定一个服务。 Node
和 Node
之间通过 Channel
来实现通信,在不同平台上 Channel
有不同的实现方式,在Linux上是Shared memory,在windows上是name pipe,在MAC OS平台上是 Mach Port。在Port上一层,Mojo封装了3个"应用层协议",分别为 MessagePipe
, DataPipe
和 SharedBuffer
(类似在TCP上封装了HTTP,SMTP等)。整体结构如下图
上图展示了在两个进程间使用Mojo的数据流。它有以下几个特点:
Channel
: Mojo内部的实现细节,对外不可见,用于包装系统底层的通信通道,在Linux下是Shared memory,Windows下是name pipe,MAC OS下是mach port;Node
: 每个进程只有一个Node,它在Mojo中的作用相当于TCP/IP中的IP地址,同样是内部实现细节,对外不可见;Port
: 每个进程可以有上百万个Port,它在Mojo中的作用相当于TCP/IP中的端口,同样是内部实现细节,对外不可见,每个Port都必定会对应一种应用层接口,目前Mojo支持三种应用层接口;MessagePipe
: 应用层接口,用于进程间的 双向通信,类似UDP,消息是基于数据报的,底层使用Channel通道;DataPipe
: 应用层接口,用于进程间 单向块数据传递,类似TCP,消息是基于数据流的,底层使用系统的Shared Memory实现;SharedBuffer
: 应用层接口,支持 双向块数据传递,底层使用系统Shared Memory实现;MojoHandle
: 所有的 MessagePipe,DataPipe,SharedBuffer 都使用MojoHandle来包装,有了这个Handle就可以对它们进行读写操作。还可以通过MessagePipe将MojoHandle发送到网络中的任意进程。- PlatformHandle: 用来包装系统的句柄或文件描述符,可以将它转换为MojoHandle然后发送到网络中的任意进程。
MessagePipe
一个进程中可以有N多个MessagePipe,所有的MessagePipe都共享底层的一条通信通道,就像下图这样
Mojo保证同一个MessagePipe中数据的发送顺序和接收顺序一致,但是不保证多个MessagePipe之间的数据的有序。
Mojo 模糊了进程边界
需要特别说明的是,Mojo不是只能在不同进程间使用,它从一开始就考虑了在单进程中使用的场景,并且有专门的优化,因此,使用Mojo带来的一个额外好处是,在Mojo的一端进行读写不必知道另一端是运行在当前进程还是外部进程,这非常有利于将单进程程序逐步的使用Mojo拆分为多进程程序,并且可以在调试的时候使用单进程方便调试,在正式环境中使用多进程缩小程序崩溃时的影响范围。
Mojo 的应用
代码基于chromium 105版本,不同版本可能略有差异
Mojo 不仅可以在 Chromium 中使用,也可以在任何第三方程序中使用,因为它本身不依赖于 Chromium 中的业务逻辑部分。不过由于它的源码在 Chromium 中,在其他程序中使用可能没有那么方便。
Mojo提供了不同层次的API,外部可以根据自己的需要选择使用的层次,下面我们简单介绍每种API的使用方法,详细信息可以查看对应的demo程序。
目前 Mojo 支持 C++/Java/Js,这里只介绍C++相关用法。
一个MessagePipe中有一对handle,分别是handle0和handle1,向其中一个handle写的数据可以从另外一个handle读出来,这是前面已经说过的,如果把其中的一个handle发送到另外一个进程,这一对handle之间依然能够相互收发数据。Mojo提供了多种方法来发送handle到其他的进程,其中最简单的是使用 Invitation。
要在多个进程间使用Mojo,必须先通过Invitation将这些进程"连接"起来,这需要一个进程发送Invitation,另一个进程接收Invitation。
这样就实现了将pipe中的一个handle发送到其他进程了,这两个进程可以开始使用pipe进行收发数据了。
以上只是将handle从一个进程发送到另一个进程的一种方法,这种方法一般用在新进程创建的时候,如果两个进程已经通过Invitation连接起来了,那么可以通过已经建立起来的MessagePipe来发送新的MessagePipe的handle到接收进程。
简单的使用方式
![](https://i-blog.csdnimg.cn/blog_migrate/d60c70890cc20506cd843c1a975cceb7.png)
1 mojo初始化
// 初始化 mojo
mojo::core::Init();
// 初始化 Mojo 的IPC支持,只有初始化后进程间的Mojo通信才能有效
// 这个对象要保证一直存活,否则IPC通信就会断开
ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
dbus_thread_->task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
2 client 端
一个MessagePipe中有一对handle,分别是handle0和handle1,向其中一个handle写的数据可以从另外一个handle读出来,如果把其中的一个handle发送到另外一个进程,这一对handle之间依然能够相互收发数据。Mojo提供了多种方法来发送handle到其他的进程,其中最简单的是使用 Invitation。
要在多个进程间使用Mojo,必须先通过Invitation将这些进程"连接"起来,这需要一个进程发送Invitation,另一个进程接收Invitation。发送Invitation的方法如下:
-
1 service端实现dbus接口BootstrapMojoConnection,参数:文件描述符
-
2 client端"Invitation"service进行mojo通信
mojo::OutgoingInvitation invitation; // 创建一条系统级的IPC通信通道 // 在linux上是 socketpair, 该通道用于支持跨进程的消息通信 mojo::PlatformChannel channel; // 创建1个Message Pipe用来和其他进程通信 // 这里的 pipe 就相当于单进程中的pipe.handle0 // handle1 会被存储在invitation中,随后被发送出去 // 可以多次调用,以便Attach多个MessagePipe到Invitation中 mojo::ScopedMessagePipeHandle server_pipe; server_pipe = invitation.AttachMessagePipe(kBootstrapMojoConnectionChannelToken); if (!server_pipe.is_valid()) { LOG(ERROR) << "FaceAuthManager could not bind to invitation"; std::move(callback).Run(false); return; } // 发送Invitation mojo::OutgoingInvitation::Send(std::move(invitation), base::kNullProcessHandle, channel.TakeLocalEndpoint()); //idl 生成代理类,关联到server_pipe(即pipe.handle0) logger_proxy_.Bind( mojo::PendingRemote<sample::mojom::Logger>(std::move(server_pipe), 0u)); LOG(INFO) << "Bound remote interface to pipe."; //ipc关闭时候的回调 logger_proxy_.set_disconnect_handler( base::BindOnce(&mojo::Remote<sample::mojom::Logger>::reset, base::Unretained(&logger_proxy_))); //通过dbus接口将 channel.TakeRemoteEndpoint() 发送到service端 face_auth::DBusThreadManager::Get()->GetFaceAuthClient()->BootstrapMojoConnection( channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD(), base::BindOnce(&FaceAuthManager::OnBootstrapMojoConnection, base::Unretained(this), std::move(callback)));
3 service端
-
1 通过dbus接口获取到client端传递的文件描述符channel.TakeRemoteEndpoint()
-
2 接受邀请 ReceiveMojoInvitation
//Accept an invitation,其中fd即从dbus接口获得的channel.TakeRemoteEndpoint() mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept( mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(fd)))); //取出 Invitation 中的pipe ,通过token :kBootstrapMojoConnectionChannelToken //获取到mojo_pipe_handle,kBootstrapMojoConnectionChannelToken 必须与 //client 端通过invitation.AttachMessagePipe 创建server_pipe 时候一致 mojo::ScopedMessagePipeHandle mojo_pipe_handle = invitation.ExtractMessagePipe(kBootstrapMojoConnectionChannelToken); if (!mojo_pipe_handle.is_valid()) { LOG(ERROR) << "mojo_pipe_handle not is_valid"; callback_runner->PostTask( FROM_HERE, base::BindOnce(std::move(callback), /*success=*/false)); return; } // 创建impl类关联mojo_pipe_handle 即通道的pipe1 if (!service_) { service_ = std::make_unique<FaceAuthServiceImpl>( mojo::PendingReceiver<sample::mojom::Logger>(std::move(mojo_pipe_handle)), base::BindOnce(&FaceAuthService::OnDisconnect, base::Unretained(this))); } else { service_->Clone(mojo::PendingReceiver<sample::mojom::Logger>( std::move(mojo_pipe_handle))); }
实现了将pipe中的一个handle发送到其他进程了,这两个进程可以开始使用pipe进行收发数据了。
4 Mojo C++ Bindings API
Bindings API 是使用 Mojo 的重点,在项目中会大量使用
- 1 Interface:负责定义一个接口,和java的Interface性质类似,使用idl,语言无关
- 2 Receiver:server端,负责实现一个接口,响应请求,支持多种语言
- 3 Remote:client端,负责发起请求,支持多种语言
Mojo在Binding层中引入了 Mojom 这种IDL语言,用它来定义接口。接口定义文件的后缀一般为 .mojom
,一个简单的接口定义如下
// 定义接口所属的"module",在生成的C++代码中对应到命名空间
module sample.mojom;
// 定义一个接口,在C++中对应一个抽象类
interface Logger {
// 定义一个Log方法,该方法没有返回值
Log(string message);
// 定义一个GetTail方法,返回值为string类型
// 返回值会对应到C++中的回调方法
GetTail() => (string message);
};
Mojom IDL 接口在生成的标头中转换为相应的 C++(纯虚拟)类接口定义,由接口上每个请求消息的单个生成方法签名组成。在内部,还有用于消息序列化和反序列化的生成代码,但这个细节对绑定消费者是隐藏的。
定义好mojom文件后,需要在合适的BUILD.gn文件中添加一下代码,以便在编译时将mojom文件生成源代码文件:
# for mojo
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom_test") {
sources = [
"sample.mojom",
]
}
Mojo接口的使用是依赖MessagePipe的,我们知道一个MessagePipe有一对handle,这一对handle套到接口类中就是一个handle对应Logger接口,一个handle对应Logger的实现LoggerImpl。MessagePipe和Interface的关系如下图:
创建接口管道
//test interface
// 创建一个MessagePipe
mojo::MessagePipe pipe1;
mojo::Remote<sample::mojom::Logger> logger(
mojo::PendingRemote<sample::mojom::Logger>(std::move(pipe1.handle0), 0));
mojo::PendingReceiver<sample::mojom::Logger> receiver(std::move(pipe1.handle1));
这很冗长,但 C++ 绑定库提供了一种更方便的方法来完成同样的事情。remote.h定义了一个BindNewPipeAndPassReceiver方法:
mojo::Remote<sample::mojom::Logger> logger;
auto receiver = logger.BindNewPipeAndPassReceiver();
第二个片段相当于第一个片段。
一旦PendingRemote被绑定,我们就可以立即开始调用Logger它的接口方法,这将立即将消息写入管道。这些消息将在管道的接收端排队,直到有人绑定到它并开始读取它们。
logger->Log("hello");
这实际上将一条Log消息写入管道。
但是如上所述,PendingReceiver 实际上并没有做任何事情,因此该消息将永远停留在管道上。我们需要一种方法来读取管道另一端的消息并发送它们。我们必须绑定挂起的接收者
绑定挂起的接收器
绑定库中有许多不同的帮助器类,用于绑定消息管道的接收端。其中最原始的是mojo::Receiver. mojo::Receiver桥接具有T单个绑定消息管道端点(通过 mojo::PendingReceiver)的实现,它不断监视可读性。
任何时候绑定管道变得可读时,Receiver都会安排一个任务来读取、反序列化(使用生成的代码),并将所有可用消息分派到绑定T实现。下面是该Logger接口的示例实现。请注意,实现本身拥有一个mojo::Receiver. 这是一种常见的模式,因为绑定实现必须比任何mojo::Receiver绑定它的实现都长。
class LoggerImpl : public sample::mojom::Logger {
public:
// NOTE: A common pattern for interface implementations which have one
// instance per client is to take a PendingReceiver in the constructor.
explicit LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver)
: receiver_(this, std::move(receiver)) {}
~LoggerImpl() override;
// sample::mojom::Logger:
void Log(const std::string& message) override {
std::cout << "[Logger] " << message << std::endl;
}
private:
mojo::Receiver<sample::mojom::Logger> receiver_;
};
现在我们可以LoggerImpl在我们PendingReceiver的 上构造一个,并且先前排队的Log消息将在LoggerImpl的序列上尽快调度:
LoggerImpl impl(std::move(receiver));
下图说明了以下事件序列,所有这些事件都由上述代码行启动:
- 该LoggerImpl构造函数被调用,传递PendingReceiver沿到Receiver。
- 在Receiver采取所有权PendingReceiver的管道终点,并开始观察其可读性。管道可以立即读取,因此调度任务以Log尽快从管道读取待处理的消息。
- 该Log消息被读取和反序列化,导致Receiver调用Logger::Log其限制的实施LoggerImpl。
益处
1 client端不关心impl的生命周期,初始化与否等
2 client端不关心impl是否在同一个进程内
3 client端不关心impl是否在同一个线程内
4 所有异步数据使用callback的方式返回
5 基于接口的方式编程,可维护性,耦合性都较ipc更好
接收回复
一些 Mojom 接口方法需要返回值,如:
module sample.mojom;
interface Logger {
Log(string message);
GetTail() => (string message);
};
生成的 C++ 接口现在看起来像:
namespace sample {
namespace mojom {
class Logger {
public:
virtual ~Logger() {}
virtual void Log(const std::string& message) = 0;
using GetTailCallback = base::OnceCallback<void(const std::string& message)>;
virtual void GetTail(GetTailCallback callback) = 0;
}
} // namespace mojom
} // namespace sample
和以前一样,这个接口的客户端和实现都使用相同的GetTail方法签名:实现使用callback参数来响应请求,而客户端传递一个callback参数来异步receive响应。GetTailCallback传递给实现的参数GetTail是序列仿射。它必须在被调用的相同序列上GetTail被调用。客户端callback运行在它们调用的相同序列上GetTail(它们logger被绑定到的序列)。
logger->GetTail(base::BindOnce([](const std::string& message){
std::cout << "Tail was: " << message <<std::endl;
}));
连接错误
如果管道断开连接,则两个端点都将能够观察到连接错误(除非断开连接是由关闭/销毁端点引起的,在这种情况下,该端点将不会收到此类通知)。如果断开连接时端点还有剩余的传入消息,则在消息耗尽之前不会触发连接错误。
管道断开可能由以下原因引起:
-
Mojo 系统级原因:进程终止、资源耗尽等。
-
由于在处理收到的消息时出现验证错误,绑定会关闭管道。
-
对等端点已关闭。例如,远程端是一个边界mojo::Remote,它被破坏了。
不管根本原因是什么,当在接收方端点上遇到连接错误时,将调用该端点的断开连接处理程序(如果已设置)。这个处理程序很简单base::OnceClosure,只要端点绑定到同一个管道,就只能调用一次。通常,客户端和实现使用此处理程序进行某种清理,或者------特别是如果错误是意外的------创建一个新管道并尝试与其建立新连接。
所有消息管结合C ++对象(例如,mojo::Receiver,mojo::Remote,等等)支持经由设置它们的断开处理程序set_disconnect_handler方法。
我们可以设置另一个端到端Logger示例来演示断开连接处理程序调用。假设LoggerImpl在其构造函数中设置了以下断开连接处理程序 receiver_.set_disconnect_handler:LoggerImpl::LoggerImpl(mojo::PendingReceiversample::mojom::Logger receiver)
: receiver_(this, std::move(receiver)) {
receiver_.set_disconnect_handler(
base::BindOnce(&LoggerImpl::OnError, base::Unretained(this)));
}........
logger.reset(); // Closes the client end.
只要在impl这里保持活动状态,它最终会收到Log消息,然后立即调用输出"Client disconnected! Purging log lines.". 与所有其他接收器回调一样,一旦其相应的接收器对象被销毁,断开连接处理程序将永远不会被调用。
使用base::Unretained是安全的,因为错误处理程序永远不会在receiver_和this拥有的生命周期之后被调用receiver_。
关于端点生命周期和回调的说明
一旦 mojo::Remote被销毁,就可以保证不会调用挂起的回调以及连接错误处理程序(如果已注册)。
一旦 mojo::Receiver被销毁,就可以保证不会再向实现分派更多的方法调用,并且不会调用连接错误处理程序(如果已注册)。
处理流程崩溃和回调的最佳实践
调用采用回调的 mojo 接口方法时的一种常见情况是调用者想知道另一个端点是否被拆除(例如,由于崩溃)。在这种情况下,消费者通常想知道响应回调是否不会运行。这个问题有不同的解决方案,这取决于它是如何Remote保持的:
- 消费者拥有Remote:set_disconnect_handler应该使用。
- 消费者不拥有Remote:根据调用者想要的行为,有两个助手。如果调用者想要确保运行错误处理程序,mojo::WrapCallbackWithDropHandler则应使用。如果调用者希望回调始终运行,mojo::WrapCallbackWithDefaultInvokeIfNotRun则应使用 helper。对于这两个助手,应遵循通常的回调注意事项,以确保回调不会在消费者被破坏后运行(例如,因为消费者的所有者比Remote消费者更长寿)。这包括使用base::WeakPtr或base::RefCounted。还应该注意的是,使用这些帮助程序,可以在重置或销毁 Remote 时同步运行回调。
注意事项
关闭管道的一端最终会在另一端触发连接错误。然而,重要的是要注意这个事件本身是相对于管道上的任何其他事件(例如写入消息)进行排序的。
这意味着编写一些人为的东西是安全的:
LoggerImpl::LoggerImpl(mojo::PendingReceiver<sample::mojom::Logger> receiver,
base::OnceClosure disconnect_handler)
: receiver_(this, std::move(receiver)) {
receiver_.set_disconnect_handler(std::move(disconnect_handler));
}
void GoBindALogger(mojo::PendingReceiver<sample::mojom::Logger> receiver) {
base::RunLoop loop;
LoggerImpl impl(std::move(receiver), loop.QuitClosure());
loop.Run();
}
void LogSomething() {
mojo::Remote<sample::mojom::Logger> logger;
bg_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&GoBindALogger, logger.BindNewPipeAndPassReceiver()));
logger->Log("OK Computer");
}
当logger超出范围时,它会立即关闭消息管道的末端,但 impl 端在收到发送的Log消息之前不会注意到这一点。因此,impl上面将首先记录我们的消息,然后看到连接错误并跳出运行循环。
5 chromium Mojo Services
一个 Service通过提供一个或 多个 Mojo接口来暴露一套服务,一个服务可以通过 Connector来调用其他的服务,但并不是所有的服务之间都可以随意调用,而是通过 Service Manager来管理多个 Service 间的依赖关系,只有明确表示有依赖关系的服务才能够被调用,而依赖关系则是通过 Manifest来定义的,所有的这些Manifest组合在一起称为一个 Catalog。 这套机制的实现使用 Mojom 定义接口,其中最重要的是 service_manager, service, connector。
下图展示了Service A 和 Service B通过定义Manifest依赖,使用Connector相互调用的示意图:
当Service A要调用Serivce B提供的接口的时候,通过自己的Connector对象向ServiceManager发起接口Binding请求,Service Manager会收到这个请求,然后根据已经注册的Manifest依赖关系,验证Service A是否声明了对Service B的依赖,如果是,则Service Manager会检查Service B是否已经存在,如果已经存在,则使用Serivce B,否则启动一个新的Service B实例,这个实例可能运行在一个独立的进程中,这样Service A就获得了由Service B提供的一个Mojo接口。
需要注意的是,Services是多进程架构,以上的Service A,Service B以及Service Mananger 都可以运行在独立的进程中。Service Manager 所在的进程称为 Borker 进程。为了方便用户使用该多进程架构,Services模块提供了一套创建新的可执行程序的框架,目前 Chromium 的启动就使用了该框架,该框架内部会进行一些常用功能的初始化,比如日志,崩溃时的handler,CommandLine,mojo,Tracker/Trace,LANG等,其中最重要的是process_type,默认情况下进程是Embedder类型的,在Chromium中只使用了这个类型。详细信息可以查看
Mojo底层机制
![](https://i-blog.csdnimg.cn/blog_migrate/6153f8d808ec93ac304a98e0a20f3f52.png)
mojo底层管道的创建
mojo的通信依赖于底层 MessagePipe
,管道的创建在 mojo/core/core.cc
中 core::CreateMessagePipe
,传入管道的两端 handle
,这里的handle就是mojo handle了,管道创建成功并不意味着底层socket创建,发送invitation的时候才会真正创建socket
Node
网络
用于给mojo提供消息路由功能,在全局对象core中存在一个 node_controller_
在创建 MessagePipeDispatcher
的时候,会将 NodeController
对象传入。
NodeController
持有 Node
(由于全局对象core持有 NodeController
是否说明 NodeController
对象也是全局唯一?)
Node维护了一系列的Port,Port有name
NodeController
有一个 peers_
的map,当收到invitation的时候( OnAcceptInvitation
)建立连接,并将对端存入这个map
using NodeMap = std::unordered_map<ports::NodeName, scoped_refptr<NodeChannel>>;
NodeMap peers_;
OnAcceptInvation
的实质是收到一个Message(来源于 OnChannelMessage
) 发送邀请过程如下,A邀请B (A) SendInvitation
->(A) AcceptInvitee
->(B) OnAcceptInvitee
->(B) AcceptInvitation
->(A) OnAcceptInvitation
->(A) AddPeer
,邀请结束,链接建立成功(两次握手)其中, AcceptInvitee
和 AcceptInvitation
的消息都是通过 NodeChannel
的 Channel
对象发送消息 按照文档说明,node与node之间一开始只有IPC点对点通信,假如要发给另一个node,但是和当前node之间没有直接的IPC,则需要通过broker建立一个新的IPC通道, REQUEST_INTRODUCTION
消息就是这个过程
NodeChannel
和 NodeName
一起保存在 peers_
中。对上封装的接口,用于Node to Node的IPC接口。
NodeChannel::Introduce
负责介绍两个不认识的node相互认识(建立IPC通道)
也是在Invitation阶段创建的,和PlatformChannel一致,连接两端时创建
Channel
NodeChannel
对应的各平台实现,自身对象由 NodeChannel
持有 NodeChannel::WriteChannelMessage
会通过 Channel::Write
发送消息,对端信息包含在参数的message中,channel本身持有socket(posix),这个socket其实就是 PlatformChannel
创建的,message参数中包含对端的PlatformHandle,这里的PlatformHandle其实是系统对应的handle,Windows的HANDLE,posix文件描述符,Handle里面存的就是最底层的通信手段,例如socket或者是管道
port
消息Messages在Ports的两端传输,一组Ports就代表了一个外层的MessagePipe
PlatformChannel
mojo底层用来通信的通道,本质是 Unix Domain Socket
,在 Core::SendInvitation
的时候创建底层的sockcet Core::AcceptInvitation
时也会创建(Windows不是socket,而是named pipe)
dispatcher
dispatcher负责分发实现通用接口的不同实现方式,dispatcher在core内被创建时会存放到 HandleTable
中,并返回对应的MojoHandle
例如 MessagePipeDispatcher
就持有 NodeController
,是由全局对象core传过来的
MessagePipeDispatcher
对象还持有一个 port
,也是在一创建就传入的(由 NodeController
和其 Node
通过 CreatePortPair
创建的,在 CreateMessagePipe
的时候)
Broker
一个单独的node在node网络中被称作Broker,它的作用是
- 提供介绍机制(Introduce)帮助一对node建立IPC链接
- 复制node的handle(由于沙盒,node自身可能办不到)
- 创建共享内存
broker是个特殊的node channel,规定只有它能introduce(通过调用 NodeController
的能力找到两个node)并发送Introduce message,这就要求broker需要跟每个node都有链接
在接受invitation和创建invitation的时候,例如在 NodeController::OnAcceptInvitation
中,执行 AddPeer
添加对端后,还给 broker channel
添加了远端的client
相关类:https://zhuanlan.zhihu.com/p/426069459
Core:https://source.chromium.org/chromium/chromium/src/+/main:mojo/core/README.md;bpv=1;bpt=0
embedder:https://source.chromium.org/chromium/chromium/src/+/main:mojo/core/embedder/README.md
Invitations:https://source.chromium.org/chromium/chromium/src/+/main:mojo/public/cpp/system/README.md#Invitations
Mojo Core底层结构
![](https://i-blog.csdnimg.cn/blog_migrate/445e953c5fcfa554eb945832201838e2.png)
- Core 为mojo的核心类,接口暴露给外部用,一个进程只对应一个Core,这个类管理成员NodeController和Handletable
- MessagePipe是消息管道,保存了两个MessagePipeHandle,用于找到对应的MessagePipeDispatcher,每个mojo接口对应一个MessagePipe
- MessagePipeDispatcher是每个mojo接口的消息处理类,MessagePipeDispatcher对应一个MessagePipeHandle
- Handletable是用于存储MojoHanlde和MessagePipeDispatcher,MojoHanlde和MessagePipeDispatcher是一一对应关系,MojoHanlde是uint64_t数字
- MessagePipeHandle实质上就是MojoHanlde
- Node是相当于ip节点概念
- Port相当于端口概念,每个mojo接口都有自己的Port,这样就可以方便进程间通信,可以寻找到应的接口处理
- NodeController用于管理Node
- Channel是进程建通信通道管理,每个平台有对应的实现,Linux对应的实现是Shared memory,用于进程间通信的消息读写等
- SimpleWatcher用于监听Port消息
- Connector用于读取和处理Port的消息队列事件
Core类
![](https://i-blog.csdnimg.cn/blog_migrate/541a855eb97471548b102fbae909036b.png)
文件路径:mojo/core/core.h mojo/core/ http://core.cc
Core主要成员函数对外暴露的接口,主要的接口有创建消息管道,写消息,读消息等等
class MOJO_SYSTEM_IMPL_EXPORT Core {
......
// These methods correspond to the API functions defined in
// "mojo/public/c/system/message_pipe.h":
MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options,
MojoHandle* message_pipe_handle0,
MojoHandle* message_pipe_handle1);
MojoResult WriteMessage(MojoHandle message_pipe_handle,
MojoMessageHandle message_handle,
const MojoWriteMessageOptions* options);
MojoResult ReadMessage(MojoHandle message_pipe_handle,
const MojoReadMessageOptions* options,
MojoMessageHandle* message_handle);
MojoResult FuseMessagePipes(MojoHandle handle0,
MojoHandle handle1,
const MojoFuseMessagePipesOptions* options);
MojoResult NotifyBadMessage(MojoMessageHandle message_handle,
const char* error,
size_t error_num_bytes,
const MojoNotifyBadMessageOptions* options);
......
private:
// This is lazily initialized on first access. Always use GetNodeController()
// to access it.
std::unique_ptr<NodeController> node_controller_;
// The default callback to invoke, if any, when a process error is reported
// but cannot be associated with a specific process.
ProcessErrorCallback default_process_error_callback_;
std::unique_ptr<HandleTable> handles_;
......
};
如上代码可知,Core成员是NodeController和Handletable,所以一个进程中只有一份NodeController和Handletable
MessagePipe类
![](https://i-blog.csdnimg.cn/blog_migrate/f5df9cc368bca203662f4e28a8742376.png)
路径:mojo/public/cpp/system/message_pipe.h
MessagsePipe类主要是存储了两个ScopedMessagePipeHandle handle,ScopedMessagePipeHandle是本质是MessagePipeHandle的包装,MessagePipeHandle是一个MojoHandle,MojoHandle就是一个uint64_t的id,这个主要就是用于查找MessagePipeDispatcher的。
class MessagePipe {
public:
MessagePipe();
explicit MessagePipe(const MojoCreateMessagePipeOptions& options);
~MessagePipe();
ScopedMessagePipeHandle handle0;
ScopedMessagePipeHandle handle1;
};
如上是MessagePipe的源代码
typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle;
如上是ScopedMessagePipeHandle的源码
class MessagePipeHandle : public Handle {
public:
MessagePipeHandle() {}
explicit MessagePipeHandle(MojoHandle value) : Handle(value) {}
// Copying and assignment allowed.
};
如上是MessagePipeHandle的代码,可以看出它的基类是Handle
// Wrapper base class for |MojoHandle|.
class Handle {
......
private:
MojoHandle value_;
// Copying and assignment allowed.
};
如上可以知道Handle就是MojoHandle包装
typedef uint64_t MojoHandle;
MojoHandle就是一个uint64_t数字
整体关系如下图:
Handletable类
![](https://i-blog.csdnimg.cn/blog_migrate/b4e6c797674aabc336ba555d357bb064.png)
路径:mojo/core/handle_table.h
Handletable主要是用于管理存储MojoHanlde和MessagePipeDispatcher(基类是Dispatcher),他们之间的对应关系是通过unordered_map存储的,key是MojoHanlde,value是MessagePipeDispatcher,每添加一个MessagePipeDispatcher就会绑定一个handle。MojoHanlde是通过next_available_handle_++得到的,这样可可以保证每个hanlde都是唯一的。
可以查看HandleTable源码
class MOJO_SYSTEM_IMPL_EXPORT HandleTable
: public base::trace_event::MemoryDumpProvider {
......
struct Entry {
Entry();
explicit Entry(scoped_refptr<Dispatcher> dispatcher);
Entry(const Entry& other);
~Entry();
scoped_refptr<Dispatcher> dispatcher;
bool busy = false;
};
using HandleMap = std::unordered_map<MojoHandle, Entry>;
HandleMap handles_;
base::Lock lock_;
uint64_t next_available_handle_ = 1;
};
可以看到handles_就是一个 std::unordered_map<MojoHandle, Entry>,而Entry上是Dispatcher的包装。
接下来我们看下MojoHandle的由来
MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
// Oops, we're out of handles.
if (next_available_handle_ == MOJO_HANDLE_INVALID)
return MOJO_HANDLE_INVALID;
MojoHandle handle = next_available_handle_++; // j计算handle
auto result =
handles_.insert(std::make_pair(handle, Entry(std::move(dispatcher)))); // handle和dispatcher绑定到一起
DCHECK(result.second);
return handle;
}
由上代码可以知道当AddDispatcher一个Dispatcher时候next_available_handle_++这样就得到了一个唯一的handle与dispatcher对应,然后插入到handles_中
MessagePipeDispatcher类
![](https://i-blog.csdnimg.cn/blog_migrate/91000f5f555e1d52b37153903bf13701.png)
路径:mojo/core/message_pipe_dispatcher.h
MessagePipeDispatcher类是一个消息处理中间类,它的基类是Dispatcher。
当Proxy端调用则通过调用MessagePipeDispatcher::WriteMessage函数通过NodeContorl发送消息给另一个进程
当在Impl端则通过MessagePipeDispatcher::ReadMessage向Port读取消息队列读取消息,然后传给对应的mojo接口实现处理。
class MessagePipeDispatcher : public Dispatcher {
public:
.......
// Dispatcher:
Type GetType() const override;
MojoResult Close() override;
MojoResult WriteMessage(
std::unique_ptr<ports::UserMessageEvent> message) override;
MojoResult ReadMessage(
std::unique_ptr<ports::UserMessageEvent>* message) override;
.......
private:
.......
NodeController* const node_controller_;
const ports::PortRef port_;
const uint64_t pipe_id_; // 一个随机值
const int endpoint_; // 0或者1
.......
};
可以看MessagePipeDispatcher中存储了NodeController和PortRef。PortRef是存储消息对应的Port和PortName,主要用于读取对应的Port的消息队列。如下源码:
class COMPONENT_EXPORT(MOJO_CORE_PORTS) PortRef {
public:
~PortRef();
PortRef();
PortRef(const PortName& name, scoped_refptr<Port> port);
PortRef(const PortRef& other);
PortRef(PortRef&& other);
PortRef& operator=(const PortRef& other);
PortRef& operator=(PortRef&& other);
const PortName& name() const { return name_; }
bool is_valid() const { return !!port_; }
private:
friend class PortLocker;
Port* port() const { return port_.get(); }
PortName name_; // port的name
scoped_refptr<Port> port_;// port的指针
};
-
Proxy端消息发送主要的调用函数MessagePipeDispatcher::WriteMessage
MojoResult MessagePipeDispatcher::WriteMessage(
std::unique_ptrports::UserMessageEvent message) {
if (port_closed_ || in_transit_)
return MOJO_RESULT_INVALID_ARGUMENT;int rv = node_controller_->SendUserMessage(port_, std::move(message));
......
return MOJO_RESULT_OK;
}
如上代码主要是通过node_controller_->SendUserMessage把message发送给另一个进程,然后可以看到会把port带上,这样就可以发送给另一个进程对应的mojo impl处理。
通过下面的堆栈可看出,最后通过NodeChannel中的Channel 发送,每个平台channel的具体实现不同
2 Impl端读取消息主要的调用函数MessagePipeDispatcher::ReadMessage
MojoResult MessagePipeDispatcher::ReadMessage(
std::unique_ptr<ports::UserMessageEvent>* message) {
// We can't read from a port that's closed or in transit!
if (port_closed_ || in_transit_)
return MOJO_RESULT_INVALID_ARGUMENT;
int rv = node_controller_->node()->GetMessage(port_, message, nullptr);
......
return MOJO_RESULT_OK;
}
如上代码,则可以知道消息读取的时候是通过node_controller_->node()->GetMessage上读取的,本质上就是读取port里面的消息队列
Node类
路径:mojo/core/ports/node.h
相当于ip的概念,管理着port端口,在每个进程中只有一个node,但是可以多个port端口,因为在进程中肯定不止一个mojo接口,那怎么去区分接口呢?那就是通过port概念区分。
Node管理着PortName和Port,PortName和Port存储的数据结构是unordered_map。如下代码
ace-line
·
class COMPONENT_EXPORT(MOJO_CORE_PORTS) Node {
......
const NodeName name_;
const DelegateHolder delegate_;
// Just to clarify readability of the types below.
using LocalPortName = PortName;
using PeerPortName = PortName;
// Guards access to |ports_| and |peer_port_maps_| below.
//
// This must never be acquired while an individual port's lock is held on the
// same thread. Conversely, individual port locks may be acquired while this
// one is held.
//
// Because UserMessage events may execute arbitrary user code during
// destruction, it is also important to ensure that such events are never
// destroyed while this (or any individual Port) lock is held.
base::Lock ports_lock_;
std::unordered_map<LocalPortName, scoped_refptr<Port>> ports_;
......
};
Port类
路径:mojo/core/ports/port.h
port主要是存储消息队列的,当进程1的接口向进程2发送一个消息,则进程2的会从底层读取到消息存在port的MessageQueue队列中,然后等待node读取进行处理。
class Port : public base::RefCountedThreadSafe<Port> {
......
// The queue of incoming user messages received by this Port. Only non-empty
// for buffering or receiving Ports. When a buffering port enters the proxying
// state, it flushes its queue and the proxy then bypasses the queue
// indefinitely.
//
// A receiving port's queue only has elements removed by user code reading
// messages from the port.
//
// Note that this is a priority queue which only exposes messages to consumers
// in strict sequential order.
MessageQueue message_queue;
......
};
如上MessageQueue就是消息队列
class COMPONENT_EXPORT(MOJO_CORE_PORTS) MessageQueue {
......
private:
std::vector<std::unique_ptr<UserMessageEvent>> heap_;
uint64_t next_sequence_num_;
bool signalable_ = true;
size_t total_queued_bytes_ = 0;
};
如上源码可以看出UserMessageEvent是存储的为一个vector数组结构。
ChannelPosix类
路径:mojo/core/channel_posix.h mojo/core/ http://channel_posix.cc
用于保存进程间通道fd句柄,最底层的消息读取,消息发送。不同的平台有差异,Linux平台实现类是ChannelPosix。
class ChannelPosix : public Channel,
public base::CurrentThread::DestructionObserver,
public base::MessagePumpForIO::FdWatcher {
public:
ChannelPosix(Delegate* delegate,
ConnectionParams connection_params,
HandlePolicy handle_policy,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
ChannelPosix(const ChannelPosix&) = delete;
ChannelPosix& operator=(const ChannelPosix&) = delete;
void Start() override;
void ShutDownImpl() override;
void Write(MessagePtr message) override; // 消息发送
void LeakHandle() override;
bool GetReadPlatformHandles(const void* payload,
size_t payload_size,
size_t num_handles,
const void* extra_header,
size_t extra_header_size,
std::vector<PlatformHandle>* handles,
bool* deferred) override;
bool OnControlMessage(Message::MessageType message_type,
const void* payload,
size_t payload_size,
std::vector<PlatformHandle> handles) override;
......
private:
......
// We may be initialized with a server socket, in which case this will be
// valid until it accepts an incoming connection.
PlatformChannelServerEndpoint server_;
// The socket over which to communicate. May be passed in at construction time
// or accepted over |server_|.
base::ScopedFD socket_;// fd句柄
// These watchers must only be accessed on the IO thread.
std::unique_ptr<base::MessagePumpForIO::FdWatchController> read_watcher_;
std::unique_ptr<base::MessagePumpForIO::FdWatchController> write_watcher_;
.......
};
如上socket_就是socket的fd通信句柄。那创建fd是在哪里呢?
-
PlatformChannel是用于创建fd的地方
void CreateChannel(PlatformHandle* local_endpoint,
PlatformHandle* remote_endpoint) {
int fds[2];
#if BUILDFLAG(IS_NACL)
PCHECK(imc_socketpair(fds) == 0);
#else
PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);// Set non-blocking on both ends.
PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0);
PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0);#if BUILDFLAG(IS_APPLE)
// This turns off |SIGPIPE| when writing to a closed socket, causing the call
// to fail with |EPIPE| instead. On Linux we have to use |send...()| with
// |MSG_NOSIGNAL| instead, which is not supported on Mac.
int no_sigpipe = 1;
PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
sizeof(no_sigpipe)) == 0);
PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
sizeof(no_sigpipe)) == 0);
#endif // BUILDFLAG(IS_APPLE)
#endif // BUILDFLAG(IS_NACL)*local_endpoint = PlatformHandle(base::ScopedFD(fds[0]));
*remote_endpoint = PlatformHandle(base::ScopedFD(fds[1]));
DCHECK(local_endpoint->is_valid());
DCHECK(remote_endpoint->is_valid());
}
如上代码CreateChannel就是用于创建fd通信句柄的函数,主要是调用系统的socketpair创建。
2.ChannelPosix::WriteNoLock函数用于发送消息,最终是调用send发送消息
ssize_t SocketWrite(base::PlatformFile socket,
const void* bytes,
size_t num_bytes) {
#if BUILDFLAG(IS_APPLE)
return HANDLE_EINTR(write(socket, bytes, num_bytes));
#else
return send(socket, bytes, num_bytes, kSendmsgFlags);
#endif
}
3.ChannelPosix::OnFileCanReadWithoutBlocking用于读取消息,最终读取到port消息队列中
NodeChannel 类
节点到节点 IPC
处理节点到节点 IPC 的业务主要在 mojo::core::NodeChannel以及 mojo::core::Channel及其特定于操作系统的子类中通过交换 mojo::core::Channel::Message来处理。
请注意,Channel::Message 始终通过 Channel 从源节点到达目标节点(严格来说,每一侧都有一个)。由于两侧的通道(或操作系统提供的 IPC 机制)共同复制句柄,因此句柄传输中涉及的过程始终是隐式的。这意味着低权限或沙盒进程只能在消息中发送自己的句柄,并且不可能滥用它来获取另一个进程的句柄。
mojo\core\node_channel.h
// Wraps a Channel to send and receive Node control messages.
class MOJO_SYSTEM_IMPL_EXPORT NodeChannel
: public base::RefCountedDeleteOnSequence<NodeChannel>,
public Channel::Delegate {
.......
base::Lock channel_lock_;
scoped_refptr<Channel> channel_ GUARDED_BY(channel_lock_);
// Must only be accessed from the owning task runner's thread.
ports::NodeName remote_node_name_;
uint64_t remote_capabilities_ = kNodeCapabilityNone;
uint64_t local_capabilities_ = kNodeCapabilityNone;
base::Lock remote_process_handle_lock_;
base::Process remote_process_handle_;
};
值得注意的是:在Linux: Linux、CrOS 和 Android 上,通道换成了共享内存
/ ChannelLinux 是 ChannelPosix 的一个特例,它支持共享
// 通过 Mojo 通道升级的内存。 默认情况下在 Linux、CrOS 和 Android 上
// 每个通道都是可以在运行时升级的 ChannelLinux 类型
// 当所有必需的内核功能都已完成时,利用共享内存
// 展示。
// static
scoped_refptr<Channel> Channel::Create(
Delegate* delegate,
ConnectionParams connection_params,
HandlePolicy handle_policy,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
#if !BUILDFLAG(IS_NACL) && \
(BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID))
return new ChannelLinux(delegate, std::move(connection_params), handle_policy,
io_task_runner);
#else
return new ChannelPosix(delegate, std::move(connection_params), handle_policy,
io_task_runner);
#endif
}
Mojo消息发送流程
![](https://i-blog.csdnimg.cn/blog_migrate/90212d4f9f6b73c9ed23cff35b1ca2d0.png)
上图就是在Proxy端调用mojo接口,到系统底层发送给另一进程的主要流程图。注意图中Endpoint和MultiplexRouter::InterfaceEndpoint的基类是InterfaceEndpointController,他们区别是Endpoint是用于非关联接口的,而MultiplexRouter::InterfaceEndpoint用于关联接口的
Mojo消息读取处理流程
![](https://i-blog.csdnimg.cn/blog_migrate/3cb065325257862602b9ad969a73f37d.png)
上图就是mojo的接口Impl消息处理流程图,这里要注意异步关系,消息是先从底层读取到port消息队列中,然后SimpleWatcher监听到有port有消息需要处理,则进行处理,所以读消息和处理消息不是在一个任务中。如果消息携带需要创建其他接口Handle,则调用ExtractSerializedHandles最终创建一个MessagePipeDispatcher
接收数据堆栈:
![](https://i-blog.csdnimg.cn/blog_migrate/3b5e6bfb1f701cb42ed6abda577ccb23.png)
void NodeChannel::OnChannelMessage(const void* payload,
size_t payload_size,
std::vector<PlatformHandle> handles)
......
//当前是消息类型
case MessageType::EVENT_MESSAGE: {
Channel::MessagePtr message =
Channel::Message::CreateMessage(payload_size, handles.size());
message->SetHandles(std::move(handles));
memcpy(message->mutable_payload(), payload, payload_size);
delegate_->OnEventMessage(remote_node_name_, std::move(message));
return;
}
......
}
void NodeController::OnEventMessage(const ports::NodeName& from_node,
Channel::MessagePtr channel_message) {
.......
node_->AcceptEvent(from_node, std::move(event));
.......
}
int Node::AcceptEvent(const NodeName& from_node, ScopedEvent event) {
.......
PortRef port_ref;
//获取 PortRef ,包括了对端的port,该远端port对应的己方po
GetPort(event->port_name(), &port_ref);
}
![](https://i-blog.csdnimg.cn/blog_migrate/8cd5cf09de039cac4aebb18131e5eca5.png)
int Node::AcceptEvent(const NodeName& from_node, ScopedEvent event) {
PortRef port_ref;
GetPort(event->port_name(), &port_ref);
#ifndef MOJO_BACKWARDS_COMPAT
DVLOG(2) << "AcceptEvent type: " << event->type() << ", "
<< event->from_port() << "@" << from_node << " => "
<< port_ref.name() << "@" << name_
<< " seq nr: " << event->control_sequence_num() << " port valid? "
<< port_ref.is_valid();
Mojo 接口内部类关系
假设有3个进程,有3个mojo接口,他们之间相互通信,那Mojo Core内部的对应关系是怎么样的?
A接口,在进程1中创建,作用是和进程3通信
B接口,在进程2中创建,作用是和进程1通信
C接口,在进程3中创建,作用是和进程2通信
通过这3个进程和3个mojo接口,我们看下mojo core内部对应的结构
解释:
- 从上面可以看出每个mojo接口有进程发送的Proxy端,实现端是Impl端
- 每个mojo接口对一一对应一个InterfaceEndPointClient和InterfaceEnd
- 在Proxy端对应两个MessagePipeDispatcher消息处理,这个是存储在HanldeTable中
- 每个MessagePipeDispatcher会对应一个port(端口),端口会对应一个port_name
- 一个进程只有一个Node
- 一个进程只有一个NodeController
- 假如进程1和2,3进程通信,则在进程1中会有对相应2,3的NodeChannel和ChannelPosix(Linux平台)
Core
mojo\core\core.h
|Core| is an object that implements the Mojo system calls. All public methods
are thread-safe.
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
。。。。。。
private:
// Used to pass ownership of our NodeController over to the IO thread in the
// event that we're torn down before said thread.
static void PassNodeControllerToIOThread(
std::unique_ptr<NodeController> node_controller);
// Guards node_controller_.
//
// TODO(rockot): Consider removing this. It's only needed because we
// initialize node_controller_ lazily and that may happen on any thread.
// Otherwise it's effectively const and shouldn't need to be guarded.
//
// We can get rid of lazy initialization if we defer Mojo initialization far
// enough that zygotes don't do it. The zygote can't create a NodeController.
base::Lock node_controller_lock_;
// This is lazily initialized on first access. Always use GetNodeController()
// to access it.
std::unique_ptr<NodeController> node_controller_;
// The default callback to invoke, if any, when a process error is reported
// but cannot be associated with a specific process.
ProcessErrorCallback default_process_error_callback_;
std::unique_ptr<HandleTable> handles_;
base::Lock mapping_table_lock_; // Protects |mapping_table_|.
using MappingTable =
std::unordered_map<void*, std::unique_ptr<PlatformSharedMemoryMapping>>;
MappingTable mapping_table_;
};
} // namespace core
} // namespace mojo
#endif // MOJO_CORE_CORE_H_
其中 HandleTable中存在一个const std::unordered_map<MojoHandle, Entry>& GetUnderlyingMap() const;存储 handle 和对应分发器Dispatcher的映射关系。代码如下:MojoHandle HandleTable::AddDispatcher
MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
// Oops, we're out of handles.
if (next_available_handle_ == MOJO_HANDLE_INVALID)
return MOJO_HANDLE_INVALID;
MojoHandle handle = next_available_handle_++;
const bool inserted = entries_.Add(handle, Entry(std::move(dispatcher)));
DCHECK(inserted);
return handle;
}
WriteMessage
MojoResult Core::WriteMessage(MojoHandle message_pipe_handle,
MojoMessageHandle message_handle,
const MojoWriteMessageOptions* options) {
RequestContext request_context;
if (!message_handle)
return MOJO_RESULT_INVALID_ARGUMENT;
auto message_event = base::WrapUnique(
reinterpret_cast<ports::UserMessageEvent*>(message_handle));
auto* message = message_event->GetMessage<UserMessageImpl>();
if (!message || !message->IsTransmittable())
return MOJO_RESULT_INVALID_ARGUMENT;
//找到该 message_pipe_handle 对应的 dispatcher
auto dispatcher = GetDispatcher(message_pipe_handle);
if (!dispatcher)
return MOJO_RESULT_INVALID_ARGUMENT;
//调用 dispatcher 发送消息 MojoResult MessagePipeDispatcher::WriteMessage
return dispatcher->WriteMessage(std::move(message_event));
}
CreateMessagePipe
MojoResult Core::CreateMessagePipe(const MojoCreateMessagePipeOptions* options,
MojoHandle* message_pipe_handle0,
MojoHandle* message_pipe_handle1) {
RequestContext request_context;
//1 分配两个port 并且初始化
ports::PortRef port0, port1;
GetNodeController()->node()->CreatePortPair(&port0, &port1);
DCHECK(message_pipe_handle0);
DCHECK(message_pipe_handle1);
uint64_t pipe_id = base::RandUint64();
//2 见MojoHandle HandleTable::AddDispatcher,实质是
//将 uint64_t next_available_handle_ ++ ,建立一个next_available_handle_ 与Dispatcher
//的映射,然后将next_available_handle_ 返回给message_pipe_handle0
*message_pipe_handle0 = AddDispatcher(
new MessagePipeDispatcher(GetNodeController(), port0, pipe_id, 0));
if (*message_pipe_handle0 == MOJO_HANDLE_INVALID)
return MOJO_RESULT_RESOURCE_EXHAUSTED;
*message_pipe_handle1 = AddDispatcher(
new MessagePipeDispatcher(GetNodeController(), port1, pipe_id, 1));
if (*message_pipe_handle1 == MOJO_HANDLE_INVALID) {
scoped_refptr<Dispatcher> dispatcher0;
{
base::AutoLock lock(handles_->GetLock());
handles_->GetAndRemoveDispatcher(*message_pipe_handle0, &dispatcher0);
}
dispatcher0->Close();
return MOJO_RESULT_RESOURCE_EXHAUSTED;
}
return MOJO_RESULT_OK;
}
AttachMessagePipeToInvitation
MojoResult Core::AttachMessagePipeToInvitation(
MojoHandle invitation_handle,
const void* name,
uint32_t name_num_bytes,
const MojoAttachMessagePipeToInvitationOptions* options,
MojoHandle* message_pipe_handle) {
if (options && options->struct_size < sizeof(*options))
return MOJO_RESULT_INVALID_ARGUMENT;
if (!message_pipe_handle)
return MOJO_RESULT_INVALID_ARGUMENT;
if (name_num_bytes == 0)
return MOJO_RESULT_INVALID_ARGUMENT;
scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
return MOJO_RESULT_INVALID_ARGUMENT;
auto* invitation_dispatcher =
static_cast<InvitationDispatcher*>(dispatcher.get());
RequestContext request_context;
ports::PortRef remote_peer_port;
// 创建一个消息管道端点,其中返回一个未绑定的对等端口|*对等体remote_peer_port
MojoHandle local_handle = CreatePartialMessagePipe(&remote_peer_port);
if (local_handle == MOJO_HANDLE_INVALID)
return MOJO_RESULT_RESOURCE_EXHAUSTED;
MojoResult result = invitation_dispatcher->AttachMessagePipe(
base::StringPiece(static_cast<const char*>(name), name_num_bytes),
std::move(remote_peer_port));
if (result != MOJO_RESULT_OK) {
Close(local_handle);
return result;
}
*message_pipe_handle = local_handle;
return MOJO_RESULT_OK;
}
CreatePartialMessagePipe
创建一个消息管道端点,其中返回一个未绑定的对等端口|*对等体|。 对于设置跨进程引导消息管道很有用。 这返回的 消息管道句柄可由调用者立即使用。 |*peer| 中返回的值 可以与代理客户端一起传递 邀请。 请参阅下面的 SendBrokerClientInvitation()。
MojoHandle Core::CreatePartialMessagePipe(ports::PortRef* peer) {
RequestContext request_context;
ports::PortRef local_port;
GetNodeController()->node()->CreatePortPair(&local_port, peer);
return AddDispatcher(new MessagePipeDispatcher(
GetNodeController(), local_port, kUnknownPipeIdForDebug, 0));
}
HandleTable 管理MojoHandle 与MessagePipeDispatcher的映射
![](https://i-blog.csdnimg.cn/blog_migrate/ff7e94a504dabf6fb0a057c8f3248f35.png)
路径:mojo/core/handle_table.h mojo/core/ http://handle_table.cc
Handletable主要是用于管理存储MojoHandle和MessagePipeDispatcher(基类是Dispatcher),他们之间的对应关系是通过unordered_map存储的,key是MojoHanlde,value是MessagePipeDispatcher,每添加一个MessagePipeDispatcher就会绑定一个handle。MojoHanlde是通过next_available_handle_++得到的,这样可可以保证每个hanlde都是唯一的。
可以查看HandleTable源码
namespace mojo {
namespace core {
class MOJO_SYSTEM_IMPL_EXPORT HandleTable
: public base::trace_event::MemoryDumpProvider {
public:
MojoHandle AddDispatcher(scoped_refptr<Dispatcher> dispatcher);
scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle);
.......
private:
// A helper class for storing dispatchers that caches the last fetched
// dispatcher. This is an optimization for the common case that the same
// dispatcher is fetched repeatedly. Please see https://crbug.com/1295449 for
// more details.
class EntriesAccessor {
public:
.......
private:
std::unordered_map<MojoHandle, Entry> handles_;
scoped_refptr<Dispatcher> last_read_dispatcher_;
MojoHandle last_read_handle_ = MOJO_HANDLE_INVALID;
};
EntriesAccessor entries_;
base::Lock lock_;
uintptr_t next_available_handle_ = 1;
};
} // namespace core
} // namespace mojo
可以看到handles_就是一个 std::unordered_map<MojoHandle, Entry>,而Entry上是Dispatcher的包装。
接下来我们看下MojoHandle的由来
MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
// Oops, we're out of handles.
if (next_available_handle_ == MOJO_HANDLE_INVALID)
return MOJO_HANDLE_INVALID;
MojoHandle handle = next_available_handle_++;
const bool inserted = entries_.Add(handle, Entry(std::move(dispatcher)));
DCHECK(inserted);
return handle;
}
由上代码可以知道当AddDispatcher一个Dispatcher时候next_available_handle_++这样就得到了一个唯一的handle与dispatcher对应,然后插入到handles_中
MessagePipeDispatcher
![](https://i-blog.csdnimg.cn/blog_migrate/dc91f24b506ab9dc34a8ea7ef518ddbf.png)
mojo/core/message_pipe_dispatcher.h
MessagePipeDispatcher 是与特定port联系的消息分发器
MessagePipeDispatcher类是一个消息处理中间类,它的基类是Dispatcher。
当Proxy端调用则通过调用MessagePipeDispatcher::WriteMessage函数通过NodeContorl发送消息给另一个进程
当在Impl端则通过MessagePipeDispatcher::ReadMessage向Port读取消息队列读取消息,然后传给对应的mojo接口实现处理。
WriteMessage
MojoResult MessagePipeDispatcher::WriteMessage(
std::unique_ptr<ports::UserMessageEvent> message) {
if (port_closed_ || in_transit_)
return MOJO_RESULT_INVALID_ARGUMENT;
//调用 Node::SendUserMessage
int rv = node_controller_->SendUserMessage(port_, std::move(message));
DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_
<< " [port=" << port_.name() << "; rv=" << rv << "]";
if (rv != ports::OK) {
if (rv == ports::ERROR_PORT_UNKNOWN ||
rv == ports::ERROR_PORT_STATE_UNEXPECTED ||
rv == ports::ERROR_PORT_CANNOT_SEND_PEER) {
return MOJO_RESULT_INVALID_ARGUMENT;
} else if (rv == ports::ERROR_PORT_PEER_CLOSED) {
return MOJO_RESULT_FAILED_PRECONDITION;
}
NOTREACHED();
return MOJO_RESULT_UNKNOWN;
}
// We may need to update anyone watching our signals in case we just exceeded
// the unread message count quota.
base::AutoLock lock(signal_lock_);
watchers_.NotifyState(GetHandleSignalsStateNoLock());
return MOJO_RESULT_OK;
}
NodeController
// ports::Node 的所有者,它促进了核心 EDK 的实现。 全部
// 公共接口方法可以安全地从任何线程调用。
// These are safe to access from any thread as long as the Node is alive.
const ports::NodeName name_; //随机化生成
const std::unique_ptr<ports::Node> node_;
// Ports reserved by name, per peer.
using PortMap = std::map<std::string, ports::PortRef>;
std::map<ports::NodeName, PortMap> reserved_ports_;
// Channels to known peers, including inviter and invitees, if any.
using NodeMap = std::unordered_map<ports::NodeName, scoped_refptr<NodeChannel>>;
NodeMap peers_;
// A set of port merge requests awaiting inviter connection.
std::vector<std::pair<std::string, ports::PortRef>> pending_port_merges_;
// The name of the node which invited us to join its network, if any.
ports::NodeName inviter_name_;
// A temporary reference to the inviter channel before we know their name.
scoped_refptr<NodeChannel> bootstrap_inviter_channel_;
// Channels to invitees during handshake.
NodeMap pending_invitations_;
NodeController
有一个 peers_
的map,当收到invitation的时候( OnAcceptInvitation
)建立连接,并将对端存入这个map
void NodeController::OnAcceptInvitation(const ports::NodeName& from_node,
const ports::NodeName& token,
const ports::NodeName& invitee_name) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
auto it = pending_invitations_.find(from_node);
if (it == pending_invitations_.end() || token != from_node) {
DLOG(ERROR) << "Received unexpected AcceptInvitation message from "
<< from_node;
DropPeer(from_node, nullptr);
return;
}
{
base::AutoLock lock(reserved_ports_lock_);
auto reserved_ports_it = reserved_ports_.find(from_node);
if (reserved_ports_it != reserved_ports_.end()) {
// Swap the temporary node name's reserved ports into an entry keyed by
// the real node name.
auto result = reserved_ports_.emplace(
invitee_name, std::move(reserved_ports_it->second));
DCHECK(result.second);
reserved_ports_.erase(reserved_ports_it);
}
}
scoped_refptr<NodeChannel> channel = it->second;
pending_invitations_.erase(it);
DCHECK(channel);
DVLOG(1) << "Node " << name_ << " accepted invitee " << invitee_name;
AddPeer(invitee_name, channel, false /* start_channel */);
// TODO(rockot): We could simplify invitee initialization if we could
// synchronously get a new async broker channel from the broker. For now we do
// it asynchronously since it's only used to facilitate handle passing, not
// handle creation.
scoped_refptr<NodeChannel> broker = GetBrokerChannel();
if (broker) {
// Inform the broker of this new client.
broker->AddBrokerClient(invitee_name, channel->CloneRemoteProcessHandle());
} else {
// If we have no broker, either we need to wait for one, or we *are* the
// broker.
scoped_refptr<NodeChannel> inviter = GetInviterChannel();
if (!inviter) {
base::AutoLock lock(inviter_lock_);
inviter = bootstrap_inviter_channel_;
}
if (!inviter) {
// Yes, we're the broker. We can initialize the client directly.
channel->AcceptBrokerClient(name_, PlatformHandle(),
channel->LocalCapabilities());
} else {
// We aren't the broker, so wait for a broker connection.
base::AutoLock lock(broker_lock_);
pending_broker_clients_.push(invitee_name);
}
}
}
SendBrokerClientInvitation 发送邀请
在MojoResult Core::SendInvitation 的最后会调用该函数
GetNodeController()->SendBrokerClientInvitation(
std::move(target_process), std::move(connection_params), attached_ports,
process_error_callback);
reserved_ports_是一个逆向的查找表,根据NodeName和MessagePipe关联的名字查找到对应的Port。
pending_invitations_存放正在进行握手的Invitation。用生成的token与创建的NodeChannel进行映射。
void NodeController::SendBrokerClientInvitation(
base::Process target_process,
ConnectionParams connection_params,
const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
const ProcessErrorCallback& process_error_callback) {
// Generate the temporary remote node name here so that it can be associated
// with the ports "attached" to this invitation.
ports::NodeName temporary_node_name;
GenerateRandomName(&temporary_node_name);
{
base::AutoLock lock(reserved_ports_lock_);
PortMap& port_map = reserved_ports_[temporary_node_name];
for (auto& entry : attached_ports) {
auto result = port_map.emplace(entry.first, entry.second);
DCHECK(result.second) << "Duplicate attachment: " << entry.first;
}
}
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NodeController::SendBrokerClientInvitationOnIOThread,
base::Unretained(this), std::move(target_process),
std::move(connection_params), temporary_node_name,
process_error_callback));
}
SendBrokerClientInvitationOnIOThread
void NodeController::SendBrokerClientInvitationOnIOThread(
base::Process target_process,
ConnectionParams connection_params,
ports::NodeName temporary_node_name,
const ProcessErrorCallback& process_error_callback) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
......
scoped_refptr<NodeChannel> channel = NodeChannel::Create(
this, std::move(node_connection_params), handle_policy, io_task_runner_,
process_error_callback);
......
// We set up the invitee channel with a temporary name so it can be identified
// as a pending invitee if it writes any messages to the channel. We may start
// receiving messages from it (though we shouldn't) as soon as Start() is
// called below.
// 我们使用临时名称设置受邀者频道,以便可以识别它
// 如果它向频道写入任何消息,则作为待处理的被邀请者。 我们可以开始
// 一旦 Start() 开始就从它那里接收消息(尽管我们不应该这样做)
// 在下面调用。
pending_invitations_.insert(std::make_pair(temporary_node_name, channel));
channel->SetRemoteNodeName(temporary_node_name);
channel->SetRemoteProcessHandle(std::move(target_process));
channel->Start();
//调用NodeChannel AcceptInvitee
channel->AcceptInvitee(name_, temporary_node_name);
}
AcceptBrokerClientInvitation 接收邀请
void NodeController::AcceptBrokerClientInvitation(
ConnectionParams connection_params) {
absl::optional<PlatformHandle> broker_host_handle;
DCHECK(!GetConfiguration().is_broker_process);
#if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_FUCHSIA)
if (!connection_params.is_async()) {
// Use the bootstrap channel for the broker and receive the node's channel
// synchronously as the first message from the broker.
DCHECK(connection_params.endpoint().is_valid());
broker_ = std::make_unique<Broker>(
connection_params.TakeEndpoint().TakePlatformHandle(),
/*wait_for_channel_handle=*/true);
PlatformChannelEndpoint endpoint = broker_->GetInviterEndpoint();
if (!endpoint.is_valid()) {
// Most likely the inviter's side of the channel has already been closed
// and the broker was unable to negotiate a NodeChannel pipe. In this case
// we can cancel our connection to our inviter.
DVLOG(1) << "Cannot connect to invalid inviter channel.";
CancelPendingPortMerges();
return;
}
const bool leak_endpoint = connection_params.leak_endpoint();
connection_params = ConnectionParams(std::move(endpoint));
connection_params.set_leak_endpoint(leak_endpoint);
} else {
// For async connections, we instead create a new channel for the broker and
// send a request for the inviting process to bind to it. This avoids doing
// blocking I/O to accept the invitation. Does not work in some sandboxed
// environments, where the PlatformChannel constructor will CHECK fail.
PlatformChannel channel;
broker_ = std::make_unique<Broker>(
channel.TakeLocalEndpoint().TakePlatformHandle(),
/*wait_for_channel_handle=*/false);
broker_host_handle = channel.TakeRemoteEndpoint().TakePlatformHandle();
}
#endif
// Re-enable port merge operations, which may have been disabled if this isn't
// the first invitation accepted by this process.
base::AutoLock lock(pending_port_merges_lock_);
reject_pending_merges_ = false;
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NodeController::AcceptBrokerClientInvitationOnIOThread,
base::Unretained(this), std::move(connection_params),
std::move(broker_host_handle)));
}
void NodeController::AcceptBrokerClientInvitationOnIOThread(
ConnectionParams connection_params,
absl::optional<PlatformHandle> broker_host_handle) {
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
{
base::AutoLock lock(inviter_lock_);
if (inviter_name_ != ports::kInvalidNodeName) {
// We've already accepted an invitation before and are already part of
// a different Mojo process network. In order to accept this new one and
// remain in a consistent state, we have to purge all peer connections and
// start from scratch.
{
base::AutoUnlock unlock(inviter_lock_);
DropAllPeers();
}
inviter_name_ = ports::kInvalidNodeName;
}
const bool leak_endpoint = connection_params.leak_endpoint();
// At this point we don't know the inviter's name, so we can't yet insert it
// into our |peers_| map. That will happen as soon as we receive an
// AcceptInvitee message from them.
bootstrap_inviter_channel_ =
NodeChannel::Create(this, std::move(connection_params),
Channel::HandlePolicy::kAcceptHandles,
io_task_runner_, ProcessErrorCallback());
if (leak_endpoint) {
// Prevent the inviter pipe handle from being closed on shutdown. Pipe
// closure may be used by the inviter to detect that the invited process
// has terminated. In such cases, the invited process must not be invited
// more than once in its lifetime; otherwise this leak matters.
//
// Note that this behavior is supported primarily to help adapt legacy
// Chrome IPC to Mojo, since channel disconnection is used there as a
// signal for normal child process termination.
bootstrap_inviter_channel_->LeakHandleOnShutdown();
}
}
bootstrap_inviter_channel_->Start();
if (broker_host_handle)
bootstrap_inviter_channel_->BindBrokerHost(std::move(*broker_host_handle));
}
Node
mojo\core\ports\node.h
一个节点维护一个由唯一 128 位索引的端口集合(参见 port.h)地址(名称),执行事件的路由和处理 节点内的端口以及系统中其他节点的端口。 通 常每个进程都有一个节点。 因此,节点边界有效地模拟了进程边界。 从GetNodeController()->node()->CreatePortPair(&port0, &port1); 也可以看出来 Controller 是 单例 ,其中仅仅持有一个Node
可以使用 CreateUninitializedPort( 稍后使用 InitializePort 进行初始化),或者在完全初始化的情况下创建使用 CreatePortPair() 状态。 初始化的端口只有一个共轭端口,它是该端口发送的任何用户消息的最终接收者。参见 SendUserMessage()。
除了路由用户消息事件外,还使用了各种控制事件通过节点来协调节点内和节点之间的端口行为和生命周期。查看事件文档以了解不同类型的事件的描述一个节点来协调行为。
// A Node maintains a collection of Ports (see port.h) indexed by unique 128-bit
// addresses (names), performing routing and processing of events among the
// Ports within the Node and to or from other Nodes in the system. Typically
// (and practically, in all uses today) there is a single Node per system
// process. Thus a Node boundary effectively models a process boundary.
//
// New Ports can be created uninitialized using CreateUninitializedPort (and
// later initialized using InitializePort), or created in a fully initialized
// state using CreatePortPair(). Initialized ports have exactly one conjugate
// port which is the ultimate receiver of any user messages sent by that port.
// See SendUserMessage().
//
// In addition to routing user message events, various control events are used
// by Nodes to coordinate Port behavior and lifetime within and across Nodes.
// See Event documentation for description of different types of events used by
// a Node to coordinate behavior.
std::unordered_map<LocalPortName, scoped_refptr<Port>> ports_; //端口名称与实际端口的映射
创建一个port
int Node::CreateUninitializedPort(PortRef* port_ref) {
//生成port_name
PortName port_name;
GenerateRandomPortName(&port_name);
//创建Port
scoped_refptr<Port> port(new Port(kInitialSequenceNum, kInitialSequenceNum));
//添加到ports_ map中映射<port_name, port>
int rv = AddPortWithName(port_name, port);
if (rv != OK)
return rv;
//获取引用
*port_ref = PortRef(port_name, std::move(port));
return OK;
}
其中 PortName 是一对 uint64
struct COMPONENT_EXPORT(MOJO_CORE_PORTS) PortName : Name {
PortName() : Name(0, 0) {}
PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};
CreatePortPair
生成绑定到该节点的新连接端口对。 这些端口已初始化并准备就绪。
int Node::CreatePortPair(PortRef* port0_ref, PortRef* port1_ref) {
int rv;
// 分配一个端口port 以及独一无二的port_name ,
rv = CreateUninitializedPort(port0_ref);
if (rv != OK)
return rv;
rv = CreateUninitializedPort(port1_ref);
if (rv != OK)
return rv;
/*
int Node::InitializePort(const PortRef& port_ref,
const NodeName& peer_node_name,
const PortName& peer_port_name,
const NodeName& prev_node_name,
const PortName& prev_port_name)
*/
//让port机端记住node 名称和 对方port的name
rv = InitializePort(*port0_ref, name_, port1_ref->name(), name_,
port1_ref->name());
if (rv != OK)
return rv;
rv = InitializePort(*port1_ref, name_, port0_ref->name(), name_,
port0_ref->name());
if (rv != OK)
return rv;
return OK;
};
InitializePort的作用,将node_name 和对方port写入到己方的port的成员变量中
InitializePort
将该port初始化对端的name,和node的name,同时加入PortStatusChanged
int Node::InitializePort(const PortRef& port_ref,
const NodeName& peer_node_name,
const PortName& peer_port_name,
const NodeName& prev_node_name,
const PortName& prev_port_name) {
{
// Must be acquired for UpdatePortPeerAddress below.
PortLocker::AssertNoPortsLockedOnCurrentThread();
base::AutoLock ports_locker(ports_lock_);
SinglePortLocker locker(&port_ref);
auto* port = locker.port();
if (port->state != Port::kUninitialized)
return ERROR_PORT_STATE_UNEXPECTED;
port->state = Port::kReceiving;
UpdatePortPeerAddress(port_ref.name(), port, peer_node_name,
peer_port_name);
port->prev_node_name = prev_node_name;
port->prev_port_name = prev_port_name;
}
delegate_->PortStatusChanged(port_ref);
return OK;
}
其中 delegate_是 NodeController,调用void NodeController::PortStatusChanged(const ports::PortRef& port),将
SendUserMessage
// Sends a message from the specified port to its peer. Note that the message
// notification may arrive synchronously (via PortStatusChanged() on the
// delegate) if the peer is local to this Node.
// 从指定端口向其对等端发送消息。 请注意,该消息
// 通知可能同步到达(通过 PortStatusChanged()
// 委托)如果对等点是该节点的本地节点。
int SendUserMessage(const PortRef& port_ref,
std::unique_ptr<UserMessageEvent> message);
int Node::SendUserMessage(const PortRef& port_ref,
std::unique_ptr<UserMessageEvent> message) {
int rv = SendUserMessageInternal(port_ref, &message);
if (rv != OK) {
// If send failed, close all carried ports. Note that we're careful not to
// close the sending port itself if it happened to be one of the encoded
// ports (an invalid but possible condition.)
for (size_t i = 0; i < message->num_ports(); ++i) {
if (message->ports()[i] == port_ref.name())
continue;
PortRef port;
if (GetPort(message->ports()[i], &port) == OK)
ClosePort(port);
}
}
return rv;
}
int Node::PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
Port::State expected_port_state,
bool ignore_closed_peer,
UserMessageEvent* message,
NodeName* forward_to_node) {
bool target_is_remote = false;
base::queue<PendingUpdatePreviousPeer> peer_update_events;
for (;;) {
NodeName target_node_name;
{
SinglePortLocker locker(&forwarding_port_ref);
target_node_name = locker.port()->peer_node_name;
}
// NOTE: This may call out to arbitrary user code, so it's important to call
// it only while no port locks are held on the calling thread.
// 首先判断note_name 是否一致 是否是用一个channal
if (target_node_name != name_) {
if (!message->NotifyWillBeRoutedExternally()) {
LOG(ERROR) << "NotifyWillBeRoutedExternally failed unexpectedly.";
return ERROR_PORT_STATE_UNEXPECTED;
}
}
......
#if DCHECK_IS_ON()
DVLOG(4) << "Sending message " << message->sequence_num()
<< " [ports=" << ports_buf.str() << "]"
<< " from " << forwarding_port_ref.name() << "@" << name_ << " to "
<< forwarding_port->peer_port_name << "@" << target_node_name;
#endif
*forward_to_node = target_node_name;
message->set_port_name(forwarding_port->peer_port_name);//对方端口
message->set_from_port(forwarding_port_ref.name());//自己的端口
message->set_control_sequence_num(
forwarding_port->next_control_sequence_num_to_send++);
break;
}
......
return OK;
}
int Node::PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
Port::State expected_port_state,
bool ignore_closed_peer,
UserMessageEvent* message,
NodeName* forward_to_node) {
bool target_is_remote = false;
base::queue<PendingUpdatePreviousPeer> peer_update_events;
for (;;) {
NodeName target_node_name;
{
SinglePortLocker locker(&forwarding_port_ref);
target_node_name = locker.port()->peer_node_name;
}
// NOTE: This may call out to arbitrary user code, so it's important to call
// it only while no port locks are held on the calling thread.
// 首先判断note_name 是否一致 是否是用一个channal
if (target_node_name != name_) {
if (!message->NotifyWillBeRoutedExternally()) {
LOG(ERROR) << "NotifyWillBeRoutedExternally failed unexpectedly.";
return ERROR_PORT_STATE_UNEXPECTED;
}
}
......
#if DCHECK_IS_ON()
DVLOG(4) << "Sending message " << message->sequence_num()
<< " [ports=" << ports_buf.str() << "]"
<< " from " << forwarding_port_ref.name() << "@" << name_ << " to "
<< forwarding_port->peer_port_name << "@" << target_node_name;
#endif
*forward_to_node = target_node_name;
message->set_port_name(forwarding_port->peer_port_name);//对方端口
message->set_from_port(forwarding_port_ref.name());//自己的端口
message->set_control_sequence_num(
forwarding_port->next_control_sequence_num_to_send++);
break;
}
......
return OK;
}
![](https://i-blog.csdnimg.cn/blog_migrate/61a4e595ecf849a48468e563bed5992b.png)
Port
mojo/core/ports/port.h
// A Port is essentially a node in a circular list of addresses. For the sake of
// this documentation such a list will henceforth be referred to as a "route."
// Routes are the fundamental medium upon which all Node event circulation takes
// place and are thus the backbone of all Mojo message passing.
//
// Each Port is identified by a 128-bit address within a Node (see node.h). A
// Port doesn't really *do* anything per se: it's a named collection of state,
// and its owning Node manages all event production, transmission, routing, and
// processing logic. See Node for more details on how Ports may be used to
// transmit arbitrary user messages as well as other Ports.
//
// Ports may be in any of a handful of states (see State below) which dictate
// how they react to system events targeting them. In the simplest and most
// common case, Ports are initially created as an entangled pair (i.e. a simple
// cycle consisting of two Ports) both in the |kReceiving| State. Consider Ports
// we'll label |A| and |B| here, which may be created using
// Node::CreatePortPair():
//
// +-----+ +-----+
// | |--------->| |
// | A | | B |
// | |<---------| |
// +-----+ +-----+
//
// |A| references |B| via |peer_node_name| and |peer_port_name|, while |B| in
// turn references |A|. Note that a Node is NEVER aware of who is sending events
// to a given Port; it is only aware of where it must route events FROM a given
// Port.
//
// For the sake of documentation, we refer to one receiving port in a route as
// the "conjugate" of the other. A receiving port's conjugate is also its peer
// upon initial creation, but because of proxying this may not be the case at a
// later time.
//
// ALL access to this data structure must be guarded by |lock_| acquisition,
// which is only possible using a PortLocker. PortLocker ensures that
// overlapping Port lock acquisitions on a single thread are always acquired in
// a globally consistent order.
端口本质上是地址循环列表中的一个节点。为了本文档中这样的列表将被称为"路线"。
路由是所有 Node 事件循环所采用的基本媒介放置,因此是所有 Mojo 消息传递的主干。
每个端口由节点内的 128 位地址标识(参见 node.h)。一个端口本身并没有真正*做*任何事情:它是一个命名的状态集合,及其所属节点管理所有事件生产、传输、路由和处理逻辑。有关如何使用端口的更多详细信息,请参阅节点传输任意用户消息以及其他端口。
端口可能处于少数几种状态中的任何一种(参见下面的状态)他们如何对针对他们的系统事件做出反应。在最简单和最 常见情况,端口最初创建为一个纠缠对(即一个简单的由两个端口组成的循环)都在|kReceiving|中状态。考虑端口 我们将标记 |A|和 |B|这里,可以使用创建节点::CreatePortPair():
// // +-----+ +-----+ // | |--------->| | // | A | | B | // | |<---------| | // +-----+ +-----+
|A | 与 |B|通过 |peer_node_name|和 |peer_port_name| 通信。请注意,节点永远不会知道谁在发送事件到给定的端口;它只知道它必须将事件从给定路由到哪里端口。
为了便于说明,我们将路由中的一个接收端口称为另一个的"共轭"。接收端口的共轭也是它的对等点在初始创建时,但由于代理,这可能不是晚点。
对这个数据结构的所有访问都必须由 |lock_| 保护获得,这只能使用 PortLocker。 PortLocker 确保单个线程上的重叠端口锁获取总是在全局一致的顺序。
// 事件应从该端口路由到的节点和端口地址。注意这不一定是当前发送的端口地址事件到这个端口。
// The Node and Port address to which events should be routed FROM this Port.
// Note that this is NOT necessarily the address of the Port currently sending
// events TO this Port.
NodeName peer_node_name;
PortName peer_port_name;
// 我们跟踪当前正在向该端口发送消息的端口。这允许我们验证发送者节点是否被允许发送消息
//将此端口作为缓解信息泄漏漏洞的措施。
// 跟踪前一个端口有一个很好的副作用是保持接收
// 按顺序发送消息。
// We keep track of the port that is currently sending messages to this port.
// This allows us to verify that the sender node is allowed to send messages
// to this port as a mitigation against info leak vulnerabilities.
// Tracking the previous port has the nice side effect of keeping received
// messages in order.
NodeName prev_node_name;
PortName prev_port_name;
邀请机制
OutgoingInvitation
OutgoingInvitation 用于邀请另一个进程加入调用进程的IPC网络。 典型的使用涉及构建一个 |PlatformChannel| 并使用一端发送邀请(见下文|Send()|),同时将另一个传递给子进程。这也可以与 |NamedPlatformChannel| 的服务器端点一起使用。
典型的邀请机制使用方式
邀请方:
mojo::OutgoingInvitation invitation;
mojo::PlatformChannel channel;
mojo::ScopedMessagePipeHandle server_pipe;
// 1 创建一个新的消息管道,将一端附加到这个邀请和
// 将另一端返回给调用者。 被邀请人可以提取
// 附加端点(参见 |IncomingInvitation|)从而建立端到端
// mojo通讯。
server_pipe = invitation.AttachMessagePipe(kBootstrapMojoConnectionChannelToken);
if (!server_pipe.is_valid()) {
LOG(ERROR) << "FaceAuthManager could not bind to invitation";
std::move(callback).Run(false);
return;
}
// 2 发送邀请:invitation 携带了附加端点,mojo::PlatformChannel 在linux下使用socketpair,
mojo::OutgoingInvitation::Send(std::move(invitation),
base::kNullProcessHandle,
channel.TakeLocalEndpoint());
//3 调用端绑定绑定在消息通道server_pipe
logger_proxy_.Bind(
mojo::PendingRemote<sample::mojom::Logger>(std::move(server_pipe), 0u));
LOG(INFO) << "Bound remote interface to pipe.";
logger_proxy_.set_disconnect_handler(
base::BindOnce(&FaceAuthManager::OnDisconnect,
base::Unretained(this)));
//4 通过PlatformChannel channel;将 channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD()
//发送到对端引导mojo通信;
//这仅仅是传递PlatformChannel 一种方式:在chromium 中,是在创建子进程时,通过子进程的命令行传递;
//因为chromium中的进程时父子关系,在chromiumos 中,多个服务之间的mojo通信目前有两种方式:
//其中一种便是首先通过dbus引导
face_auth::ThreadManager::Get()->GetFaceAuthClient()->BootstrapMojoConnection(
channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD(),
base::BindOnce(&FaceAuthManager::OnBootstrapMojoConnection,
base::Unretained(this), std::move(callback)));
接收方:
1.dbus 接口提取引导通信的fd
void DbusService::BootstrapMojoConnection(
MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
LOG(INFO) << __func__;
MessageReader reader(method_call);
base::ScopedFD file_handle;
if (!reader.PopFileDescriptor(&file_handle)) {
LOG(ERROR) << "PopFileDescriptor error";
std::move(response_sender).Run(std::unique_ptr<Response>());
return;
}
.......
2
mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(fd))));
3 从IncomingInvitation中抽取token:kBootstrapMojoConnectionChannelToken 对应的另外一个
MessagePipeHandle
mojo::ScopedMessagePipeHandle mojo_pipe_handle =
invitation.ExtractMessagePipe(kBootstrapMojoConnectionChannelToken);
if (!mojo_pipe_handle.is_valid()) {
LOG(ERROR) << "mojo_pipe_handle not is_valid";
callback_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), /*success=*/false));
return;
}
4 将mojo_pipe_handle绑定在PendingReceiver从而完成一条通信渠道的建立
if (!service_) {
service_ = std::make_unique<FaceAuthServiceImpl>(
mojo::PendingReceiver<sample::mojom::Logger>(std::move(mojo_pipe_handle)),
base::BindOnce(&FaceAuthService::OnDisconnect, base::Unretained(this)));
} else {
service_->Clone(mojo::PendingReceiver<sample::mojom::Logger>(
std::move(mojo_pipe_handle)));
}
流程
消息类型:
// NOTE: Please ONLY append messages to the end of this enum.
enum class MessageType : uint32_t {
ACCEPT_INVITEE,//
ACCEPT_INVITATION,
ADD_BROKER_CLIENT,
BROKER_CLIENT_ADDED,
ACCEPT_BROKER_CLIENT,
EVENT_MESSAGE,
REQUEST_PORT_MERGE,
REQUEST_INTRODUCTION,
INTRODUCE,
#if BUILDFLAG(IS_WIN)
RELAY_EVENT_MESSAGE,
#endif
BROADCAST_EVENT,
#if BUILDFLAG(IS_WIN)
EVENT_MESSAGE_FROM_RELAY,
#endif
ACCEPT_PEER,
BIND_BROKER_HOST,
};
Invitation机制类似于一个简化版的TCP握手协议。
![](https://i-blog.csdnimg.cn/blog_migrate/9868292e761603b36312c305039aa976.png)
先前面代码示例部分中不难发现,创建子进程在先,而后父进程才会发送Invitation。因此在上图中,新进程会先调用 AcceptInvitation以接受邀请。
在这一步骤中,子进程将会拿到父进程传来的文件描述符,以此作为与父组件的通信信道,创建出了一个 NodeChannel,变量名是bootstrap_inviter_channel_。此时子进程并不清楚邀请它的人是谁,因此inviter_name和NodeChannel中remote_node_name都为kInvalidNodeName,即无效名称。
![](https://i-blog.csdnimg.cn/blog_migrate/ed411f104c5e0c9c6f514da05a7e634d.png)
之后父进程执行 SendInvitation 发送邀请,这一步也会创建一个 NodeChannel,用以与子进程的NodeChannel通信;并且还会生成一个token,代表的是父进程与子进程通信的凭证。并且这里还涉及到一张映射表reserved_ports_和一张映射表pending_invitations_。
reserved_ports_是一个逆向的查找表,根据NodeName和MessagePipe关联的名字查找到对应的Port。
pending_invitations_存放正在进行握手的Invitation。用生成的token与创建的NodeChannel进行映射。
此时,父进程中
// These are safe to access from any thread as long as the Node is alive.
const ports::NodeName name_; //随机化生成
const std::unique_ptr<ports::Node> node_;
// Ports reserved by name, per peer.
using PortMap = std::map<std::string, ports::PortRef>;
std::map<ports::NodeName, PortMap> reserved_ports_;
// Channels to known peers, including inviter and invitees, if any.
using NodeMap = std::unordered_map<ports::NodeName, scoped_refptr<NodeChannel>>;
NodeMap peers_;
// A set of port merge requests awaiting inviter connection.
std::vector<std::pair<std::string, ports::PortRef>> pending_port_merges_;
// The name of the node which invited us to join its network, if any.
ports::NodeName inviter_name_;
// A temporary reference to the inviter channel before we know their name.
scoped_refptr<NodeChannel> bootstrap_inviter_channel_;
// Channels to invitees during handshake.
NodeMap pending_invitations_;
SendInvitation之后会调用AcceptInvitee。
调用AcceptInvitee,就代表握手协议开始进行了。
它会上一步利用创建好的NodeChannel发送数据。数据内容主要包括inviter_name和token(次要内容不包括进来)。inviter_name也就是父进程的NodeName。
子进程的NodeChannel接收到数据,解析发现是一个 AcceptInvitee 类型的数据,调用OnAcceptInvitee回调。在这一步中,子进程得知了邀请者,也就是父进程的NodeName, 因此将inviter_name_和NodeChannel中的remote_node_name设置为传来的inviter_name。
OnAcceptInvitee之后会调用 AcceptInvitation。
它也会利用之前创建好的NodeChannel向父进程发送数据。数据内容主要包括 invitee_name 和 token (次要内容不包括进来)。invitee_name也就是子进程的 NodeName。
父进程的NodeChannel接收到AcceptInvitation类型的数据,调用OnAcceptInvitation回调。
从传来的数据中可以知道子进程的NodeName。
这一步中,主要会对三张映射表进行处理。
reserved_ports_:之前使用的是token作为NodeName作为键进行映射,现在子进程NodeName知道了,需要更换。
pending_invitations:握手协议已经完成了,将当前Invitation从这张表中移除。
peers_:这张表是已经完成握手,建立连接关系的映射表。通过NodeName映射到NodeChannel。( using NodeMap = std::unordered_map<ports::NodeName, scoped_refptr<NodeChannel>>;)
这一步完成后,就代表着Invitation正式完成了。
既然底层使用到了Channel,几个协议也只有MessagePipe用到了Channel,不难想到这个机制是为了方便MessagePipe而设立的。
一个MessagePipe中有一对handle,分别是handle0和handle1,向其中一个handle写的数据可以从另外一个handle读出来,如果把其中的一个handle发送到另外一个进程,这一对handle之间依然能够相互收发数据。
Constructor
本质是构造MojoHandle invitation_handle;并且绑定InvitationDispatcher,使得handle_ 与 dispatcher映射
typedef uint64_t MojoHandle;
OutgoingInvitation::OutgoingInvitation() {
MojoHandle invitation_handle;
MojoResult result = MojoCreateInvitation(nullptr, &invitation_handle);
DCHECK_EQ(result, MOJO_RESULT_OK);
handle_.reset(InvitationHandle(invitation_handle));
}
---------------------
MojoResult Core::CreateInvitation(const MojoCreateInvitationOptions* options,
MojoHandle* invitation_handle) {
if (options && options->struct_size < sizeof(*options))
return MOJO_RESULT_INVALID_ARGUMENT;
if (!invitation_handle)
return MOJO_RESULT_INVALID_ARGUMENT;
*invitation_handle = AddDispatcher(new InvitationDispatcher);//
if (*invitation_handle == MOJO_HANDLE_INVALID)
return MOJO_RESULT_RESOURCE_EXHAUSTED;
return MOJO_RESULT_OK;
}
//参看 Handletable ,map 映射 handle 与dispatcher
MojoHandle Core::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
base::AutoLock lock(handles_->GetLock());
return handles_->AddDispatcher(dispatcher);
}
AttachMessagePipe
// Creates a new message pipe, attaching one end to this invitation and
// returning the other end to the caller. The invitee can extract the
// attached endpoint (see |IncomingInvitation|) thus establishing end-to-end
// Mojo communication.
//
// |name| is an arbitrary value that must be used by the invitee to extract
// the corresponding attached endpoint.
// 创建一个新的消息管道,将一端附加到这个邀请和
// 将另一端返回给调用者。 被邀请人可以提取
// 附加端点(参见 |IncomingInvitation|)从而建立端到端
// mojo通讯。
//
// |名称| 是一个任意值,被邀请者必须使用它来提取
// 相应的附加端点。
ScopedMessagePipeHandle OutgoingInvitation::AttachMessagePipe(
base::StringPiece name) {
DCHECK(!name.empty());
DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
MojoHandle message_pipe_handle;
//MojoResult Core::AttachMessagePipeToInvitation
MojoResult result = MojoAttachMessagePipeToInvitation(
handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
nullptr, &message_pipe_handle);
DCHECK_EQ(MOJO_RESULT_OK, result);
return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
}
--------------------------MojoAttachMessagePipeToInvitation 即
MojoResult Core::AttachMessagePipeToInvitation(
MojoHandle invitation_handle,
const void* name,
uint32_t name_num_bytes,
const MojoAttachMessagePipeToInvitationOptions* options,
MojoHandle* message_pipe_handle) {
if (options && options->struct_size < sizeof(*options))
return MOJO_RESULT_INVALID_ARGUMENT;
if (!message_pipe_handle)
return MOJO_RESULT_INVALID_ARGUMENT;
if (name_num_bytes == 0)
return MOJO_RESULT_INVALID_ARGUMENT;
//获取OutgoingInvitation 对应的 dispathcer
scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
return MOJO_RESULT_INVALID_ARGUMENT;
auto* invitation_dispatcher =
static_cast<InvitationDispatcher*>(dispatcher.get());
RequestContext request_context;
//创建 一对 port(己方和对方),并且local_port绑定到MessagePipeDispatcher
//这是未来邀请后,对端(remote_peer_port)与 local_port(映射到MessagePipeDispatcher)的通信渠道
/*
MojoHandle Core::CreatePartialMessagePipe(ports::PortRef* peer) {
RequestContext request_context;
ports::PortRef local_port;
GetNodeController()->node()->CreatePortPair(&local_port, peer);
return AddDispatcher(new MessagePipeDispatcher(
GetNodeController(), local_port, kUnknownPipeIdForDebug, 0));
}
*/
ports::PortRef remote_peer_port;
//将一端附加到这个邀请(remote_peer_port)和将另一端返回给调用者(local_handle):
MojoHandle local_handle = CreatePartialMessagePipe(&remote_peer_port);
if (local_handle == MOJO_HANDLE_INVALID)
return MOJO_RESULT_RESOURCE_EXHAUSTED;
//
MojoResult result = invitation_dispatcher->AttachMessagePipe(
base::StringPiece(static_cast<const char*>(name), name_num_bytes),
std::move(remote_peer_port));
if (result != MOJO_RESULT_OK) {
Close(local_handle);
return result;
}
*message_pipe_handle = local_handle;
return MOJO_RESULT_OK;
}
![](https://i-blog.csdnimg.cn/blog_migrate/8b11c25846917c3c8cf4eda6b52bc338.png)
OutgoingInvitation::Send
// Sends |invitation| to another process via |channel_endpoint|, which should
// correspond to the local endpoint taken from a |PlatformChannel|.
//
// |process_handle| is a handle to the destination process if known. If not
// provided, IPC may be limited on some platforms (namely Mac and Windows) due
// to an inability to transfer system handles across the boundary.
// static
void OutgoingInvitation::Send(OutgoingInvitation invitation,
base::ProcessHandle target_process,
PlatformChannelEndpoint channel_endpoint,
const ProcessErrorCallback& error_callback) {
SendInvitation(std::move(invitation.handle_), target_process,
channel_endpoint.TakePlatformHandle(),
MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
invitation.extra_flags_, error_callback, "");
}
MojoResult Core::SendInvitation
MojoResult Core::SendInvitation(
MojoHandle invitation_handle,
const MojoPlatformProcessHandle* process_handle,
const MojoInvitationTransportEndpoint* transport_endpoint,
MojoProcessErrorHandler error_handler,
uintptr_t error_handler_context,
const MojoSendInvitationOptions* options) {
if (options && options->struct_size < sizeof(*options))
return MOJO_RESULT_INVALID_ARGUMENT;
//1 目标进程句柄
base::Process target_process;
if (process_handle) {
MojoResult result =
UnwrapAndClonePlatformProcessHandle(process_handle, target_process);
if (result != MOJO_RESULT_OK)
return result;
}
//2 异常回调
ProcessErrorCallback process_error_callback;
if (error_handler) {
process_error_callback =
base::BindRepeating(&RunMojoProcessErrorHandler,
base::Owned(new ProcessDisconnectHandler(
error_handler, error_handler_context)),
error_handler, error_handler_context);
} else if (default_process_error_callback_) {
process_error_callback = default_process_error_callback_;
}
......
// 3 找到invitation 对应的dispatcher
scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
return MOJO_RESULT_INVALID_ARGUMENT;
auto* invitation_dispatcher =
static_cast<InvitationDispatcher*>(dispatcher.get());
auto endpoint = PlatformHandle::FromMojoPlatformHandle(
&transport_endpoint->platform_handles[0]);
if (!endpoint.is_valid())
return MOJO_RESULT_INVALID_ARGUMENT;
//4 ConnectionParams 中存储了endpoint
ConnectionParams connection_params;
if (transport_endpoint->type ==
MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
connection_params =
ConnectionParams(PlatformChannelServerEndpoint(std::move(endpoint)));
}
// At this point everything else has been validated, so we can take ownership
// of the dispatcher.
{
base::AutoLock lock(handles_->GetLock());
scoped_refptr<Dispatcher> removed_dispatcher;
MojoResult result = handles_->GetAndRemoveDispatcher(invitation_handle,
&removed_dispatcher);
}
//5 获取到 invitation_dispatcher 关联的 ports :port 建立的地方
//:invitation_dispatcher->AttachMessagePipe
std::vector<std::pair<std::string, ports::PortRef>> attached_ports;
InvitationDispatcher::PortMapping attached_port_map =
invitation_dispatcher->TakeAttachedPorts();
invitation_dispatcher->Close();
for (auto& entry : attached_port_map)
attached_ports.emplace_back(entry.first, std::move(entry.second));
connection_params.set_is_untrusted_process(
options &&
(options->flags & MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS));
.......
//6 SendBrokerClientInvitation
GetNodeController()->SendBrokerClientInvitation(
std::move(target_process), std::move(connection_params), attached_ports,
process_error_callback);
}
return MOJO_RESULT_OK;
}
connection_params 如下:
其中268是本地端点local: 0000000000000268 remote: 000000000000026C
void NodeController::SendBrokerClientInvitation(
base::Process target_process,
ConnectionParams connection_params,
const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
const ProcessErrorCallback& process_error_callback) {
// Generate the temporary remote node name here so that it can be associated
// with the ports "attached" to this invitation.
ports::NodeName temporary_node_name;
GenerateRandomName(&temporary_node_name);
{
base::AutoLock lock(reserved_ports_lock_);
PortMap& port_map = reserved_ports_[temporary_node_name];
for (auto& entry : attached_ports) {
auto result = port_map.emplace(entry.first, entry.second);
DCHECK(result.second) << "Duplicate attachment: " << entry.first;
}
}
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NodeController::SendBrokerClientInvitationOnIOThread,
base::Unretained(this), std::move(target_process),
std::move(connection_params), temporary_node_name,
process_error_callback));
}
void NodeController::SendBrokerClientInvitationOnIOThread(
base::Process target_process,
ConnectionParams connection_params,
ports::NodeName temporary_node_name,
const ProcessErrorCallback& process_error_callback) {
......
scoped_refptr<NodeChannel> channel = NodeChannel::Create(
this, std::move(connection_params), Channel::HandlePolicy::kAcceptHandles,
io_task_runner_, process_error_callback);
......
//由于不知道对方名字是啥,因此用temporary_node_name
pending_invitations_.insert(std::make_pair(temporary_node_name, channel));
channel->SetRemoteNodeName(temporary_node_name);
channel->SetRemoteProcessHandle(std::move(target_process));
channel->Start();
//发起邀请:见NodeChannel::AcceptInvitee
channel->AcceptInvitee(name_, temporary_node_name);
}
IncomingInvitation
IncomingInvitation 可以通过调用被邀请的进程接受 |IncomingInvitation::Accept()。 接受后,即可使用邀请按名称提取附加的消息管道。
// An IncomingInvitation can be accepted by an invited process by calling
// |IncomingInvitation::Accept()|. Once accepted, the invitation can be used
// to extract attached message pipes by name.
class MOJO_CPP_SYSTEM_EXPORT IncomingInvitation {
public:
// Accepts an incoming invitation from |channel_endpoint|. If the invitation
// was sent using one end of a |PlatformChannel|, |channel_endpoint| should be
// the other end of that channel. If the invitation was sent using a
// |PlatformChannelServerEndpoint|, then |channel_endpoint| should be created
// by |NamedPlatformChannel::ConnectToServer|.
//
// Note that this performs blocking I/O on the calling thread.
// 接受来自 |channel_endpoint| 的传入邀请。 如果邀请
// 使用 |PlatformChannel|, |channel_endpoint| 的一端发送 应该
// 该通道的另一端。 如果邀请是使用
// |PlatformChannelServerEndpoint|,然后是 |channel_endpoint| 应该创建
// 通过 |NamedPlatformChannel::ConnectToServer|。
//
// 请注意,这会在调用线程上执行阻塞 I/O。
static IncomingInvitation Accept(
PlatformChannelEndpoint channel_endpoint,
MojoAcceptInvitationFlags flags = MOJO_ACCEPT_INVITATION_FLAG_NONE);
// Extracts an attached message pipe from this invitation. This may succeed
// even if no such pipe was attached, though the extracted pipe will
// eventually observe peer closure.
// 从此邀请中提取附加的消息管道。 这可能会成功
// 即使没有附加这样的管道,尽管提取的管道将
// 最终观察对等关闭。
ScopedMessagePipeHandle ExtractMessagePipe(base::StringPiece name);
// Same as above but allows use of an integer name for convenience.
ScopedMessagePipeHandle ExtractMessagePipe(uint64_t name);
private:
ScopedInvitationHandle handle_;
};
ace-line
// static
IncomingInvitation IncomingInvitation::Accept(
PlatformChannelEndpoint channel_endpoint,
MojoAcceptInvitationFlags flags) {
MojoPlatformHandle endpoint_handle;
PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
&endpoint_handle);
CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
MojoInvitationTransportEndpoint transport_endpoint;
transport_endpoint.struct_size = sizeof(transport_endpoint);
transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
transport_endpoint.num_platform_handles = 1;
transport_endpoint.platform_handles = &endpoint_handle;
MojoAcceptInvitationOptions options;
options.struct_size = sizeof(options);
options.flags = flags;
MojoHandle invitation_handle;
// 1 调用MojoResult Core::AcceptInvitation(
MojoResult result =
MojoAcceptInvitation(&transport_endpoint, &options, &invitation_handle);
if (result != MOJO_RESULT_OK)
return IncomingInvitation();
return IncomingInvitation(
ScopedInvitationHandle(InvitationHandle(invitation_handle)));
}
MojoResult Core::AcceptInvitation(
const MojoInvitationTransportEndpoint* transport_endpoint,
const MojoAcceptInvitationOptions* options,
MojoHandle* invitation_handle) {
......
// 1 抽取endpoint
auto endpoint = PlatformHandle::FromMojoPlatformHandle(
&transport_endpoint->platform_handles[0]);
// 2 创建params
ConnectionParams connection_params;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
if (transport_endpoint->type ==
MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
connection_params =
ConnectionParams(PlatformChannelServerEndpoint(std::move(endpoint)));
}
#endif
if (!connection_params.server_endpoint().is_valid()) {
connection_params =
ConnectionParams(PlatformChannelEndpoint(std::move(endpoint)));
}
if (options &&
options->flags & MOJO_ACCEPT_INVITATION_FLAG_LEAK_TRANSPORT_ENDPOINT) {
connection_params.set_leak_endpoint(true);
}
// 3 接收连接
node_controller->AcceptBrokerClientInvitation(std::move(connection_params));
return MOJO_RESULT_OK;
}
InvitationDispatcher
邀请机制的消息分发类
class MOJO_SYSTEM_IMPL_EXPORT InvitationDispatcher : public Dispatcher {
public:
InvitationDispatcher();
InvitationDispatcher(const InvitationDispatcher&) = delete;
InvitationDispatcher& operator=(const InvitationDispatcher&) = delete;
// Dispatcher:
Type GetType() const override;
MojoResult Close() override;
MojoResult AttachMessagePipe(base::StringPiece name,
ports::PortRef remote_peer_port) override;
MojoResult ExtractMessagePipe(base::StringPiece name,
MojoHandle* message_pipe_handle) override;
using PortMapping = base::flat_map<std::string, ports::PortRef>;
PortMapping TakeAttachedPorts();
private:
~InvitationDispatcher() override;
base::Lock lock_;
bool is_closed_ = false;
PortMapping attached_ports_;
};
attached_ports_ 维护了token(name),与对应的ports 的映射关系
ace-line
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/core/invitation_dispatcher.h"
#include "base/strings/string_piece.h"
#include "mojo/core/core.h"
namespace mojo {
namespace core {
InvitationDispatcher::InvitationDispatcher() = default;
Dispatcher::Type InvitationDispatcher::GetType() const {
return Type::INVITATION;
}
MojoResult InvitationDispatcher::Close() {
PortMapping attached_ports;
{
base::AutoLock lock(lock_);
if (is_closed_)
return MOJO_RESULT_INVALID_ARGUMENT;
is_closed_ = true;
std::swap(attached_ports, attached_ports_);
}
for (auto& entry : attached_ports)
Core::Get()->GetNodeController()->ClosePort(entry.second);
return MOJO_RESULT_OK;
}
//将token 与remote_peer_port映射
MojoResult InvitationDispatcher::AttachMessagePipe(
base::StringPiece name,
ports::PortRef remote_peer_port) {
base::AutoLock lock(lock_);
auto result = attached_ports_.emplace(std::string(name), remote_peer_port);
if (!result.second) {
Core::Get()->GetNodeController()->ClosePort(remote_peer_port);
return MOJO_RESULT_ALREADY_EXISTS;
}
return MOJO_RESULT_OK;
}
//抽取token对应message_pipe_handle 这里的对应关系 message_pipe_handle 映射 dispathcher(其中
//有prot<里面有uuid portname>) ,
MojoResult InvitationDispatcher::ExtractMessagePipe(
base::StringPiece name,
MojoHandle* message_pipe_handle) {
ports::PortRef remote_peer_port;
{
base::AutoLock lock(lock_);
auto it = attached_ports_.find(std::string(name));
if (it == attached_ports_.end())
return MOJO_RESULT_NOT_FOUND;
remote_peer_port = std::move(it->second);
attached_ports_.erase(it);
}
*message_pipe_handle =
Core::Get()->CreatePartialMessagePipe(remote_peer_port);
if (*message_pipe_handle == MOJO_HANDLE_INVALID)
return MOJO_RESULT_RESOURCE_EXHAUSTED;
return MOJO_RESULT_OK;
}
InvitationDispatcher::PortMapping InvitationDispatcher::TakeAttachedPorts() {
PortMapping attached_ports;
{
base::AutoLock lock(lock_);
std::swap(attached_ports, attached_ports_);
}
return attached_ports;
}
InvitationDispatcher::~InvitationDispatcher() {
DCHECK(is_closed_);
}
} // namespace core
} // namespace mojo
PlatformChannel
PlatformChannel 封装了两个纠缠的构造和所有权 特定于平台的通信原语的端点,例如a Windows pipe, a Unix domain socket, or a macOS Mach port pair.
一个端点是指定为"本地"端点,应由创建进程保留; 另一个端点被指定为"远程"端点,并且应该传递给外部进程。 PlatformChannels 可用于在一个进程和一个进程之间引导 Mojo IPC 其他。 通常,另一个进程是该进程的子进程,并且是将端点传递给子节点的辅助方法; 但是这个 并非所有平台都严格要求排列。 对于允许客户端按名称连接的通道(即命名管道 或套接字服务器,仅在 Windows 和 POSIX 系统上支持)见 命名平台通道。
使用方式:
mojo::OutgoingInvitation invitation;
mojo::PlatformChannel channel;
mojo::ScopedMessagePipeHandle server_pipe;
server_pipe = invitation.AttachMessagePipe(kBootstrapMojoConnectionChannelToken);
if (!server_pipe.is_valid()) {
LOG(ERROR) << "FaceAuthManager could not bind to invitation";
std::move(callback).Run(false);
return;
}
mojo::OutgoingInvitation::Send(std::move(invitation),
base::kNullProcessHandle,
channel.TakeLocalEndpoint());
构造函数:在linux 下创建了 socketpair(AF_UNIX, SOCK_STREAM, 0, fds)
PlatformChannel::PlatformChannel() {
PlatformHandle local_handle;
PlatformHandle remote_handle;
CreateChannel(&local_handle, &remote_handle);
local_endpoint_ = PlatformChannelEndpoint(std::move(local_handle));
remote_endpoint_ = PlatformChannelEndpoint(std::move(remote_handle));
}
#elif BUILDFLAG(IS_POSIX)
void CreateChannel(PlatformHandle* local_endpoint,
PlatformHandle* remote_endpoint) {
int fds[2];
#if BUILDFLAG(IS_NACL)
PCHECK(imc_socketpair(fds) == 0);
#else
PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
// Set non-blocking on both ends.
PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0);
PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0);
#if BUILDFLAG(IS_APPLE)
// This turns off |SIGPIPE| when writing to a closed socket, causing the call
// to fail with |EPIPE| instead. On Linux we have to use |send...()| with
// |MSG_NOSIGNAL| instead, which is not supported on Mac.
int no_sigpipe = 1;
PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
sizeof(no_sigpipe)) == 0);
PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
sizeof(no_sigpipe)) == 0);
#endif // BUILDFLAG(IS_APPLE)
#endif // BUILDFLAG(IS_NACL)
*local_endpoint = PlatformHandle(base::ScopedFD(fds[0]));
*remote_endpoint = PlatformHandle(base::ScopedFD(fds[1]));
DCHECK(local_endpoint->is_valid());
DCHECK(remote_endpoint->is_valid());
}
NodeChannel
节点到节点 IPC
处理节点到节点 IPC 的业务主要在 mojo::core::NodeChannel以及 mojo::core::Channel及其特定于操作系统的子类中通过交换 mojo::core::Channel::Message来处理。
请注意,Channel::Message 始终通过 Channel 从源节点到达目标节点(严格来说,每一侧都有一个)。由于两侧的通道(或操作系统提供的 IPC 机制)共同复制句柄,因此句柄传输中涉及的过程始终是隐式的。这意味着低权限或沙盒进程只能在消息中发送自己的句柄,并且不可能滥用它来获取另一个进程的句柄。
一个 NodeChannel 包装了一个与另一个给定节点通信的通道。它实现了到另一个特定节点的所有消息传递,既涉及一般消息传递(请参阅 NodeChannel::OnEventMessage),也涉及与邀请和端口管理相关的消息传递。
mojo\core\node_channel.h
// 包装一个 Channel 来发送和接收 Node 控制消息。
SendInvitation发送邀请,这一步也会创建一个NodeChannel,用以与子进程的NodeChannel通信;并且还会生成一个token,代表的是父进程与子进程通信的凭证。并且这里还涉及到一张映射表reserved_ports_和一张映射表pending_invitations_。
reserved_ports_是一个逆向的查找表,根据NodeName和MessagePipe关联的名字查找到对应的Port。
pending_invitations_存放正在进行握手的Invitation。用生成的token与创建的NodeChannel进行映射。
发送邀请过程如下,A邀请B (A) SendInvitation
->(A) AcceptInvitee
->(B) OnAcceptInvitee
->(B) AcceptInvitation
->(A) OnAcceptInvitation
->(A) AddPeer
,邀请结束,链接建立成功(两次握手)其中, AcceptInvitee
和 AcceptInvitation
的消息都是通过 NodeChannel
的 Channel
对象发送消息 按照文档说明,node与node之间一开始只有IPC点对点通信,假如要发给另一个node,但是和当前node之间没有直接的IPC,则需要通过broker建立一个新的IPC通道, REQUEST_INTRODUCTION
消息就是这个过程
消息处理逻辑:
ace-line
void NodeChannel::OnChannelMessage(const void* payload,
size_t payload_size,
std::vector<PlatformHandle> handles) {
DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
RequestContext request_context(RequestContext::Source::SYSTEM);
if (payload_size <= sizeof(Header)) {
delegate_->OnChannelError(remote_node_name_, this);
return;
}
const Header* header = static_cast<const Header*>(payload);
switch (header->type) {
case MessageType::ACCEPT_INVITEE: {
AcceptInviteeData data;
if (GetMessagePayloadMinimumSized<AcceptInviteeData, AcceptInviteeDataV0>(
payload, payload_size, &data)) {
// Attach any capabilities that the other side advertised.
SetRemoteCapabilities(data.capabilities);
delegate_->OnAcceptInvitee(remote_node_name_, data.inviter_name,
data.token);
return;
}
break;
}
case MessageType::ACCEPT_INVITATION: {
AcceptInvitationData data;
if (GetMessagePayloadMinimumSized<AcceptInvitationData,
AcceptInvitationDataV0>(
payload, payload_size, &data)) {
// Attach any capabilities that the other side advertised.
SetRemoteCapabilities(data.capabilities);
delegate_->OnAcceptInvitation(remote_node_name_, data.token,
data.invitee_name);
return;
}
break;
}
AcceptInvitee 邀请对方
void NodeChannel::AcceptInvitee(const ports::NodeName& inviter_name,
const ports::NodeName& token) {
AcceptInviteeData* data;
Channel::MessagePtr message = CreateMessage(
MessageType::ACCEPT_INVITEE, sizeof(AcceptInviteeData), 0, &data);
data->inviter_name = inviter_name;
data->token = token;
data->capabilities = local_capabilities_;
WriteChannelMessage(std::move(message));
}
![](https://i-blog.csdnimg.cn/blog_migrate/b1111179e96b4b7ca86a064dfbf3eedf.png)
其中,inviter_name 即node name_;token 即temporary_node_name,一个随机生成的,这部分代码在:void NodeController::SendBrokerClientInvitation
AcceptInvitation 接受邀请
void NodeChannel::AcceptInvitation(const ports::NodeName& token,
const ports::NodeName& invitee_name) {
AcceptInvitationData* data;
Channel::MessagePtr message = CreateMessage(
MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data);
data->token = token;
data->invitee_name = invitee_name;
data->capabilities = local_capabilities_;
WriteChannelMessage(std::move(message));
}
介绍机制
Broker
- 提供介绍机制(Introduce)帮助一对node建立IPC链接
broker是个特殊的node channel,规定只有它能introduce(通过调用 NodeController
的能力找到两个node)并发送Introduce message,这就要求broker需要跟每个node都有链接
node与node之间一开始只有IPC点对点通信,假如要发给另一个node,但是和当前node之间没有直接的IPC,则需要通过broker建立一个新的IPC通道, REQUEST_INTRODUCTION
消息就是这个过程.
在浏览器中,主进程是broker,它创建了子进程,每个子进程都有一个nodechannel与broker进程连接。
ace-line
枚举类型:uint32_t {
// 用户消息事件包含任意用户指定的负载数据
// 可能包括任意数量的端口和/或系统句柄(例如 FD)。
kUserMessage,
// 当一个 Node 接收到一个带有一个或多个端口的用户消息时,它
// 为每个附加端口发回此事件的实例以指示
// 该端口已被其目标节点接受。
kPortAccepted,
// 只要端口进入代理状态,此事件就会开始循环。 它
// 在某些边缘情况下可能会重新循环,但最终目的是
// 该事件是为了确保路线上的每个端口都是(如有必要)
// 意识到代理端口确实在代理(以及代理到哪里)以便它
// 可以开始沿路线绕过。
kObserveProxy,
// 用于向代理确认所有相关节点和
// 端口知道它的代理状态并且不再有用户消息
// 在给定的最终序列号之外被路由到它。
kObserveProxyAck,
//表示一个端口已经关闭。 本次活动充分流传
//路由以确保所有端口都知道关闭。
kObserve闭包,
// 用于通过两个牺牲接收请求合并两条路由
// 端口,每条路由一个。
kMergePort,
// 用于请求共轭端口确认读取的消息
// 发送回 UserMessageReadAck。
kUserMessageReadAckRequest,
// 用于向共轭物确认已读消息。
kUserMessageReadAck,
// 用于更新端口的前一个节点和端口名称。
kUpdatePreviousPeer,
ace-line
ObserveProxyEvent::ObserveProxyEvent(const PortName& port_name,
const PortName& from_port,
uint64_t control_sequence_num,
const NodeName& proxy_node_name,
const PortName& proxy_port_name,
const NodeName& proxy_target_node_name,
const PortName& proxy_target_port_name)
: Event(Type::kObserveProxy, port_name, from_port, control_sequence_num),
proxy_node_name_(proxy_node_name),
proxy_port_name_(proxy_port_name),
proxy_target_node_name_(proxy_target_node_name),
proxy_target_port_name_(proxy_target_port_name) {}
Chromium进程间的通信机制
主进程在创建子进程时,会设置将远程端点句柄传递给一个即将到来的进程启动。 返回一个可以在远程进程中使用的字符串:字符串在新进程的命令行上传递。
mojo\public\cpp\platform\platform_channel.h
void PlatformChannel::PrepareToPassRemoteEndpoint(
HandlePassingInfo* info,
base::CommandLine* command_line) {
std::string value;
PrepareToPassRemoteEndpoint(info, &value);
if (!value.empty())
command_line->AppendSwitchASCII(kHandleSwitch, value);
}
创建的字符串: --mojo-platform-channel-handle=7832
void ChildProcessLauncherHelper::PostLaunchOnLauncherThread(
ChildProcessLauncherHelper::Process process,
int launch_result) {
#if BUILDFLAG(IS_WIN)
// The LastError is set on the launcher thread, but needs to be transferred to
// the Client thread.
DWORD last_error = ::GetLastError();
#endif
if (mojo_channel_)
mojo_channel_->RemoteProcessLaunchAttempted();
if (process.process.IsValid()) {
RecordHistogramsOnLauncherThread(base::TimeTicks::Now() -
begin_launch_time_);
}
// Take ownership of the broker client invitation here so it's destroyed when
// we go out of scope regardless of the outcome below.
mojo::OutgoingInvitation invitation = std::move(mojo_invitation_);
if (process.process.IsValid()) {
#if !BUILDFLAG(IS_FUCHSIA)
if (mojo_named_channel_) {
DCHECK(!mojo_channel_);
mojo::OutgoingInvitation::Send(
std::move(invitation), base::kNullProcessHandle,
mojo_named_channel_->TakeServerEndpoint(), process_error_callback_);
} else
#endif
// Set up Mojo IPC to the new process.
{
DCHECK(mojo_channel_);
DCHECK(mojo_channel_->local_endpoint().is_valid());
mojo::OutgoingInvitation::Send(
std::move(invitation), process.process.Handle(),
mojo_channel_->TakeLocalEndpoint(), process_error_callback_);
}
}
client_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ChildProcessLauncherHelper::PostLaunchOnClientThread,
this, std::move(process),
#if BUILDFLAG(IS_WIN)
last_error,
#endif
launch_result));
}
参考文档:
Idl:https://chromium.googlesource.com/chromium/src/+/master/mojo/public/tools/bindings/README.md
bindings:https://chromium.googlesource.com/chromium/src/+/master/mojo/public/cpp/bindings/README.md#Other-Interface-Binding-Types
https://keyou.github.io/blog/2020/01/03/Chromium-Mojo\&IPC/
Invition 机制
https://zhuanlan.zhihu.com/p/512820043
使用文档:
mojo/public/cpp/system/README.md
/chromium/src/docs/mojo_and_services.md
explicit MathImpl(mojo::PendingReceiver<math::mojom::Math> receiver)的使用
mojo/docs/basics.md
https://blog.csdn.net/linuxarmsummary/article/details/112645740?utm_medium=distribute.pc_relevant.none-task-blog-2\~default\~baidujs_title\~default-0-112645740-blog-82411747.pc_relevant_multi_platform_whitelistv3\&spm=1001.2101.3001.4242.1\&utm_relevant_index=3
https://keyou.github.io/blog/2020/01/03/Chromium-Mojo\&IPC/