chromium进程间通信

因为chromium是多进程的架构,因此需要一种可靠的进程间通信机制。chromium在Windows上的进程间通信是基于命名管道实现的。异步的命名管道通信模式确保不会阻塞通信的双方。

browser中的IPC

在browser进程中,与renderers进程通信是在独立的I/O线程里。跟views的来往消息会通过主线程的ChannelProxy。这种架构的优势是资源请求等性能要求较高的消息会直接在I/O线程里处理了,不会导致UI的阻塞。

renderer中的IPC

每个renderer进程的主线程管理消息通信,大多数消息会调度到renderer线程去处理。

消息的类型

Chromium处理进程间通信类似于MFC/WTL的那套消息映射机制。一个进程创建一个IPC消息,然后打包上参数,发送到接收的进程。这里有两种类型的IPC消息,routed消息和control消息。两者的差别是发送routed的消息的时候需要一个routing_id参数,通过这个routing_id才能发送到正确的接收方。一般跟view或者frame概念相关的类才有routing_id。
举个例子,我要从主进程发送一个ViewMsg_ClosePage消息到渲染进程,通知它关闭某个网页。往往一个渲染进程里面存在多个RenderView对象,到底该哪个对象来处理ViewMsg_ClosePage消息呢。这种情况下需要用routed消息的routing_id参数来控制将消息传递到正确的RenderView里面。
control消息不带有routing_id参数,并不意味着RenderView、RenderFrame这样的类不能处理control消息。消息都可以自定义参数,你完全可以把routing_id加到control消息里面。但是通常的做法是routed消息是跟网页有关的,其他的情况则用control消息。
从另一个角度来看,IPC消息又可以分为同步消息跟异步消息。使用同步消息的时候应当特别注意,这可能会导致浏览器阻塞。应当尽量避免使用同步消息,事实上Chromium里面使用到同步消息的场景也很少。

声明消息

我们可以在各种*_messages.h的头文件看到IPC消息的定义。通过IPC_MESSAGE_ROUTED*、IPC_MESSAGE_CONTROL*这类的宏来定义异步消息。比如处理0个参数的消息,定义成IPC_MESSAGE_*0,处理2个参数的消息定义成IPC_MESSAGE_*2。
一些简单的参数类型,可以直接的打包到消息里面,比如:

Chromium内部已经可以支持直接序列化GURL、base::FilePath、base::Time等类型了。更多支持直接序列化的类型,可以从ipc_message_utils.h文件中看到。只要某个类型T有sruct ParamTraits<T>类型的实现,IPC系统就能自动序列化和反序列化T类型,比如支持GURL类型的定义:

枚举类型参数则通过IPC_ENUM_TRAITS_VALIDATE、IPC_ENUM_TRAITS、IPC_ENUM_TRAITS_MAX_VALUE相关宏去注册。

从IPC_MESSAGE_ROUTED*、IPC_MESSAGE_CONTROL*的定义里可以看到,它们最多支持5个参数。如果一个消息需要包含更多的参数,那么就只能把这些参数放到一个结构体里面。比如ChromeViewHostMsg_GetPluginInfo需要包含很多参数,超过了5个,就把更多的参数打包到ChromeViewHostMsg_GetPluginInfo_Output里面。

ChromeViewHostMsg_GetPluginInfo_Output是通过IPC_STRUCT_BEGIN、IPC_STRUCT_MEMBER这样的宏定义的:

有时候我们需要传递一个现有的结构体里面某个字段,而不是所有的字段,那么可以通过IPC_STRUCT_TRAITS_BEGIN、IPC_STRUCT_TRAITS_MEMBER把这个结构体里面需要传递的结构体提取出来,比如ChromeViewMsg_SearchBoxSubmit消息里EmbeddedSearchRequestParams参数的定义:

发送消息

我们发送消息都是通过channels。在browser中,RenderProcessHost中有个channel用来从UI线程发生消息到renderer中。

消息都是一个指针的形式发送出去,消息派送完成之后IPC会自己删除指针。因此我们都直接new一个消息,然后用send发生就行了,就像这样Send(new ViewMsg_StopFinding(routing_id_));。

发送routed消息时为了找到对应的view/viewhost,我们往往需要指定一个routed id。RenderWidgetHost和RenderWidget类已经提供了一个GetRoutingID成员函数以便我们使用。

不是所有的类都可以发送和接收IPC消息。对于control来说,browser进程中各种*ProcessHost*和继承自BrowserMessageFilter的类可以发送消息,gpu或者renderer等其他进程中,*ChildThread*,*ThreadObserver可以发送消息。

routed消息是跟页面相关,各种*RenderWidget*、*RenderView*、*RenderFrame*类可以发送该类型消息。其他不能直接发送IPC的类通过调用可以发送IPC的类来发送消息。

处理消息

要处理消息,需要实现IPC::Listener接口。在OnMessageReceived中,我们用一些宏去简化处理消息,比如这样:

同样的,你还可以使用IPC_MESSAGE_FORWARD进一步派发你收到的消息,让其他类去处理,比如这样:

browser进程通过继承自BrowserMessageFilter类来接收消息,其他进程通过继承IPC::MessageFilter类来接收消息。

Channels

IPC::Channel是通过管道通信的。IPC::SyncChannel提供同步等待消息回应的功能。Channels 不是线程安全的,但是有时候我们需要在其他线程发送消息。比如在UI线程上发送消息,我们借助的是IPC::ChannelProxy。

同步消息

有些消息需要同步处理,比如javascript需要获取cookies。browser到renderer的同步消息是被禁止使用的,以防阻塞了UI。

同步消息是通过IPC_SYNC_MESSAGE_* 宏声明的。同步消息还需要一个返回参数。比如:

同步消息和异步消息都是用IPC_MESSAGE_HANDLER去处理。

发表评论

电子邮件地址不会被公开。 必填项已用*标注