chromium源码阅读--进程间通信(IPC)
第一篇就有提到Chromium是目前默认是采用多进程架构,当然,chromium有singe-process的版本。
多进程与多线程的区别,确实有很多可以讲的,我的另一篇博客也讲了一些 (Linux 进程,线程),这里是从浏览器的角度来说,如果是多线程,如果一个线程崩溃,影响了整个浏览器的使用,因为在现在的网页标准更新了很多个版本,会有不同标准的页面在网络上,极大可能出现解析,渲染,插件等问题,那么对于用户来说,体验就会差很多了,浏览一个页面出问题,就要重启浏览器。而多进程则可以避免此问题,render进程崩溃只会影响当前的tab。
嗯,上面说了那么多,就是为了说,多进程之间就需要进程通信来协作,而chromium的进程间通信是非常繁杂的,如何处理这个是我们需要了解的关键。
那么本质的问题就是:
1、发那些消息(Message Type)
2、消息通道是怎么建立的 (Message Channel)
3、发送者和接收者(Sender,Listener)
OK,咱一个个来。
一、 Message Type
主要分为2类:“routed” 和 “control”。
1、routed消息
主要是用来给某个RenderViewHost对象发送消息的。不过,任何类都可以通过GetNextRoutingID 和 AddRoute 注册,就能接收routed消息。
2、control消息
control消息有创建pipe的类处理,当然这些类也可以接收routed消息。比如,请求资源或修改剪贴板不是特定于视图的,所以是控制消息。
3、消息的声明
1 IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)
这个宏用来声明routed消息,这里声明了一个从render进程发送到browser进程的消息,并有一个GURL参数,一个int参数
1 IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)
这个宏用来声明control消息,这里声明了一个从browser进程发送到render进程的消息,没有参数。
这里还有几个默认的约定:
(1)这些宏后面的数字表明有几个参数,最多5个参数,即: IPC_MESSAGE_ROUTED0~IPC_MESSAGE_ROUTED5 或者 IPC_MESSAGE_CONTROL0~IPC_MESSAGE_CONTROL5
(2)消息名称表明消息的接受者,FrameHostMsg,带Host后缀的类,表示在browser进程接收处理的消息,FrameMsg,则表示在render进程处理的消息,如果是Plugin进程,也会带有Plugin字样。
二、Message Channel
chromium的使用mojo IPC,并且在官网提供了性能对比 (Times in microseconds)
Windows Z840 |
Linux Z620 |
MacBook Pro 15" 2016 |
|
IPC |
36.9 |
69.5 |
52.5 |
Mojo cross-process |
28.2 |
48 |
34.9 |
这里是官网关于mojo的一些介绍,https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md#System-Overview
从unittest看channel的创建:
1 void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) { 2 channel_ = 3 IPC::ChannelMojo::Create(TakeHandle(), IPC::Channel::MODE_SERVER, 4 listener, base::ThreadTaskRunnerHandle::Get()); 5 }
在IPC::ChannelMojo::Create里看到需要 IPC::ChannelMojo的构造,
1 ChannelMojo::ChannelMojo( 2 mojo::ScopedMessagePipeHandle handle, 3 Mode mode, 4 Listener* listener, 5 const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) 6 : task_runner_(ipc_task_runner), 7 pipe_(handle.get()), 8 listener_(listener), 9 weak_factory_(this) { 10 weak_ptr_ = weak_factory_.GetWeakPtr(); 11 bootstrap_ = MojoBootstrap::Create(std::move(handle), mode, ipc_task_runner); 12 }
在MojoBootstrapImpl里完成sender和listener的绑定:
1 class MojoBootstrapImpl : public MojoBootstrap { 2 public: 3 MojoBootstrapImpl( 4 mojo::ScopedMessagePipeHandle handle, 5 const scoped_refptr<ChannelAssociatedGroupController> controller) 6 : controller_(controller), 7 associated_group_(controller), 8 handle_(std::move(handle)) {} 9 10 ~MojoBootstrapImpl() override { 11 controller_->ShutDown(); 12 } 13 14 private: 15 void Connect(mojom::ChannelAssociatedPtr* sender, 16 mojom::ChannelAssociatedRequest* receiver) override { 17 controller_->Bind(std::move(handle_)); 18 controller_->CreateChannelEndpoints(sender, receiver); 19 } 20 21 。。。 22 }
上面的mojo Channel的创建过程,linux提供的IPC比如:pipe,unix socket,share memory都不是线程安全的,mojo封装了底层IPC细节并提供了线程安全保障,并且看上面的性能对比,mojo性能更好,这也是chromium逐渐转用mojo的主要因素吧。
OK,上面介绍了mojo,接下来我们会发现,在进程里都是使用IPC::ChannelProxy这个类来代理完成Channel的各种工作。
这里我们只需看一个例子就能理解了,比如在browser进程的RenderProcessHost类里声明了GetChannel接口:
1 IPC::ChannelProxy* GetChannel() = 0;
根据chromium的套路,你大致就能想到,有一个RenderProcessHostImpl类会来实现这个接口,嗯,果不其然:
1 class CONTENT_EXPORT RenderProcessHostImpl 2 : public RenderProcessHost, 3 public ChildProcessLauncher::Client, 4 public ui::GpuSwitchingObserver, 5 public mojom::RouteProvider, 6 public mojom::AssociatedInterfaceProvider, 7 public mojom::RendererHost { 8 ... 9 IPC::ChannelProxy* GetChannel() override; 10 ... 11 }
我们可以看到这里会提供一个IPC::ChannelProxy的指针,那么顺着这个,ChannelProxy的创建和初始化就不远了。
bool RenderProcessHostImpl::Init() { ... if (!channel_) InitializeChannelProxy(); ... CreateMessageFilters(); RegisterMojoInterfaces(); ... }
可以看到,上面初始化了Channel并给当前实例创建了MessageFilter和在mojo里注册了消息发送的mojo interface。
mojo会负责将channel两端连通,之后的消息发送就可使用IPC::ChannelProxy来完成了。
三、发送者和接收者
1、发送者
chromium里定义了IPC::Sender的接口:
1 class Message; 2 3 class IPC_EXPORT Sender { 4 public: 5 // Sends the given IPC message. The implementor takes ownership of the 6 // given Message regardless of whether or not this method succeeds. This 7 // is done to make this method easier to use. Returns true on success and 8 // false otherwise. 9 virtual bool Send(Message* msg) = 0; 10 11 protected: 12 virtual ~Sender() {} 13 };
上面的使用例子,我们可以看到 IPC::ChannelProxy 是消息的发送者,看类的声明:
1 class IPC_EXPORT ChannelProxy : public Sender { 2 3 }
2、接收者
同样chromium也定义Listener。
class Message; // Implemented by consumers of a Channel to receive messages. class IPC_EXPORT Listener { public: // Called when a message is received. Returns true iff the message was // handled. virtual bool OnMessageReceived(const Message& message) = 0; ... };
我们在前面提到的router,是消息接收者,也是消息发送者:
1 class IPC_EXPORT MessageRouter : public Listener, public Sender { 2 ... 3 }
还有子线程实例也是Listener:
1 class CONTENT_EXPORT ChildThreadImpl 2 : public IPC::Listener, 3 virtual public ChildThread, 4 private base::FieldTrialList::Observer, 5 public mojom::RouteProvider, 6 public mojom::AssociatedInterfaceProvider, 7 public mojom::ChildControl { 8 ... 9 }
好了,更多例子我也不举了,chromium IPC还有更多的内容,在代码待我们学习,这里暂时总结到这里,后续再补充。