Chromium多进程架构简要介绍和学习计划

Chromium以多进程架构著称,它主要包含四类进程,分别是Browser进程、Render进程、GPU进程和Plugin进程。之所以要将Render进程、GPU进程和Plugin进程独立出来,是为了解决它们的不稳定性问题。也就是说,Render进程、GPU进程和Plugin进程由于不稳定而引发的Crash不会导致整个浏览器崩溃。本文就对Chromium的多进程架构进行简要介绍,以及制定学习计划。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

一个Chromium实例只有一个Browser进程和一个GPU进程,但是Render进程和Plugin进程可能有若干个。Browser进程负责合成浏览器的UI,包括标题栏、地址栏、工具栏以及各个TAB的网页内容。Render进程负责解析和渲染网页的内容。一般来说,一个TAB就对应有一个Render进程。但是我们也可以设置启动参数,让具有相同的域名的TAB都运行在同一个Render进程中。简单起见,我们就假设一个TAB就对应有一个Render进程。无论是Browser进程,还是Render进程,当启用了硬件加速渲染时,它们都是通过GPU进程来渲染UI的。不过Render进程是将网页内容渲染在一个离屏窗口的,例如渲染在一个Frame Buffer Object上,而Browser进程是直接将UI渲染在Frame Buffer上,也就是屏幕上。正因为如此,Render进程渲染好的网页UI要经过Browser进程合成之后,才能在屏幕上看到。Plugin进程,就是用来运行第三方开发的Plugin,以便可以扩展浏览器的功能。例如,Flash就是一个Plugin,它运行在独立的Plugin进程中。注意,为了避免创建过多的Plugin进程,同一个Plugin的不同实例都是运行在同一个Plugin进程中的。也就是说,不管是在同一个TAB的网页创建的同类Plugin,还是在不同TAB的网页创建的同类Plugin,它们都是运行在同一个Plugin进程中。

从上面的分析就可以知道,虽然每一个进程的职责不同,但是它们不是相互孤立的,而是需要相同协作,这样就需要执行进程间通信(IPC)。例如,Render进程渲染好自己负责解析的网页之后,需要通知GPU进程离屏渲染已经解决好的网页的UI,接着还要通知Browser进程合成已经离屏渲染好的网页UI。同样,Browser进程也需要通过GPU进程合成标题栏、地址栏、工具栏和各个网页的离屏UI。对于Plugin进程,Render进程需要将一些网页的事件发送给它处理,这样Render进程就需要与Plugin进程进行通信。反过来,Plugin进程也需要通过SDK接口向Render进程请求一些网页相关的信息,以便可以扩展网页的内容。更进一步地,如果Plugin进程需要绘制自己的UI,那么它也需要通过Render进程间接地和GPU进程进行通信。

以上分析的Browser进程、Render进程、GPU进程和Plugin进程,以及它们之间的通信方式,可以通过图1描述,如下所示:

图1 Chromium多进程架构

从图1可以看到,每一个进程除了具有一个用来实现各自职责的主线程之外,都具有一个IO线程。这个IO线程不是用来执行读写磁盘文件之类的IO的,而是用来负责执行IPC的。它们之所以称为IO线程,是因为它们操作的对象是一个文件描述符。即然操作的对象是文件描述符,当然也可以称之类IO。当然,这些是特殊的IO,具体来说,就是一个UNIX Socket。UNIX Socket是用来执行本地IPC的,它的概念与管道是类似的。只不过管道的通信是单向的,一端只能读,另一端只能写,而UNIX Socket的通信是双向的,每一端都既可读也可写。

关于IO线程的实现,可以参考前面Chromium多线程模型设计和实现分析一文。简单来说,就是我们创建了一个UNIX Socket之后,就可以获得两个文件描述符。其中一个文件描述符作为Server端,增加到Server端的IO线程的消息循环中去监控,另一个文件描述符作为Client端,增加到Client端的IO线程的消息循环中去监控。对这些文件描述符的读写操作都封装在一个Channel对象。因此,Server端和Client端都有一个对应的Channel对象。

