[WebKit]WebKit2多进程机制的解析

简介: 在中说到WebKit2中的多进程模型。多进程模型已经是浏览器的基本架构要素,下面展开分析一下WebKit2中的多进程模型。 协作决定接口,确立责任分工后,对于模块或系统间最重要的事莫过于接口定义,而且是有着简洁明确的定义。
在<<WebKit模块化分析>>中说到WebKit2中的多进程模型。多进程模型已经是浏览器的基本架构要素,下面展开分析一下WebKit2中的多进程模型。

协作决定接口,确立责任分工后,对于模块或系统间最重要的事莫过于接口定义,而且是有着简洁明确的定义。对于WebKit2中三个进程中的交互也是相当频繁和多样,如果使用传统的查表法对应解析执行,就会面临巨大的维护成本。WebKit2使用了Encoder和Decoder的概念的很好地将消息解析的工作放到各个功能上。于是提供的公共接口主要关注于消息的分发,而不再是似乎拥有一切的解析执行功能。

WebKit2在多进程接口上Process Launcher、CoreIPC以及WorkQueue构成了核心控制功能 (进程本身的Run Loop自然也是,但非此处的关注点),Encoder/Decoder/FunctionWrapper则是重要的工具类。下面就是对它们进行的学习和分析。

多线程模型的基本架构


先从基本结构看起。多进程模型是在WebKit中实现的,由WebKit中的三个Process分别处理应用主进程,网页处理进程及插件进程。
它们在具体的实现上是基于Process Launcher(WebKit2/UIProcess/Launcher)进行进程管理,再通过Core IPC进行跨进程通讯(IPC)。
   

CoreIPC做为一个公共,且由各个进程共享的模块,日后会被放置到WebCore或WTF中。放在WTF中较为合适,因为它在模块化的角色就是公共功能模块。

Process Launcher具体创建进程的代码需要平台适配,它使用了如下的方式:
      
ProcessLauncher.h和ProcessLauncher.cpp有通用的定义和公共代码的实现。再为不同平台建立不同的单元文件来实现平台差异的部分。负责创建新进程的launchProcess就在其中。

Core IPC也是类似的实现方式。在不同的平台下使用不同的IPC技术。
   
Mach Port是Mac OS下类似pipes技术的实现,而XPC则是新推出一种更为安全的IPC技术,目前国内相关的中文资料并不多,还是要花时研究官方文档(Creating XPC Services)和示例代码(SandboxedFetch)。另外网友rainbird还推荐一个对XPC的封装


多进程模型的交互机制


WebKit2中使用来Proxy来与其它进程进行通讯。
  
WebProcessProxy与PluginProcessProxy均会继承自ProcessLauncher::Client, 最后是通过CoreIPC::Connection来完成通讯。因为存在页面上的对应关系,所以消息传递时都会带一个Page ID来标识,这样在共享Web Process时就不会出现混淆的问题。

下图是一个进程通讯的架构, CoreIPC::Connection会通过与其它几个类一起完成进程间消息传递:
   
流程也很容易理解,消息发送者通过Encoder对要发送的信息进行编码,再由Work Queue控制触发发送或接收操作,最后由Decoder进行解码,最后交由目标类解析执行。
消息发送者Sender Class会继承自CoreIPC::MessageSender,而接收者Target Class会继承自CoreIPC::MessageReceiver。

Encoder的实现与平台无关。 Work Queue自己会有队列管理机制触发发送操作,再由注册回调函数触发接收操作。无论发送和接收的函数都是由Connection通Function Wrapper提供的,Work Queue就是负责在适当的时机下触发。它在不同平台使用了不同的技术:
  
 关于GCD可以参考一下这篇文章:
   GCD介绍
 Windows下的Thread Pool的说明可以看这里:

下面Work Queue的类图:
    

.dispatch用于发送时,由Connection调用并传入负责发送的函数句柄。
.executeFunction则是用于执行指定的函数指针。
.invalidate则是用于清除当前排入的任务队列。
.platformXXX表示的是对应于各个平台的不同实现。其跨平台模式类似于ProcessLauncher。
.registerXXX与unregisterXXX用于注册和取消处理接收到新消息的回调函数,这个函数也是由Connection指定的( Mac OS下和Windows下的函数名不同)。


下面是Connection与WorkQueue交互示意的序列图:
      

多进程模型中消息数据设计


上面提到了消息在传递过程中外发的消息(outgoing message)和收到的消息(incoming message)都由CoreIPC::Connection类来管理,而且包含
一个编解码的过程。

1. 消息


每一个消息都有一个特定的定义。如下所示,Arguement1和Argument2是两个模板。
        
首先CoreIPC为不同数量参数的消息定义了一套模板,就是ArgumentX, X从1到8,就是最大表示8个参数的消息,模板数据类型指定的是各个参数的类型。
其中一部分使用了修饰模式。不同参数的模板都采用对前一个模板的实现来达到复用的目的。 这种定义方式和一般的查表法是不同的,目的在于消息本身就能表示自己,而不需要再做额外的对消息的映射。

下面是LoadURL消息的定义:

struct LoadURL : CoreIPC::Arguments2<constWTF::String&, constWebKit::SandboxExtension::Handle&> {

    static const Kind messageID = LoadURLID;

    static CoreIPC::StringReference receiverName() { return messageReceiverName(); }

    staticCoreIPC::StringReference name() { returnCoreIPC::StringReference("LoadURL"); }

