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

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

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

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       一个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应用程序的主进程,它具有Android应用程序申请的所有权限,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进程间的通信过程为情景,更好地理解Chromium的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

  • 13
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
### 回答1: Chromium进程启动顺序主要包括:初始化过程、处理消息过程、绘制过程和渲染过程。初始化过程中,Chromium会载入配置文件、建立内核进程、建立渲染进程等。然后进入处理消息过程,Chromium会接收来自浏览器内核和渲染进程的消息并处理。接着开始绘制过程,Chromium会将网页内容绘制到屏幕上,而最后一步是渲染过程,在这个过程中,Chromium会将绘制好的内容展示在浏览器中。 ### 回答2: Chromium是一种开放源代码的网页浏览器,其进程启动顺序可以简单分为以下几个步骤: 1. 主进程启动:当用户双击打开Chromium浏览器时,系统会首先启动主进程。主进程负责管理其他相关进程,如浏览器标签、插件管理、窗口调度等。 2. 渲染进程启动:在主进程启动之后,当用户打开一个新的网页或者标签时,主进程会启动一个渲染进程来处理该网页的渲染和交互。 3. GPU进程启动:在有需要的情况下,主进程还会启动GPU进程,用于处理与图形相关的任务,如3D渲染、视频播放等。 4. 插件进程启动:当用户访问需要插件支持的网页时(如Flash),主进程会启动相应的插件进程,用于加载和运行插件,以提供相应的功能和特性。 总的来说,Chromium进程启动顺序可以概括为主进程启动,然后根据用户的操作和需要,逐渐启动渲染进程GPU进程和插件进程等。这种进程启动方式可以提高浏览器的稳定性和安全性,同时也能更好地利用系统资源。 ### 回答3: Chromium 进程的启动顺序可以简要概括为以下几个步骤: 1. 启动浏览器进程:当用户双击 Chromium 可执行文件或创建一个新的浏览器窗口时,浏览器进程会被启动。该进程负责管理整个浏览器的生命周期和资源分配。 2. 建立渲染进程:在浏览器进程中,每个标签页或独立窗口将启动一个独立的渲染进程。渲染进程负责处理网页的渲染、脚本执行和用户交互等任务。为了提高安全性和稳定性,每个渲染进程都被隔离在自己的沙盒环境中。 3. 创建网络进程:网络进程负责处理网络请求,包括下载网页内容、JavaScript、CSS、图像等资源。当浏览器需要进行网络通信时,这个进程会被启动。 4. 启动 GPU 进程GPU 进程是用于加速图形渲染的,它与浏览器进程和渲染进程进行通信,处理与图像相关的任务。 5. 建立插件进程:如果页面中包含 Flash 或其他插件,每个插件都将在单独的插件进程中运行。这种隔离可以防止插件的问题影响到整个浏览器的稳定性。 总结起来,Chromium 进程的启动顺序是先启动浏览器进程,然后根据需要分别建立渲染进程、网络进程GPU 进程和插件进程。这种多进程架构的设计使得 Chromium 在安全性和稳定性方面表现出色,并能够提供更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值