当一个进程的主线程执行某个操作需要与另一个进程进行通信时,它的主线程就会将一个消息发送到IO线程的消息循环去。IO线程在处理这个消息的时候,就会通过前面已经创建好的UNIX Socket转发给目标进程处理。目标进程在其IO线程接收到消息之后,一般也会通过其主线程的消息循环通知主线程执行相应的操作。这就是说,在Chromium里面,线程间通过消息循环进行通信,而进程间通过UNIX Socket进行通信的。

我们先来看Browser进程和Render进程之间的通信。Browser进程每启动一个Render进程,都会创建一个RenderProcessHost对象。Render进程启动之后,会创建一个RenderProcess对象来描述自己。这样,Browser进程和Render进程之间的通信就通过上述的RenderProcessHost对象和RenderProcess对象进行。

我们再来看Browser进程和GPU进程之间的通信。Browser进程会创建一个GpuProcessHost对象来描述它启动的GPU进程,GPU进程启动之后,会创建一个GpuProcess进程。这样,Browser进程和GPU进程之间的通信就通过上述的GpuProcessHost对象和GpuProcess对象进行。注意,这两个对象之间的Channel是用来执行信令类通信的。例如,Browser进程通过上述Channel可以通知GPU进程创建另外一个Channel,专门用来执行OpenGL命令。这个专门用来执行OpenGL命令的Channel称为Gpu Channel。

我们知道,GPU进程需要同时为多个进程执行OpenGL命令,而OpenGL命令又是具有状态的,因此,GPU进程就需要为每一个Client进程创建一个OpenGL上下文,也就是一个GLContext对象。GPU进程在为某一个Client进程执行OpenGL命令之前,需要找到之前为该Client进程创建的GLContext对象,并且将该GLContext对象描述的OpenGL上下文设置为当前的OpenGL上下文。

前面提到,Render进程也需要与GPU进行通信,这意味着它们也像Browser进程一样,需要与GPU进程建立一对Gpu Channel。不过,Render进程不能像Browser进程一样,直接请求GPU进程创建一对Gpu Channel。Render进程首先要向Browser进程发送一个创建Gpu Channel的请求,Browser进程收到这个请求之后,再向GPU进程转发。GPU接收到创建Gpu Channel的请求后,就会创建一个UNIX Socket,并且将Server端的文件描述符封装在一个GpuChannel对象中,而将Client端的文件描述符返回给Browser进程,Browser进程再返回到Render进程,这样Render进程就可以创建一个Client端的Gpu Channel了。除了创建一个Client端的Gpu Channel,Render进程还会创建一个WebGrahpicsContext3DCommandBufferImpl对象,用来描述一个Client端的OpenGL上下文,这个OpenGL上下文与GPU进程里面的GLContext对象描述的OpenGL上下文是对应的。

最后我们再来看Render进程与Plugin进程之间的通信。Chromium支持两种类型的插件,一种是NPAPI插件,另一种是PPAPI插件。NPAPI插件是来自于Mozilla的一种插件机制,它被很多浏览器所支持,Chromium也不例外。不过由于运行在NPAPI插件中的代码不能利用完全利用Chromium的沙箱技术和其他安全防护技术,现在NPAPI插件已经不被支持。因此这里我们就只关注PPAPI插件机制。

Render进程在解析网页的过程中发现需要创建一个PPAPI插件实列时,就会通知Browser进程创建一个Plugin进程。当然,如果对应的Plugin进程已经存在,就会利用它,而不是再启动一个。Browser进程每启动一个Plugin进程,都会创建一个PpapiPluginProcessHost对象描述它。Plugin进程启动完成后,也会创建一个ChildProcess对象描述自己。这样,以后Browser进程和Plugin进程就可以通过PpapiPluginProcessHost对象和ChildProcess对象之间的Channel进行通信。但是Render进程和Plugin之间的通信需要另外一个Channel。因此,Browser进程会进一步请求Plugin进程创建另外一个Channel,用来在Render进程和Plugin进程之间进行通信。有了这个Channel之后,Render进程会创建一个HostDispatcher对象,而Plugin进程会创建一个PluginDispatcher对象,以后Render进程和Plugin进程之间的通信就通上述两个对象进行。