    staticconstbool isSync = false;


    typedefCoreIPC::Arguments2<constWTF::String&, constWebKit::SandboxExtension::Handle&> DecodeType;

    LoadURL(const WTF::String& url, const WebKit::SandboxExtension::Handle& sandboxExtensionHandle)

        : CoreIPC::Arguments2<const WTF::String&, const WebKit::SandboxExtension::Handle&>(url, sandboxExtensionHandle)

    {

    }

};

2. Encoder

所谓Encoder就是信息的各个参数组装到一个buffer中的操作。
Encode操作都是通过CoreIPC::MessageEncoder来完成的。它对Message的Message Name, Receiver Name, Destination ID(即Page ID)以及各个参数进行编码。
   
*encode方法有不同类型的副本。
*m_inlineBuffer是字符数组,会比m_buffer动态分配的方式效率要高。

下面Encode的结果(以loadURL为例):

实际发送时就是将这个buffer的内容发送出去。再由接收到使用对应的Decoder解析出来。

   
通过ReceiverName可以创建一个MessageReceiver对象,即此消息的处理对象。就这样,这个消息就确定应当由谁来处理了。

bool MessageReceiverMap::dispatchMessage(Connection* connection, MessageID messageID, MessageDecoder& decoder)

{

    if (MessageReceiver* messageReceiver = m_globalMessageReceivers.get(decoder.messageReceiverName())) {

        ASSERT(!decoder.destinationID());


        messageReceiver->didReceiveMessage(connection, messageID, decoder);

        return true;

    }
    ……
}

和传统的处理方式相比,是不是简洁很多。虽然Recevier Names还是要有表存在,但相对常常的switch/case或查表法,它的变动性已经被极大的缩小了,也就降低了日后的维护成本。如果要接受消息处理,只要调用MessageReceiverMap::addMessageReceiver()添加一项Receiver,而参数就是Receiver的名称和类的实例。

再到具体的Message处理对象中处理,对于要接受消息处理的类,会继承自MessageReceiver,使得它们都有一个消息处理的入口。如:
void WebPage::didReceiveWebPageMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::MessageDecoder& decoder)
{
     ……

    if (decoder.messageName() == Messages::WebPage::LoadURL::name()) {

        CoreIPC::handleMessage<Messages::WebPage::LoadURL>(decoder, this, &WebPage::loadURL);

        return;

    }
   ……
}

最终通过 callMemberFunction()来执行消息对应的函数,即上面代码指定的this和&WebPage::loadURL,decoder中则存储着参数。

3. Function Wrapper


Function Wrapper的功能就是将函数或者某个对象的成员封装成函数指针。在Work Queue与Connection的函数调用过程都是使用Function Wrapper来完成的。
   
Function Wrapper也是一个模板类,用于达到多态以支持不同的函数类型。比如下面支持类成员函数的定义(Functional.h):

template<typename R, typename C>

class FunctionWrapper<R (C::*)()> {

public:

    typedef R ResultType;

    static const bool shouldRefFirstParameter = HasRefAndDeref<C>::value;

    static const bool shouldValidateFirstParameter = true;


    explicit FunctionWrapper(R (C::*function)())

        : m_function(function)

    {

    }


    R operator()(C* c)

    {

        return (c->*m_function)();

    }


private:

    R (C::*m_function)();

};


Function Wrapper对Work Queue要封装就是Connection::sendOutgoingMessage和Connection:: dispatchOneMessage()两个分别用于处理发送和收到的消息。


多进程模型的交互序列图示例

以下是一个交互的序列图,方便更好地理解它的设计。

在UI上加载页面时,UIProcess中的WebPageProxy向WebProcess中的WebPage发起loadURL请求。首先会通过调用Connection增加一个message到自己的outgoing message queue中。然后将自身的发送函数(sendOutgoingMessages)发送给WorkQueue对象执行,而不是直接把消息发送到WorkQueue中执行。所以WorkQueue只是一个控件类,而不是接口类。
    
WorkQueue会在适当的时机执行提交的任务,由executeFunction来执行相应的发送函数,将消息通过系统的机制发送出去。


而在WebProcess中,它会先在WorkQueue(不同的实例对象)中注册一个事件处理函数,当事件进来后,它就会被执行到。进行触发Connection对新的消息进行解析并加入到自己的incoming message queue中。
  

Web Process会在自己的Run Loop中要求Connection解析收到的消息,并在解析后调用对应的对象的处理函数。通过callMemberFunction()就最终调用到了WebPage::loadURL()函数。


转载请注明出处:http://blog.csdn.net/horkychen





  





目录
相关文章
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
567 2
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
280 20
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
13999 46
|
PHP 开发者 UED
PHP中的异常处理机制解析####
本文深入探讨了PHP中的异常处理机制,通过实例解析try-catch语句的用法,并对比传统错误处理方式,揭示其在提升代码健壮性与可维护性方面的优势。文章还简要介绍了自定义异常类的创建及其应用场景,为开发者提供实用的技术参考。 ####
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
326 8
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
726 8
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
651 4
|
Java 测试技术 API
Java 反射机制:深入解析与应用实践
《Java反射机制:深入解析与应用实践》全面解析Java反射API,探讨其内部运作原理、应用场景及最佳实践,帮助开发者掌握利用反射增强程序灵活性与可扩展性的技巧。
609 5

热门文章

最新文章

推荐镜像

更多
  • DNS