分享

Chromium内核原理之进程间通信(IPC)

 wusiqi111 2019-07-05

https://www.jianshu.com/p/c9703029671b


 

《Chromium内核原理之blink内核工作解密》
《Chromium内核原理之多进程架构》
《Chromium内核原理之进程间通信(IPC)》
《Chromium内核原理之网络栈》
《Chromium内核原理之网络栈HTTP Cache》
《Chromium内核原理之Preconnect》
《Chromium内核原理之Prerender》
《Chromium内核原理之cronet独立化》

1.进程间通信概述

1.1 browser端的IPC
1.2 renderer端的IPC

2.消息机制

2.1 消息类型
2.2 消息声明
2.3 发送消息
2.4 处理消息
2.5 安全策略

3.Channels通道
4.同步消息

4.1 声明同步消息
4.2 发送同步消息
4.3 处理同步消息
4.4 将消息类型转换为消息名称

5.IPC方式总结

1.进程间通信概述

Chromium具有多进程架构,这意味着我们有许多进程相互通信。我们的主要进程间通信原语是命名管道。在Linux和OS X上,我们使用socketpair()。为每个渲染器进程分配命名管道,以便与浏览器进程通信。管道以异步模式使用,以确保两端都不会被阻塞等待另一端。

有关如何编写安全IPC端点的建议,请参阅IPC安全提示。

1.1 browser端的IPC

在浏览器中,与渲染器的通信在单独的I / O线程中完成。然后,必须使用ChannelProxy将进出视图的消息代理到主线程。此方案的优点是资源请求(对于网页等)是最常见和性能关键的消息,可以完全在I / O线程上处理,而不是阻止用户界面。这些是通过使用ChannelProxy :: MessageFilter完成的,它由RenderProcessHost插入到通道中。此过滤器在I / O线程中运行,拦截资源请求消息,并将它们直接转发到资源调度程序主机。有关资源加载的详细信息,请参阅多进程资源加载。

1.2 renderer端的IPC

每个渲染器还有一个管理通信的线程(在本例中为主线程),渲染和大多数处理发生在另一个线程上(参见多进程架构中的图表)。大多数消息通过主渲染器线程从浏览器发送到WebKit线程,反之亦然。这个额外的线程是支持同步渲染器到浏览器的消息(参见下面的“同步消息”)。

2.消息机制

2.1 消息类型

我们有两种主要类型的消息:“路由”和“控制”。控制消息由创建管道的类处理。有时,该类将允许其他人通过拥有其他侦听器可以注册的MessageRouter对象来接收消息,并接收以其唯一(每个管道)id发送的“路由”消息。

例如,在渲染时,控制消息不是特定于给定视图,而是由RenderProcess(渲染器)或RenderProcessHost(浏览器)处理。对资源的请求或修改剪贴板不是特定于视图的,因此控制消息也是如此。路由消息的示例是要求视图绘制区域的消息。

历史上已使用路由消息将消息发送到特定的RenderViewHost。但是,从技术上讲,任何类都可以通过使用RenderProcessHost :: GetNextRoutingID并使用RenderProcessHost :: AddRoute注册自己来接收路由消息。目前,RenderViewHost和RenderFrameHost实例都有自己的路由ID。

与消息类型无关的是消息是从浏览器发送到渲染器,还是从渲染器发送到浏览器。与从浏览器发送到渲染器的文档框架相关的消息称为框架消息,因为它们被发送到RenderFrame。同样,从渲染器发送到浏览器的消息称为FrameHost消息,因为它们被发送到RenderFrameHost。您会注意到frame_messages.h中定义的消息是两个部分,一个用于Frame,另一个用于FrameHost消息。

可以参考frame_message.h的代码:
https://cs./chromium/src/content/common/frame_messages.h

插件也有单独的进程。与渲染消息一样,有PluginProcess消息(从浏览器发送到插件进程)和PluginProcessHost消息(从插件进程发送到浏览器)。这些消息都在plugin_process_messages.h中定义。自动化消息(用于从UI测试控制浏览器)以类似的方式完成。

同一组织适用于在浏览器和渲染器之间交换的其他消息组,以及在view_messages.h中定义的RenderViewHost和RenderView之间交换的View和ViewHost标记消息。

