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 ActivityManagerNativeimplements 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 ActivityManagerNativeimplements 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。

Chromium多进程架构简要介绍和学习计划相关推荐

  1. Chromium视频标签video简要介绍和学习计划

    随着互联网的发展,在网页上观看视频变得越来越流行,尤其是泛娱乐(手机直播)大行其道的今天.在HTML5之前,在网页上播放视频需要插件支持,例如Flash插件.有了HTML5之后,标签<video ...

  2. Android进程间通信(IPC)机制Binder简要介绍和学习计划

    在Android系统中,每一个应用程序都是由一些Activity和Service组成的,这些Activity和Service有可能运行在同一个进程中,也有可能运行在不同的进程中.那么,不在同一个进程的 ...

  3. Android WebView简要介绍和学习计划

    我们通常会在App的UI中嵌入WebView,用来实现某些功能的动态更新.在4.4版本之前,Android WebView基于WebKit实现.不过,在4.4版本之后,Android WebView就 ...

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

    在Android系统中,Content Provider作为应用程序四大组件之一,它起到在应用程序之间共享数据的作用,同时,它还是标准的数据访问接口.前面的一系列文章已经分析过Android应用程序的 ...

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

    在前一个系列文章中,我们从个体的角度来分析了Android应用程序窗口的实现框架.事实上,如果我们从整体的角度来看,Android应用程序窗口的实现要更复杂,因为它们的类型和作用不同,且会相互影响.在 ...

  6. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划

    在Android系统中,提供了独特的匿名共享内存子系统Ashmem(Anonymous Shared Memory),它以驱动程序的形式实现在内核空间中.它有两个特点,一是能够辅助内存管理系统来有效地 ...

  7. 老郭的《Dalvik虚拟机垃圾收集机制简要介绍和学习计划》

    伴随着"Dalvik is dead,long live Dalvik"这行AOSP代码提交日志,在Android5.0中,ART运行时取代了Dalvik虚拟机.虽然Dalvik虚 ...

  8. Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

    前面我们从Android应用程序与SurfaceFlinger服务的关系出发,从侧面简单学习了SurfaceFlinger服务.有了这些预备知识之后,我们就可以从正面来分析SurfaceFlinger ...

  9. Chromium插件(Plugin)机制简要介绍和学习计划

    在Chromium中,除了可以使用Extension增强浏览器功能,还可以使用Plugin.两者最大区别是前者用JS开发,后者用C/C++开发.这意味着Plugin以Native Code运行,在性能 ...

最新文章

  1. linux 内核 netfilter 网络过滤模块 (2)-conntrack
  2. 实现单机五子棋,难吗?
  3. 【Python】利用 pytesseract 识别图片中的数字
  4. 微信小程序的setData
  5. [vue] 组件和插件有什么区别?
  6. c语言玫瑰花图形程序,一个玫瑰花的程序
  7. 黑客动画吧-黑客闯关之古墓探秘攻略
  8. Elasticsearch7.5配置IK中文分词器+拼音分词
  9. HDU 4699 题解
  10. 深度学习之图像分类(二十五)-- S2MLPv2 网络详解
  11. Oracle中按天、自然周、月、季、年周期统计
  12. Android 从服务器获取信息 并显示 (包含服务器端代码)
  13. ubuntu 12.04下安装adobe flash
  14. 【088】中国大学MOOC-高教社大学课程学习平台
  15. 连续函数的运算与初等函数的连续性——“高等数学”
  16. OSChina 周六乱弹 ——你知道妹子喜欢什么了么?
  17. 如何组装一个注册中心
  18. 翻译:确认中的处理控制(CO1P)
  19. C语言数组fun函数逆置数组元素,C语言
  20. 矩阵和图结构(图论) 最短路径问题 学习笔记

热门文章

  1. Python基础(九)Python3 面向对象
  2. 小米wifi信号测试软件,小米wifi6路由器评测
  3. java显示proe文件_PROE配置自定义大全(绝对值得你看)
  4. 1651989498494
  5. 没有管理经验,怎么当测试负责人?
  6. Win+ubuntu单硬盘双系统安装(雷神新911机型)
  7. windows下使用waveOut播放音频pcm
  8. LRU算法 Java
  9. ChatGPT 公司 CEO —— Altman 给创业公司的建议
  10. 易语言对x64进程卸载DLL技术(可卸载64位进程里DLL)