前面提到,Plugin进程有可能也需要渲染UI,因此,PPAPI插件机制提供了一个Graphics3D接口,PPAPI插件可以通过该接口与GPU进行通信。注意,Plugin进程和GPU进程之间的通信,不同于Render进程和GPU进程之间的通信,前者没有一个专门的Channel用来执行IPC通信。不过,Plugin进程却可以利用之前它已经与Render进程建立好的Channel进行通信。这意味着,Plugin进程和GPU进程之间的通信是要通过Render进程间接进行的。具体来说,就是Plugin进程首先要将OpenGL命令发送给Render进程,然后再由Render进程通过Gpu Channel发送给GPU进程执行。

在Chromium的运行过程中,进程之间需要发送很多IPC消息。不同类型的IPC消息会被不同的模块处理。为了能够快速地对这些IPC消息进行分发处理,Chromium提供了一套灵活的消息发分机制。这套分发机制规定每一个IPC消息都具有一个32位的Routing ID和一个也是32位的Type,其中,Type的高16位描述的是IPC消息类别,低16位没有特殊的意义。

基于IPC消息的类别信息,我们可以在IO线程中注册一系列的MessageFilter,每一个Filter都可以指定自己所支持的IPC消息类别。IO线程接收到一个IPC消息的时候,首先就会根据它的类别查找有没有注册相应的MessageFilter。如果有的话,就快速地在IO线程分发给它处理。

此外,我们还可以IO线程中注册一个Listener。当一个IPC消息没有相应的MessageFilter可以处理时,那么它接下来就会分发给上述注册的Listener处理。注意,这时候Listener处理代码运行在注册时的线程中。这个线程一般就是主线程。Listener首先根据IPC消息的Type进行分发给相应的Handler进行处理。如果没有相应的Handler可以处理,并且Listener支持注册Router,那么就会再根据IPC消息的Routing ID分发相应的Router进行处理。

最后,还有一点需要注意的是,在Android平台中,Chromium的Browser进程就是Android应用程序的主进程,它具有droid应用程序申请的所有权限,Render进程、GPU进程和Plugin进程是Android应用程序的Service进程。这些Service在AndroidManifest文件中被配置为运行在的孤立进程中,也就是它的android:isolatedProcess属性被设置为true。这类进程在启动的时候,将不会被赋予Android应用程序申请的权限,也就是它们运行在一个非常受限的进程中。这一点我们可以通过分析Android应用程序进程启动的过程源代码看到。

从前面Android应用程序进程启动过程的源代码分析一文可以知道,Android应用程序进程是由ActivityManagerService启动的。具体来说,在启动一个Service的时候,如果发现需要为它创建一个单独的进程时,就会调用ActivityManagerService类的以下成员函数startProcessLocked创建一个新的进程,如下所示:

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    ......

    private final void startProcessLocked(ProcessRecord app,
            String hostingType, String hostingNameStr) {
        startProcessLocked(app, hostingType, hostingNameStr, null /* abiOverride */,
                null /* entryPoint */, null /* entryPointArgs */);
    }

    ......
}

这个函数定义在文件frameworks/base/services/core/java/com/adroid/server/am/ActivityManagerService.java中。

ActivityManagerService类三个参数版本的成员函数startProcessLocked调用了另外一个重载版本的成员函数startProcessLocked,后者的实现如下所示:

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    ......

    private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
        ......

        try {
            ......

            int[] gids = null;
            ......

            if (!app.isolated) {
                int[] permGids = null;
                try {
                    ......
                    final PackageManager pm = mContext.getPackageManager();
                    permGids = pm.getPackageGids(app.info.packageName);

                    ......
                } catch (PackageManager.NameNotFoundException e) {
                    ......
                }

                ......

                if (permGids == null) {
                    gids = new int[2];
                } else {
                    gids = new int[permGids.length + 2];
                    System.arraycopy(permGids, 0, gids, 2, permGids.length);
                }
                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                gids[1] = UserHandle.getUserGid(UserHandle.getUserId(uid));
            }

            ......

            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);
            ......
        } catch (RuntimeException e) {
            ......
        }
    }

    ......
}