可以参考view_messages.h的代码:
https://cs./chromium/src/content/common/view_messages.h

2.2 消息声明

特殊宏用于声明消息。要从呈现器向浏览器声明路由消息(例如,特定于帧的FrameHost消息),其中包含URL和整数作为参数,请写入:
IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)

要声明从浏览器到渲染器的控制消息(例如,不是特定于帧的帧消息),不包含任何参数,请写入:
IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)

使用ParamTraits模板将参数序列化并反序列化为消息体。为ipc_message_utils.h中的大多数常见类型提供了此模板的特化。如果您定义自己的类型,则还必须为其定义自己的ParamTraits专门化。

有时,消息中包含太多值,无法合理地放入消息中。在这种情况下,我们定义一个单独的结构来保存值。例如,对于FrameMsg_Navigate消息,在navigation_params.h中定义了CommonNavigationParams结构。 frame_messages.h使用IPC_STRUCT_TRAITS宏系列定义结构的ParamTraits特化。

2.3 发送消息

您通过“Channels”通道发送消息(见下文)。在浏览器中,RenderProcessHost包含用于将消息从浏览器的UI线程发送到渲染器的通道。 RenderWidgetHost(RenderViewHost的基类)提供了一个方便使用的Send函数。
消息由指针发送,并在调度后由IPC层删除。因此,一旦找到合适的发送功能,只需使用新消息调用它:
Send(new ViewMsg_StopFinding(routing_id_));

请注意,您必须指定路由ID,以便将消息路由到接收端的正确View / ViewHost。 RenderWidgetHost(RenderViewHost的基类)和RenderWidget(RenderView的基类)都有可以使用的GetRoutingID()成员。

2.4 处理消息

消息通过实现IPC :: Listener接口来处理,该接口是OnMessageReceived最重要的功能。我们有各种各样的宏来简化此函数中的消息处理,最好通过示例来说明:

MyClass::OnMessageReceived(const IPC::Message& message) {
  IPC_BEGIN_MESSAGE_MAP(MyClass, message)    // Will call OnMyMessage with the message. The parameters of the message will be unpacked for you.
    IPC_MESSAGE_HANDLER(ViewHostMsg_MyMessage, OnMyMessage)  
    ...
    IPC_MESSAGE_UNHANDLED_ERROR()  // This will throw an exception for unhandled messages.
  IPC_END_MESSAGE_MAP()
}// This function will be called with the parameters extracted from the ViewHostMsg_MyMessage message.MyClass::OnMyMessage(const GURL& url, int something) {
  ...
}

您也可以使用IPC_DEFINE_MESSAGE_MAP为您实现功能定义。在这种情况下,不要指定消息变量名,它将在给定的类上声明OnMessageReceived函数并实现其内容。

其他的宏定义:IPC_MESSAGE_FORWARD:这与IPC_MESSAGE_HANDLER相同,但您可以指定自己的类来发送消息,而不是将其发送到当前类。

IPC_MESSAGE_FORWARD(ViewHostMsg_MyMessage, some_object_pointer, SomeObject::OnMyMessage)
IPC_MESSAGE_HANDLER_GENERIC:这允许您编写自己的代码,但您必须自己从消息中解压缩参数:
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_MyMessage, printf("Hello, world, I got the message."))

2.5 安全策略

IPC中的安全漏洞可能会产生令人讨厌的后果(文件被盗,沙箱逃逸,远程代码执行)。查看我们的IPC文档安全性,获取有关如何避免常见陷阱的提示。

3.Channels通道

IPC :: Channel(在ipc / ipc_channel.h中定义)定义了跨管道通信的方法。 IPC :: SyncChannel提供了同步等待对某些消息的响应的附加功能(呈现器进程使用它,如下面“同步消息”部分所述,但浏览器进程从不这样做)。

通道不是线程安全的。我们经常想在另一个线程上使用频道发送消息。例如,当UI线程想要发送消息时,它必须通过I / O线程。为此,我们使用IPC :: ChannelProxy。它具有与常规通道对象类似的API,但将消息代理到另一个线程以发送它们,并在接收时将消息代理回原始线程。它允许您的对象(通常在UI线程上)在通道线程(通常是I / O线程)上安装IPC :: ChannelProxy :: Listener,以过滤掉代理的某些消息。我们将此用于资源请求和可以直接在I / O线程上处理的其他请求。 RenderProcessHost安装一个RenderMessageFilter对象来执行此过滤。

