Chromium的GPU进程启动过程分析Chromium除了有Browser进程和Render进程,还有GPU进程。GPU进程负责Chromium的GPU操作,例如Render进程通过GPU进程离屏渲染网页,Browser进程也是通过GPU进程将离屏渲染好的网页显示在屏幕上。Chromium之所以将GPU操作运行在独立进程中,是考虑到稳定性问题。毕竟GPU操作是硬件相关操作,硬件的差异性会引发一定的不稳性。本文分析GPU进程的启动过程。GPU进程由Browser进程负责启动,它的启动过程与Render进程的启动过程是类似的,因此在阅读本文之前,最好先阅读一文。不过,GPU进程启动之后,Browser进程会与它建立两个IPC通道。一个IPC通道用来传输普通的IPC消息,另一个IPC通道专门用来执行GPU操作,称为GPU通道。类似地,Render进程需要执行GPU操作时,也会通过Browser进程与GPU进程建立一个专门用来执行GPU操作的IPC通道。Render进程之所以要通过Browser进程间接地与GPU进程建立GPU通道,是因为GPU进程是由Browser进程启动的,Render进程对它一无所知。以上描述的Browser进程、Render进程和GPU进程的关系可以通过图1概括,如下所示:在图1中,Browser进程与Render进程的IPC通道的建立过程可以参考前面一文,本文只分析以下三部分内容:1.Browser进程与GPU进程的IPC通道的建立过程。2.Browser进程与GPU进程的GPU通道的建立过程。3.Render进程与GPU进程的GPU通道的建立过程。Browser进程通过一个GpuProcessHost对象描述由它启动的GPU进程。GPU进程启动起来之后,会创建一个GpuProcess对象用来与Browser进程进行IPC。接下来,Browser进程中的GpuProcessHost对象会通过已经建立起来的IPC通道请求GPU进程中创建一个GPU通道,以便以后可以执行GPU操作。Render进程需要通过GPU渲染网页的时候,会通过之前与Browser进程建立的IPC通道请求Browser进程为它创建一个GPU通道,并且将该GPU通道封装在一个WebGraphicsContext3DCommandBufferImpl对象,以后就可以通过该WebGraphicsContext3DCommandBufferImpl对象向GPU进程请求执行GPU操作了。GPU进程在启动的过程中,也会像Browser进程和Render进程一样,启动一个IO线程,专门用来执行IPC。以后每当GPU进程通过上述IPC通道接收到一个创建GPU通道的请求的时候,都会在内部创建一个OpenGL上下文。这个OpenGL上下文通过一个GLContext对象描述。这样在GPU进程中,就会存在若干个OpenGL上下文。这些OpenGL上下文都运行在同一个线程中,这个线程称为GPUChildThread。这样就会涉及到一个OpenGL上下文调度问题,即每当GPU进程接收到一个GPU操作请求时,都要先切换到请求的GPU操作所在的OpenGL上下文,然后才能执行请求的GPU操作。关于GPU进程的OpenGL上下文调度问题,我们在下一系列的文章中再详细分析。接下来,我们就先分析Browser进程启动GPU进程的过程。这个过程主要是涉及到Browser进程和GPU进程的IPC通道的建立过程。在前面一文中,我们提到,Browser进程,也就是Chromium应用程序的主进程,在启动的时候,会调用BrowserMainLoop类的成员函数CreateStartupTasks。BrowserMainLoop类的成员函数CreateStartupTasks会请求启动一个GPU进程,相关的代码如下所示:[cpp]viewplaincopyvoidBrowserMainLoop::CreateStartupTasks(){......//Firsttimethrough,wereallywanttocreateallthetasksif(!startup_task_runner_.get()){startup_task_runner_=make_scoped_ptr(newStartupTaskRunner(base::Bind(&BrowserStartupComplete),base::MessageLoop::current()-message_loop_proxy()));......StartupTaskbrowser_thread_started=base::Bind(&BrowserMainLoop::BrowserThreadsStarted,base::Unretained(this));startup_task_runner_-AddTask(browser_thread_started);......if(BrowserMayStartAsynchronously()){startup_task_runner_-StartRunningTasksAsync();}}if(!BrowserMayStartAsynchronously()){......startup_task_runner_-RunAllTasksNow();}}这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。BrowserMainLoop类的成员函数CreateStartupTasks首先是会创建一个StartupTaskRunner对象,并且保存在成员变量startup_task_runner_中。这个StartupTaskRunner对象封装了当前线程的一个消息循环,因此通过它可以向当前线程的消息队列发送消息。当前线程即为Browser进程的主线程,因此有了这个StartupTaskRunner对象之后,接下来可以向其主线程的消息队列发送消息。BrowserMainLoop类的成员函数CreateStartupTasks接下来创建了一个StartupTask,这个StartupTask绑定的函数为BrowserMainLoop类的成员函数BrowserThreadsStarted,用来执行一个Browser线程启动完毕任务,并且会保存在前面创建的一个StartupTaskRunner对象的内部等待执行。最后,取决于Browser进程使用同步还是异步方式启动,BrowserMainLoop类的成员函数CreateStartupTasks使用不同的方式来执行保存在成员变量startup_task_runner_指向的一个StartupTaskRunner对象中的StartupTask:1.如果是使用同步方式启动,那么就调用上述StartupTaskRunner对象的成员函数RunAllTasksNow立即执行保存在它里面的各个StartupTask对象所描述的任务。2.如果是使用异步方式启动,那么就调用上述StartupTaskRunner对象的成员函数StartRunningTasksAsync向主线程的消息队列发送一个消息,当该消息被处理时,再执行保存在上述StartupTaskRunner对象里面的各个StartupTask对象所描述的任务。无论是同步方式,还是异步方式,最终都会在主线程中调用BrowserMainLoop类的成员函数BrowserThreadsStarted,与GPU进程启动相关的代码如下所示:[cpp]viewplaincopyintBrowserMainLoop::BrowserThreadsStarted(){......boolinitialize_gpu_data_manager=true;#ifdefined(OS_ANDROID)//OnAndroid,GLSurface::InitializeOneOff()mustbecalledbeforeinitalizing//theGpuDataManagerImplasitusestheGLbindings.crbug.com/326295if(!gfx::GLSurface::InitializeOneOff()){......initialize_gpu_data_manager=false;}#endifif(initialize_gpu_data_manager)GpuDataManagerImpl::GetInstance()-Initialize();boolalways_uses_gpu=true;boolestablished_gpu_channel=false;#ifdefined(USE_AURA)||defined(OS_MACOSX)if(ShouldInitializeBrowserGpuChannelAndTransportSurface()){established_gpu_channel=true;if(!GpuDataManagerImpl::GetInstance()-CanUseGpuBrowserCompositor()){established_gpu_channel=always_uses_gpu=false;}BrowserGpuChannelHostFactory::Initialize(established_gpu_channel);......}#elifdefined(OS_ANDROID)established_gpu_channel=true;BrowserGpuChannelHostFactory::Initialize(established_gpu_channel);#endif......}这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。在Android平台上,BrowserMainLoop类的成员函数BrowserThreadsStarted首先调用gfx::GLSurface类的静态成员函数InitializeOneOff在当进程中加载合适的OpenGL库,以及创建一个EGLDisplay。这样做有两个原因,一是后面调用GpuDataManagerImpl类的成员函数Initialize时,在Android平台上需要通过加载的OpenGL库来获取GPU信息,二是Android平台的Chromium实际上并没有独立的GPU进程,而是在Browser进程中创建一个GPU线程,不过这个GPU线程起到的作用与GPU进程是一样的。上述第二个原因要求Browser进程要做一些GPU相关的初始化工作,即加载合适的OpenGL库,以及创建一个EGLDisplay,以后创建OpenGL上下文时需要使用到这个EGLDisplay。对于独立GPU进程的情况,上述的GPU初始化也是需要做的。后面我们就会看到,GPU进程在启动的时候,会调用gfx::GLSurface类的静态成员函数InitializeOneOff。只有在gfx::GLSurface类的静态成员函数InitializeOneOff的返回值为true,即在当进程中成功加载了合适的OpenGL库之后,BrowserMainLoop类的成员函数Initialize才会被调用,负责检查当前设备使用的GPU及其相关的驱动是否在黑名单中。如果在黑名单中,那么Chromium就不会采用GPU对网页进行硬件加速渲染。这是由于不是所有的GPU都能够很好地支持Chromium进行硬件加速渲染,因此就需要设置一个黑名单,避免在渲染网页的过程中出现错误。一旦不能使用GPU对网页进行硬件加速渲染,那么Chromium就会退而求其次,使用CPU进行渲染。如果Chromium在编译时定义了宏USE_AURA,那么