这个函数定义在文件frameworks/base/services/core/java/com/adroid/server/am/ActivityManagerService.java中。

从这里可以看到,只有当参数app描述的一个ProcessRecord对象的成员变量isolated等于false的时候,ActivityManagerService类的成员函数startProcessLocked才会请求PackageManagerService返回要启动的Service所属的Android应用程序申请的权限,也就是一系列GID。而当一个Service在AndroidManifest.xml中将属性android:isolatedProcess属性设置为true的时候,这里的参数app描述的ProcessRecord对象的成员变量isolated也是等于true,这时候就相当于是不给该Service所运行在的进程赋予任何权限,因此它就运行在一个沙箱中。

关于Chromium的多进程架构,我们就介绍到这里。在接下来的一系列文章,我们再结合源代码详细分细它的实现细节。具体来说,包括以下几个情景分析:

1. Render进程的启动过程分析;

2. IPC消息分发机制分析;

3. GPU进程的启动过程分析;

4. Plugin进程的启动过程分析;

这里我们有意略过Browser进程的启动过程分析,这是因为Browser进程实际上就是Android应用程序进程,但是会涉及到一些Chromium相关库的加载过程,而且有些Chromium相关库会由Zygote进程执行预加载处理,等到后面我们分析WebView的启动过程时,再详细分析Browser进程的启动过程。

同时,我们在分析Render进程的启动过程之后,并没有马上连贯分析GPU进程的启动过程,而是先分析一下前面描述过的IPC消息分发机制的具体实现,这是因为理解了Render进程的启动过程之后,我们就可以以它与Browser进程间的通信过程为情景,更好地理解Chromimum的IPC消息分发机制。

当分析完成上述四个情景之后,我们还有一个重要任务,那就是分析Render进程和Plugin进程是如何通过GPU进程进行硬件加速渲染UI的。硬件加速渲染是智能设备获得流畅UI的必要条件。可以说,没有硬件加速渲染的支持,设备UI动画要达到60fps,是非常困难的。Chromium使用硬件加速渲染的方式非常独特,具有以下特点:

1. Render进程和Plugin进程虽然调用了OpenGL的API,但是这些API仅仅是一个代理接口,也就是这些API调用仅仅是将对应的命令发送给GPU进程处理而已。

2. 有些OpenGL API,例如glBufferSubData,除了要发送对应的命令给GPU进程之外,还需要发送该命令关联的数据给GPU进程。这些数据往往很大的,这样就涉及到如何正确有效地传输它们给GPU进程。

3. GPU进程只有一个用来处理Client端发送过来的OpenGL命令的线程,但是该线程却要同时服务多个Client端,也就是要同时为Render进程、Plugin进程以及Browser进程服务,每一个Client端都相当于有一个OpenGL上下文,这将会涉及到如何为每一个OpenGL上下文进行调度的问题。

4. GPU进程同时服务的多个Client端,它们并不是相互孤立的,它们有时候存在一定的关联性。例如,Render进程渲染好的离屏UI,需要交给Browser进程合成,以便可以最终显示在屏幕中。也就是说,Browser进程需要等待Render进程渲染完成之后,才可以进行合成,否则就会得到不完整的UI。对于GPU进程来说,涉及到的问题就是如何在两个不同的OpengGL上下文中进行同步。

对于上面提到的这些特点,在完成了Chromium的多进程架构分析之后,我们再通过另外一个系列的文章进行详细分析,敬请关注!更多的信息,也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-05 16:47:48

Chromium多进程架构简要介绍和学习计划的相关文章

Chromium网页输入事件处理机制简要介绍和学习计划

用户在浏览网页的时候,需要与网页进行交互,常用的操作如滑动.捏合网页,以及点击网页中的链接等.这些交互操作也称为用户输入事件,浏览器需要对它们作出迅速的响应,例如及时更新网页内容或者打开新的网页等.浏览器能够对用户输入事件作出迅速的响应是至关重要的,因为这关乎到用户浏览网页时的体验,尤其是在用户滑动和捏合网页时.本文接下来就简要介绍Chromium对用户输入事件的处理机制,以及制定后续的学习计划. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在任何一个