4.同步消息

从渲染器的角度来看,某些消息应该是同步的。这种情况主要发生在我们应该返回某个WebKit调用时,但我们必须在浏览器中执行此操作。此类消息的示例包括拼写检查和获取JavaScript的cookie。禁止同步浏览器到渲染器IPC以防止在可能存在碎片的渲染器上阻塞用户界面。

注意:不要在UI线程中处理任何同步消息!您必须仅在I / O线程中处理它们。否则,应用程序可能会死锁,因为插件需要从UI线程进行同步绘制,并且当渲染器等待来自浏览器的同步消息时,这些将被阻止。

4.1 声明同步消息

使用IPC_SYNC_MESSAGE_ *宏声明同步消息。这些宏具有输入和返回参数(非同步消息缺少返回参数的概念)。对于带有两个输入参数并返回一个参数的控制函数,您可以将2_1附加到宏名称以获取:

IPC_SYNC_MESSAGE_CONTROL2_1(SomeMessage,  // Message name
                            GURL, //input_param1
                            int, //input_param2
                            std::string); //result

同样,您也可以将消息路由到视图,在这种情况下,您可以将“control”替换为“routed”以获取IPC_SYNC_MESSAGE_ROUTED2_1。您还可以有0个输入或返回参数。当渲染器必须等待浏览器执行某些操作但不需要任何结果时,将使用没有返回参数。我们将它用于某些打印和剪贴板操作。

4.2 发送同步消息

当WebKit线程发出同步IPC请求时,请求对象(从IPC :: SyncMessage派生)通过IPC :: SyncChannel对象(同一个也用于发送所有异步消息)被分派到渲染器上的主线程。 。 SyncChannel在收到同步消息时将阻塞调用线程,并且只在收到回复时才解除阻塞。

当WebKit线程正在等待同步回复时,主线程仍然从浏览器进程接收消息。这些消息将被添加到WebKit线程的队列中,以便在唤醒时进行处理。收到同步消息回复后,线程将被取消阻止。请注意,这意味着可以无序处理同步消息回复。

同步消息的发送方式与普通消息的发送方式相同,输出参数将提供给构造函数。例如:

const GURL input_param("http://www.google.com/");std::string result;
content::RenderThread::Get()->Send(new MyMessage(input_param, &result));printf("The result is %s\n", result.c_str());
4.3 处理同步消息

同步消息和异步消息使用相同的IPC_MESSAGE_HANDLER等宏来分派消息。消息的处理函数将具有与消息构造函数相同的签名,该函数将简单地将输出写入输出参数。对于上面的消息,您将添加IPC_MESSAGE_HANDLER(MyMessage, OnMyMessage)到OnMessageReceived 函数中。

void RenderProcessHost::OnMyMessage(GURL input_param, std::string* result) {
  *result = input_param.spec() + " is not available";
}
4.4 将消息类型转换为消息名称

如果您遇到崩溃并且您有消息类型,则可以将其转换为消息名称。消息类型为32位值,高16位为类,低16位为id。该类基于ipc / ipc_message_start.h中的枚举,id基于定义消息的文件中的行号。这意味着您需要获取Chromium的确切修订版才能准确获取消息名称。

5.IPC方式总结

Chromium为解决这些问题,使用了 非阻塞式,多线程,配合状态监听的解决方案! 主要运用了以下关键技术:

  • Unix Domain Socket (POSIX下使用的IPC机制)

  • libevent (轻型事件驱动的网络库,用于监听IPC中的端口(文件描述符))

  • ChannelProxy (为Channel提供线程安全的机制)

  • 闭包 (线程的运作方式)

Libevent是一个轻量级的开源高性能网络库.有几个显著的亮点:

  • a. 事件驱动(event-driven)

  • b. 高性能 轻量级,专注于网络,不如ACE那么臃肿庞大

  • c. 注册事件优先级


分发消息时,分为广播和专线两种方式。在Chromium中一个页面在不同线程,Browser和Renderer两端以routed id为标识彼此。如果要说悄悄话,就指定一个routed id, 这类消息称为Routed Message,是专线。 另一类消息,则是进行广播,不区分身份,这类消息为Control Message。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多