Chromium网页渲染机制简要介绍和学习计划

作为一个浏览器,快速地将网页渲染出来是最重要的工作.Chromium为了做到这一点,费尽了心机,做了大量优化工作.这些优化工作是卓有成效的,代表了当今最先进的网页渲染技术.值得一提的是,这些渲染技术不仅适用于网页渲染,也可以应用在原生系统的UI渲染上.例如,在Android系统上,我们就可以看到两者在渲染技术上的相似之处.本文接下来就对Chromium的网页渲染机制进行简要介绍,并且制定学习计划. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! Chrom

Android WebView简要介绍和学习计划

我们通常会在App的UI中嵌入WebView,用来实现某些功能的动态更新.在4.4版本之前,Android WebView基于WebKit实现.不过,在4.4版本之后,Android WebView就换成基于Chromium的实现了.基于Chromium实现,使得WebView可以更快更流畅地显示网页.本文接下来就介绍Android WebView基于Chromium的实现原理,以及制定学习计划. 通过前面几个系列文章的学习,我们知道,Chromium的实现是相当复杂的.这种复杂可以体现在编译出

0-Android编译系统简要介绍和学习计划

Android编译系统简要介绍和学习计划 来源:http://blog.csdn.net/luoshengyang/article/details/18466779 导语: 在Android源码环境中,我们开发好一个模块后,再写一个Android.mk文件,就可通过m/mm/mmm/make等命令进行编译.此外,通过make命令还可制作各种系统镜像文件,例如system.img.boot.img和recovery.img等.这一切都得益于Android编译系统,它为我们处理了各种依赖关系,以及提

Android运行时ART简要介绍和学习计划

Android在4.4就已推出新运行时ART,准备替代用了有些时日的Dalvik.不过当时尚属测试版,主角仍是Dalvik. 直到今年的Google I/O大会,ART才正式取代Dalvik.这个消息在科技界引起不小轰动,也吸引不少技术人员对它的"技术分析".可惜这些"技术分析"不过是引用了官方的数据和图表而已.这一系列文章将对ART进行真正的技术分析.老规矩,分析前先进行简要介绍和制定学习计划. 老罗的新浪微博:http://weibo.com/shengyang

Android应用程序的Activity启动过程简要介绍和学习计划

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6685853 在Android系统中,Activity和Service是应用程序的核心组件,它们以松藕合的方式组合在一起构成了一个完整的应用程序,这得益 于应用程序框架层提供了一套完整的机制来协助应用程序启动这些Activity和Service,以及提供Binder机制帮助它们相互间进行通信.在前 面的文章Android进程间通信(IPC)机制B

Android窗口管理服务WindowManagerService的简要介绍和学习计划

在前一个系列文章中,我们从个体的角度来分析了Android应用程序窗口的实现框架.事实上,如果我们从整体的角度来看,Android应用程序窗口的 实现要更复杂,因为它们的类型和作用不同,且会相互影响.在Android系统中,对系统中的所有窗口进行管理是窗口管理服务 WindowManagerService的职责.在本文中,我们就将简要介绍WindowManagerService的职能以及制定学习计划. 我们知道,在Android系统中,同一时刻,只有一个Activity窗口是激活的,但是,对于W

Android应用程序组件Content Provider简要介绍和学习计划

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6946067 在Android系统中,Content Provider作为应用程序四大组件之一,它起到在应用程序之间共享数据的作用,同时,它还是标准的数据访问接口.前面的一系列文章已经分析过 Android应用程序的其它三大组件(Activity.Service和Broadcast Receiver)了,本文将简要介绍Content Provid

SEAndroid安全机制简要介绍和学习计划

与iOS相比,Android最被人诟病的是其流畅性和安全性.然而,从4.0开始,Android不遗余力地改善其流畅性.特别是在即将发布的L版本中,用ART替换了Dalvik,相信会越来越流畅.至于安全性,Android也没有遗忘.从4.3开始,Android引入了一套基于SELinux的安全机制,称为SEAndroid,来加强系统安全性.接下来我们就对SEAndroid进行简要介绍和制定学习计划. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在介绍SE