配置多进程的情况下,Chromium的网页渲染和JS执行在一个单独的进程中进行。这个进程称为Render进程,由Browser进程启动。在Android平台中,Browser进程就是Android应用程序的主进程,而Render进程就是Android应用程序的Service进程,它们通过UNIX Socket进行通信。本文就详细分析Chromium的Browser进程启动Render进程的过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
Render进程启动完成之后,将与Browser进程建立以下的IPC通道,如图1所示:
图1 Browser进程与Render进程的IPC通信过程
在Browser进程中,一个RenderProcessHost对象用来描述它所启动的一个Render进程,而一个RenderViewHost对象用来描述运行在一个Render进程中的一个网页,我们可以将它理解为浏览器中的一个TAB。这两个对象在Render进程中都有一个对等体,它们分别是一个RenderProcess对象和一个RenderView对象。这里说的对等体,就是它们是Browser进程和Render进程进行IPC的两个端点,类似于TCP/IP网络堆栈中的层对层通信。例如,RenderViewHost和RenderView之间的IPC通信,就代表了Browser进程请求Render进程加载、更新和渲染一个网页。
RenderViewHost和RenderView之间的IPC通信,实际上是通过一个UNIX Socket进行的。这个UNIX Socket的两端分别被封装为两个Channel对象,分别运行在Browser进程和Render进程各自的IO线程中。这样RenderViewHost和RenderView之间的IPC通信就要通过上述的两个Channel对象进行。
在Browser进程中,由于RenderViewHost对象运行在主线程中,因此当它需要请求运行在IO线程中的Channel对象执行一次IPC时,就要通过IO线程的消息循环进行。这符合我们在前面Chromium多线程模型设计和实现分析一文中提到的Chromium的多线程设计哲学:每一个对象都只运行在一个线程中,对象之间需要通信时就通过消息循环进行。同样,在Render进程中,由于RenderView对象运行在Render线程中,因此当Render进程的Channel对象接收一个来自Browser进程的RenderViewHost对象的IPC消息时,需要通过Render线程的消息循环将IPC消息转发给RenderView进行处理。从RenderView对象到RenderViewHost对象的通信过程也是类似的。
我们分析Render进程的启动过程,目的就是为了能够理解Browser进程和Render进程是如何建立IPC通道的,因为以后Browser进程与Render进程的交互和协作,都是通过这个IPC通道进行的。为此,我们在分析Render进程的启动过程中,将着重分析图1涉及到的各个对象的初始过程。
我们注意到,运行在Browser进程中的通信对象是以Host结尾的,而在运行在Render进程中的对等通信对象,则是没有Host结尾的,因此当我们Chromium的源代码中看到一个对象的类型时,就可以推断出该对象运行在哪个进程中。
事实上,RenderProcessHost、RenderViewHost、RenderProcess和RenderView仅仅是定义了一个抽象接口,真正用来执行IPC通信的对象,是实现了上述抽象接口的一个实现者对象,这些实现者对象的类型以Impl结尾,因此,RenderProcessHost、RenderViewHost、RenderProcess和RenderView对应的实现者对象的类型就分别为RenderProcessHostImpl、RenderViewHostImpl、RenderProcessImpl和RenderViewImpl。
为了更好地理解Render进程的启动过程,我们有必要了解上述Impl对象的类关系图。
RenderViewHostImpl对象的类关系图如下所示:
图2 RenderViewHostImpl类关系图
RenderViewHostImpl类多重继承了RenderViewHost类和RenderWidgetHostImpl类,后面这两个类又有一个共同的虚基类RenderWidgetHost,该虚基类又实现了一个Sender接口,该接口定义了一个重要的成员函数Send,用来执行IPC通信。
RenderWidgetHostImpl类还实现了一个Listener接口,该接口定义了两个重要的成员函数OnMessageReceived和OnChannelConnected。前者用来接收IPC消息并且进行分发,后者用来在IPC通道建立时执行一些初始化工作。
实际上,当RenderViewHostImpl类需要发起一次IPC时,它是通过父类RenderWidgetHostImpl的成员变量process_指向的一个RenderProcessHost接口进行的。该RenderProcessHost接口指向的实际上是一个RenderProcessHostImpl对象,它的类关系图如图3所示:
图3 RenderProcessHostImpl类关系图
RenderProcessHostImpl类实现了RenderProcessHost接口,后者又多重继承了Sender和Listener类。
RenderProcessHostImpl类有一个成员变量channel_,它指向了一个ChannelProxy对象。ChannelProxy类实现了Sender接口,RenderProcessHostImpl类就是通过它来发送IPC消息的。
ChannelProxy类有一个成员变量context_,它指向了一个ChannelProxy::Context对象。ChannelProxy::Context类实现了Listener接口,因此它可以用来接收IPC消息。ChannelProxy类就是通过ChannelProxy::Context类来发送和接收IPC消息的。
ChannelProxy::Context类有一个类型为Channel的成员变量channel_,它指向的实际上是一个ChannelPosix对象。ChannelPosix类继承了Channel类,后者又实现了Sender接口。ChannelProxy::Context类就是通过ChannelPosix类发送IPC消息的。
绕了一圈,总结来说,就是RenderProcessHostImpl类是分别通过ChannelPosix类和ChannelProxy::Context类来发送和接收IPC消息的。
上面分析的RenderViewHostImpl对象和RenderProcessHostImpl对象都是运行在Browser进程的,接下来要分析的RenderViewImpl类和RenderProcessImpl类是运行在Render进程的。
RenderViewImpl对象的类关系图如下所示:
图4 RenderViewImpl类关系图
RenderViewImpl类多重继承了RenderView类和RenderWidget类。RenderView类实现了Sender接口。RenderWidget类也实现了Sender接口,同时也实现了Listener接口,因此它可以用来发送和接收IPC消息。
RenderWidget类实现了接口Sender的成员函数Send,RenderViewImpl类就是通过它来发送IPC消息的。RenderWidget类的成员函数Send又是通过一个用来描述Render线程的RenderThreadImpl对象来发送IPC类的。这个RenderThreadImpl对象可以通过调用RenderThread类的静态成员函数Get获得。
RenderThreadImpl对象的类关系图如下所示:
图5 RenderThreadImpl类关系图
RenderThreadImpl类多重继承了RenderThread类和ChildThread类。RenderThread类实现了Sender接口。ChildThread类也实现Sender接口,同时也实现了Listener接口,因此它可以用来发送和接收IPC消息。
ChildThread类有一个成员变量channel_,它指向了一个SyncChannel对象。SyncChannel类继承了上面提到的ChannelProxy类,因此,ChildThread类通过其成员变量channel_指向的SyncChannel对象可以发送IPC消息。
从上面的分析又可以知道,ChannelProxy类最终是通过ChannelPosix类发送IPC消息的,因此总结来说,就是RenderThreadImpl是通过ChannelPosix类发送IPC消息的。
接下来我们再来看RenderProcessImpl对象的类关系图,如下所示:
图6 RenderProcessImpl类关系图
RenderProcessImpl类继承了RenderProcess类,RenderProcess类又继承了ChildProcess类。ChildProcess类有一个成员变量io_thread_,它指向了一个Thread对象。该Thread对象描述的就是Render进程的IO线程。
有了上面的基础知识之后,接下来我们开始分析Render进程的启动过程。我们将Render进程的启动过程划分为两部分。第一部分是在Browser进程中执行的,它主要负责创建一个UNIX Socket,并且将该UNIX Socket的Client端描述符传递给接下来要创建的Render进程。第二部分是在Render进程中执行的,它负责执行一系列的初始化工作,其中之一就是将Browser进程传递过来的UNIX Socket的Client端描述符封装在一个Channel对象中,以便以后可以通过它来和Browser进程执行IPC。
Render进程启动过程的第一部分子过程如下所示:
图7 Render进程启动的第一部分子过程
图7列出的仅仅是一些核心过程,接下来我们通过代码来分析这些核心过程。
我们首先了解什么情况下Browser进程会启动一个Render进程。当我们在Chromium的地址栏输入一个网址,然后进行加载的时候,Browser进程经过判断,发现需要在一个新的Render进程中渲染该网址的内容时,就会创建一个RenderViewHostImpl对象,并且调用它的成员函数CreateRenderView触发启动一个新的Render进程。后面我们分析WebView加载一个URL的时候,就会看到触发创建RenderViewHostImpl对象的流程。
RenderViewHostImpl对象的创建过程,即RenderViewHostImpl类的构造函数的实现如下所示:
- RenderViewHostImpl::RenderViewHostImpl(
- SiteInstance* instance,
- RenderViewHostDelegate* delegate,
- RenderWidgetHostDelegate* widget_delegate,
- int routing_id,
- int main_frame_routing_id,
- bool swapped_out,
- bool hidden)
- : RenderWidgetHostImpl(widget_delegate,
- instance->GetProcess(),
- routing_id,
- hidden),
- ...... {
- ......
- }
这里我们主要关注类型为SiteInstance的参数instance,它指向的实际上是一个SiteInstanceImpl对象,用来描述Chromium当前加载的一个网站实例。RenderViewHostImpl类的构造函数调用该SiteInstanceImpl对象的成员函数GetProcess获得一个RenderProcessHostImpl对象,如下所示:
- RenderProcessHost* SiteInstanceImpl::GetProcess() {
- ......
- // Create a new process if ours went away or was reused.
- if (!process_) {
- BrowserContext* browser_context = browsing_instance_->browser_context();
- // If we should use process-per-site mode (either in general or for the
- // given site), then look for an existing RenderProcessHost for the site.
- bool use_process_per_site = has_site_ &&
- RenderProcessHost::ShouldUseProcessPerSite(browser_context, site_);
- if (use_process_per_site) {
- process_ = RenderProcessHostImpl::GetProcessHostForSite(browser_context,
- site_);
- }
- // If not (or if none found), see if we should reuse an existing process.
- if (!process_ && RenderProcessHostImpl::ShouldTryToUseExistingProcessHost(
- browser_context, site_)) {
- process_ = RenderProcessHostImpl::GetExistingProcessHost(browser_context,
- site_);
- }
- // Otherwise (or if that fails), create a new one.
- if (!process_) {
- if (g_render_process_host_factory_) {
- process_ = g_render_process_host_factory_->CreateRenderProcessHost(
- browser_context, this);
- } else {
- StoragePartitionImpl* partition =
- static_cast<StoragePartitionImpl*>(
- BrowserContext::GetStoragePartition(browser_context, this));
- process_ = new RenderProcessHostImpl(browser_context,
- partition,
- site_.SchemeIs(kGuestScheme));
- }
- }
- ......
- }
- ......
- return process_;
- }
SiteInstanceImpl对象的成员变量process_是一个RenderProcessHost指针,当它的值等于NULL的时候,就表示Chromium还没有为当前正在处理的一个SiteInstanceImpl对象创建过Render进程,这时候就需要创建一个RenderProcessHostImpl对象,并且保存在成员变量process_中,以及返回给调用者,以便调用者接下来可以通过它启动一个Render进程。另一方面,如果SiteInstanceImpl对象的成员变量process_已经指向了一个RenderProcessHostImpl对象,那么就直接将该RenderProcessHostImpl对象返回给调用者即可。
注意上述RenderProcessHostImpl对象的创建过程:
1. 如果Chromium启动时,指定了同一个网站的所有网页都在同一个Render进程中加载,即本地变量use_process_per_site的值等于true,那么这时候SiteInstanceImpl类的成员函数GetProcess就会先调用RenderProcessHostImpl类的静态函数GetProcessHostForSite检查之前是否已经为当前正在处理的SiteInstanceImpl对象描述的网站创建过Render进程。如果已经创建过,那么就可以获得一个对应的RenderProcessHostImpl对象。
2. 如果按照上面的方法找不到一个相应的RenderProcessHostImpl对象,本来就应该要创建一个新的Render进程了,也就是要创建一个新的RenderProcessHostImpl对象了。但是由于当前创建的Render进程已经超出预设的最大数量了,这时候就要复用前面已经启动的Render进程,即使这个Render进程加载的是另一个网站的内容。
3. 如果通过前面两步仍然找不到一个对应的RenderProcessHostImpl对象,这时候就真的是需要创建一个RenderProcessHostImpl对象了。取决于SiteInstanceImpl类的静态成员变量g_render_process_host_factory_是否被设置,创建一个新的RenderProcessHostImpl对象的方式有所不同。如果该静态成员变量被设置了指向一个RenderProcessHostFactory对象,那么就调用该RenderProcessHostFactory对象的成员函数CreateRenderProcessHost创建一个从RenderProcessHost类继承下来的子类对象。否则的话,就直接创建一个RenderProcessHostImpl对象。
这一步执行完成后,回到RenderViewHostImpl类的构造函数中,从这里返回的RenderProcessHostImpl对象用来初始化RenderViewHostImpl类的父类RenderWidgetHostImpl,如下所示:
- RenderWidgetHostImpl::RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
- RenderProcessHost* process,
- int routing_id,
- bool hidden)
- : ......,
- process_(process),
- ...... {
- ......
- }
参数process指向的RenderProcessHostImpl对象保存在RenderWidgetHostImpl类的成员变量process_中,以后就可以通过RenderWidgetHostImpl类的成员函数GetProcess获得该RenderProcessHostImpl对象,如下所示:
- RenderProcessHost* RenderWidgetHostImpl::GetProcess() const {
- return process_;
- }
有了RenderProcessHostImpl之后,接下来我们就开始分析RenderViewHostImpl类的成员函数CreateRenderView创建一个新的Render进程的过程了,如下所示:
- bool RenderViewHostImpl::CreateRenderView(
- const base::string16& frame_name,
- int opener_route_id,
- int proxy_route_id,
- int32 max_page_id,
- bool window_was_created_with_opener) {
- ......
- if (!GetProcess()->Init())
- return false;
- ......
- }
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。
RenderViewHostImpl类的成员函数CreateRenderView首先调用从父类RenderWidgetHostImpl继承下来的成员函数GetProcess获得一个RenderProcessHostImpl对象,接着再调用该RenderProcessHostImpl对象的成员函数Init检查是否需要为当前加载的网页创建一个新的Render进程。
RenderProcessHostImpl类的成员函数Init的实现如下所示:
- bool RenderProcessHostImpl::Init() {
- // calling Init() more than once does nothing, this makes it more convenient
- // for the view host which may not be sure in some cases
- if (channel_)
- return true;
- ......
- // Setup the IPC channel.
- const std::string channel_id =
- IPC::Channel::GenerateVerifiedChannelID(std::string());
- channel_ = IPC::ChannelProxy::Create(
- channel_id,
- IPC::Channel::MODE_SERVER,
- this,
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get());
- ......
- CreateMessageFilters();
- ......
- if (run_renderer_in_process()) {
- ......
- in_process_renderer_.reset(g_renderer_main_thread_factory(channel_id));
- base::Thread::Options options;
- ......
- options.message_loop_type = base::MessageLoop::TYPE_DEFAULT;
- in_process_renderer_->StartWithOptions(options);
- g_in_process_thread = in_process_renderer_->message_loop();
- ......
- } else {
- ......
- CommandLine* cmd_line = new CommandLine(renderer_path);
- ......
- AppendRendererCommandLine(cmd_line);
- cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
- ......
- child_process_launcher_.reset(new ChildProcessLauncher(
- new RendererSandboxedProcessLauncherDelegate(channel_.get()),
- cmd_line,
- GetID(),
- this));
- ......
- }
- return true;
- }
RenderProcessHostImpl类有一个类型为scoped_ptr<IPC::ChannelProxy>成员变量channel_,当它引用了一个IPC::ChannelProxy对象的时候,就表明已经为当前要加载的网而创建过Render进程了,因此在这种情况下,就无需要往前执行了。
我们假设到目前为止,还没有为当前要加载的网页创建过Render进程。接下来RenderProcessHostImpl类的成员函数Init就会做以下四件事情:
1. 先调用IPC::Channel类的静态成员函数GenerateVerifiedChannelID生成一个接下来用于创建UNIX Socket的名字,接着再以该名字为参数,调用IPC::ChannelProxy类的静态成员函数Create创建一个用于执行IPC的Channel,该Channel就保存在RenderProcessHostImpl类的成员变量channel_中。
2. 调用RenderProcessHostImpl类的成员函数CreateMessageFilters创建一系列的Message Filter,用来过滤IPC消息。
3. 如果所有网页都在Browser进程中加载,即不单独创建Render进程来加载网页,那么这时候调用父类RenderProcessHost的静态成员函数run_renderer_in_process的返回值就等于true。在这种情况下,就会通过在本进程(即Browser进程)创建一个新的线程来渲染网页。这个线程由RenderProcessHostImpl类的静态成员变量g_renderer_main_thread_factory描述的一个函数创建,它的类型为InProcessRendererThread。InProcessRendererThread类继承了base::Thread类,从前面Chromium多线程模型设计和实现分析一文可以知道,当调用它的成员函数StartWithOptions的时候,新的线程就会运行起来。这时候如果我们再调用它的成员函数message_loop,就可以获得它的Message Loop。有了这个Message Loop之后,以后就可以向它发送消息了。
4. 如果网页要单独的Render进程中加载,那么调用创建一个命令行,并且以该命令行以及前面创建的IPC::ChannelProxy对象为参数,创建一个ChildProcessLauncher对象,而该ChildProcessLauncher对象在创建的过程,就会启动一个新的Render进程。
接下来,我们主要分析第1、3和4件事情,第2件事情在接下来的一篇文章中分析IPC消息分发机制时再分析。
第一件事情涉及到IPC::Channel类的静态成员函数GenerateVerifiedChannelID和IPC::ChannelProxy类的静态成员函数Create。
IPC::Channel类的静态成员函数GenerateVerifiedChannelID的实现如下所示:
- std::string Channel::GenerateVerifiedChannelID(const std::string& prefix) {
- // A random name is sufficient validation on posix systems, so we don't need
- // an additional shared secret.
- std::string id = prefix;
- if (!id.empty())
- id.append(".");
- return id.append(GenerateUniqueRandomChannelID());
- }
IPC::Channel类的静态成员函数GenerateVerifiedChannelID实际上是调用另外一个静态成员函数GenerateUniqueRandomChannelID生成一个唯一的随机名字,后者的实现如下所示:
- base::StaticAtomicSequenceNumber g_last_id;
- ......
- std::string Channel::GenerateUniqueRandomChannelID() {
- ......
- int process_id = base::GetCurrentProcId();
- return base::StringPrintf("%d.%u.%d",
- process_id,
- g_last_id.GetNext(),
- base::RandInt(0, std::numeric_limits<int32>::max()));
- }
从这里就可以看到,这个用来创建UNIX Socket的名字由当前进程的PID、一个顺序数和一个随机数通过"."符号连接而成的。
回到RenderProcessHostImpl类的成员函数Init中,有了用来创建UNIX Socket的名字之后,就可以调用IPC::ChannelProxy类的静态成员函数Create创建一个Channel了,如下所示:
- scoped_ptr<ChannelProxy> ChannelProxy::Create(
- const IPC::ChannelHandle& channel_handle,
- Channel::Mode mode,
- Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner) {
- scoped_ptr<ChannelProxy> channel(new ChannelProxy(listener, ipc_task_runner));
- channel->Init(channel_handle, mode, true);
- return channel.Pass();
- }
这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。
IPC::ChannelProxy类的静态成员函数Create首先是创建了一个ChannelProxy对象,然后再调用该ChannelProxy对象的成员函数Init执行初始化工作,最后返回该ChannelProxy对象给调用者。
ChannelProxy对象的创建过程如下所示:
- ChannelProxy::ChannelProxy(Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner)
- : context_(new Context(listener, ipc_task_runner)), did_init_(false) {
- }
ChannelProxy类的构造函数主要是创建一个ChannelProxy::Context对象,并且将该ChannelProxy::Context对象保存在成员变量context_中。
ChannelProxy::Context对象的创建过程如下所示:
- ChannelProxy::Context::Context(Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner)
- : listener_task_runner_(base::ThreadTaskRunnerHandle::Get()),
- listener_(listener),
- ipc_task_runner_(ipc_task_runner),
- ......
- message_filter_router_(new MessageFilterRouter()),
- ...... {
- ......
- }
ChannelProxy::Context类有三个成员变量是需要特别关注的,它们分别是:
1. listenter_task_runner_。这个成员变量的类型为scoped_refptr<base::SingleThreadTaskRunner>,它指向的是一个SingleThreadTaskRunner对象。这个SingleThreadTaskRunner对象通过调用ThreadTaskRunnerHandle类的静态成员函数Get获得。从前面Chromium多线程模型设计和实现分析一文可以知道,ThreadTaskRunnerHandle类的静态成员函数Get返回的SingleThreadTaskRunner对象实际上是当前线程的一个MessageLoopProxy对象,通过该MessageLoopProxy对象可以向当前线程的消息队列发送消息。当前线程即为Browser进程的主线程。
2. listener_。这是一个IPC::Listener指针,它的值设置为参数listener的值。从前面的图3可以知道,RenderProcessHostImpl类实现了IPC::Listener接口,而且从前面的调用过程过程可以知道,参数listener指向的就是一个RenderProcessHostImpl对象。以后正在创建的ChannelProxy::Context对象在IO线程中接收到Render进程发送过来的IPC消息之后,就会转发给成员变量listener_指向的RenderProcessHostImpl对象处理,但是并不是让后者直接在IO线程处理,而是让后者在成员变量listener_task_runner_描述的线程中处理,即Browser进程的主线程处理。也就是说,ChannelProxy::Context类的成员变量listener_task_runner_和listener_是配合在一起使用的,后面我们分析IPC消息的分发机制时就可以看到这一点。
3. ipc_task_runner_。这个成员变量与前面分析的成员变量listener_task_runner一样,类型都为scoped_refptr<base::SingleThreadTaskRunner>,指向的者是一个SingleThreadTaskRunner对象。不过,这个SingleThreadTaskRunner对象由参数ipc_task_runner指定。从前面的调用过程可以知道,这个SingleThreadTaskRunner对象实际上是与Browser进程的IO线程关联的一个MessageLoopProxy对象。这个MessageLoopProxy对象用来接收Render进程发送过来的IPC消息。也就是说,Browser进程在IO线程中接收IPC消息。
ChannelProxy::Context类还有一个重要的成员变量message_filter_router_,它指向一个MessageFilterRouter对象,用来过滤IPC消息,后面我们分析IPC消息的分发机制时再详细分析。
回到ChannelProxy类的静态成员函数Create中,创建了一个ChannelProxy对象之后,接下来就调用它的成员函数Init进行初始化,如下所示:
- void ChannelProxy::Init(const IPC::ChannelHandle& channel_handle,
- Channel::Mode mode,
- bool create_pipe_now) {
- ......
- if (create_pipe_now) {
- ......
- context_->CreateChannel(channel_handle, mode);
- } else {
- context_->ipc_task_runner()->PostTask(
- FROM_HERE, base::Bind(&Context::CreateChannel, context_.get(),
- channel_handle, mode));
- }
- // complete initialization on the background thread
- context_->ipc_task_runner()->PostTask(
- FROM_HERE, base::Bind(&Context::OnChannelOpened, context_.get()));
- ......
- }
从前面的调用过程知道,参数channel_handle描述的是一个UNIX Socket名称,参数mode的值为IPC::Channel::MODE_SERVER,参数create_pipe_now的值为true。这样,ChannelProxy类的成员函数Init就会马上调用前面创建的ChannelProxy::Context对象的成员函数CreateChannel创建一个IPC通信通道,也就是在当前线程中创建一个IPC通信通道 。
另一个方面,如果参数create_pipe_now的值等于false,那么ChannelProxy类的成员函数Init就不是在当前线程创建IPC通信通道,而是在IO线程中创建。因为它先通过前面创建的ChannelProxy::Context对象的成员函数ipc_task_runner获得其成员变量ipc_task_runner_描述的SingleThreadTaskRunner对象,然后再将创建IPC通信通道的任务发送到该SingleThreadTaskRunner对象描述的IO线程的消息队列去。当该任务被处理时,就会调用ChannelProxy::Context类的成员函数CreateChannel。
当调用ChannelProxy::Context类的成员函数CreateChannel创建好一个IPC通信通道之后,ChannelProxy类的成员函数Init还会向当前进程的IO线程的消息队列发送一个消息,该消息绑定的是ChannelProxy::Context类的成员函数OnChannelOpened。因此,接下来我们就分别分析ChannelProxy::Context类的成员函数CreateChannel和OnChannelOpened。
ChannelProxy::Context类的成员函数CreateChannel的实现如下所示:
- void ChannelProxy::Context::CreateChannel(const IPC::ChannelHandle& handle,
- const Channel::Mode& mode) {
- ......
- channel_ = Channel::Create(handle, mode, this);
- }
ChannelProxy::Context类的成员函数CreateChannel调用Channel类的成员函数Create创建了一个IPC通信通道,如下所示:
- scoped_ptr<Channel> Channel::Create(
- const IPC::ChannelHandle &channel_handle, Mode mode, Listener* listener) {
- return make_scoped_ptr(new ChannelPosix(
- channel_handle, mode, listener)).PassAs<Channel>();
- }
从这里可以看到,对于Android平台来说,IPC通信通道通过一个ChannelPosix对象描述,该ChannelPosix对象的创建过程如下所示:
- ChannelPosix::ChannelPosix(const IPC::ChannelHandle& channel_handle,
- Mode mode, Listener* listener)
- : ChannelReader(listener),
- mode_(mode),
- ......
- pipe_(-1),
- client_pipe_(-1),
- #if defined(IPC_USES_READWRITE)
- fd_pipe_(-1),
- remote_fd_pipe_(-1),
- #endif // IPC_USES_READWRITE
- pipe_name_(channel_handle.name),
- ...... {
- ......
- if (!CreatePipe(channel_handle)) {
- ......
- }
- }
从前面的调用过程可以知道,参数channel_handle描述的是一个UNIX Socket名称,参数mode的值等于IPC::Channel::MODE_SERVER,参数listener指向的是前面创建的ChannelProxy::Context对象。
ChannelPosix类继承了ChannelReader类,后者用来读取从Render进程发送过来的IPC消息,并且将读取到的IPC消息发送给参数listener描述的ChannelProxy::Context对象,因此这里会将参数listener描述的ChannelProxy::Context对象传递给ChannelReader的构造函数。
ChannelPosix类通过UNIX Socket来描述IPC通信通道,这个UNIX Socket的Server端和Client文件描述符分别保存在成员变量pipe_和client_pipe_中。如果定义了宏IPC_USES_READWRITE,那么当发送的消息包含有文件描述时,就会使用另外一个专用的UNIX Socket来传输文件描述符给对方。这个专用的UNIX Socket的Server端和Client端文件描述符保存在成员变量fd_pipe_和remote_fd_pipe_中。后面分析IPC消息的分发过程时,我们再详细分析这一点。
ChannelPosix类的构造函数最后调用了另外一个成员函数CreatePipe开始创建IPC通信通道,如下所示:
- bool ChannelPosix::CreatePipe(
- const IPC::ChannelHandle& channel_handle) {
- ......
- int local_pipe = -1;
- if (channel_handle.socket.fd != -1) {
- ......
- } else if (mode_ & MODE_NAMED_FLAG) {
- ......
- } else {
- local_pipe = PipeMap::GetInstance()->Lookup(pipe_name_);
- if (mode_ & MODE_CLIENT_FLAG) {
- if (local_pipe != -1) {
- ......
- local_pipe = HANDLE_EINTR(dup(local_pipe));
- ......
- } else {
- ......
- local_pipe =
- base::GlobalDescriptors::GetInstance()->Get(kPrimaryIPCChannel);
- }
- } else if (mode_ & MODE_SERVER_FLAG) {
- ......
- base::AutoLock lock(client_pipe_lock_);
- if (!SocketPair(&local_pipe, &client_pipe_))
- return false;
- PipeMap::GetInstance()->Insert(pipe_name_, client_pipe_);
- }
- ......
- }
- #if defined(IPC_USES_READWRITE)
- // Create a dedicated socketpair() for exchanging file descriptors.
- // See comments for IPC_USES_READWRITE for details.
- if (mode_ & MODE_CLIENT_FLAG) {
- if (!SocketPair(&fd_pipe_, &remote_fd_pipe_)) {
- return false;
- }
- }
- #endif // IPC_USES_READWRITE
- ......
- pipe_ = local_pipe;
- return true;
- }
ChannelHandle类除了用来保存UNIX Socket的名称之外,还可以用来保存与该名称对应的UNIX Socket的文件描述符。在我们这个情景中,参数channel_handle仅仅保存了即将要创建的UNIX Socket的名称。
ChannelPosix类的成员变量mode_的值等于IPC::Channel::MODE_SERVER,它的MODE_NAMED_FLAG位等于0。Render进程启动之后,也会调用到ChannelPosix类的成员函数CreatePipe创建一个Client端的IPC通信通道,那时候用来描述Client端IPC通信通道的ChannelPosix对象的成员变量mode_的值IPC::Channel::MODE_CLIENT,它的MODE_NAMED_FLAG位同样等于0。因此,无论是在Browser进程中创建的Server端IPC通信通道,还是在Render进程中创建的Client端IPC通信通道,在调用ChannelPosix类的成员函数CreatePipe时,都按照以下逻辑进行。
对于Client端的IPC通信通道,即ChannelPosix类的成员变量mode_的MODE_CLIENT_FLAG位等于1的情况,首先是在一个Pipe Map中检查是否存在一个UNIX Socket文件描述符与成员变量pipe_name_对应。如果存在,那么就使用该文件描述符进行IPC通信。如果不存在,那么再到Global Descriptors中检查是否存在一个UNIX Socket文件描述符与常量kPrimaryIPCChannel对应。如果存在,那么就使用该文件描述符进行IPC通信。实际上,当网页不是在独立的Render进程中加载时,执行的是前一个逻辑,而当网页是在独立的Render进程中加载时,执行的是后一个逻辑。
Chromium为了能够统一地处理网页在独立Render进程和不在独立Render进程加载两种情况,会对后者进行一个抽象,即会假设后者也是在独立的Render进程中加载一样。这样,Browser进程在加载该网页时,同样会创建一个图1所示的RenderProcess对象,不过该RenderProcess对象没有对应的一个真正的进程,对应的仅仅是Browser进程中的一个线程。也就是这时候,图1所示的RenderPocessHost对象和RenderProcess对象执行的仅仅是进程内通信而已,不过它们仍然是按照进程间的通信规则进行,也就是通过IO线程来间接进行。不过,在进程内建立IPC通信通道和在进程间建立IPC通信通道的方式是不一样的。具体来说,就是在进程间建立IPC通信通道,需要将描述该通道的UNIX Socket的Client端文件描述符从Browser进程传递到Render进程,Render进程接收到该文件描述符之后,就会以kPrimaryIPCChannel为键值保存在Global Descriptors中。而在进程内建立IPC通信通道时,描述IPC通信通道的UNIX Socket的Client端文件描述符直接以UNIX Socket名称为键值,保存在一个Pipe Map中即可。后面我们分析在进程内在进程间创建Client端IPC通信通道时,会继续看到这些相关的区别。
对于Server端的IPC通信通道,即ChannelPosix类的成员变量mode_的MODE_SERVER_FLAG位等于1的情况,ChannelPosix类的成员函数CreatePipe调用函数SocketPair创建了一个UNIX Socket,其中,Server端文件描述符保存在成员变量pipe_中,而Client端文件描述符保存在成员变量client_pipe_中,并且Client端文件描述符还会以与前面创建的UNIX Socket对应的名称为键值,保存在一个Pipe Map中,这就是为建立进程内IPC通信通道而准备的。
最后,如果定义了IPC_USES_READWRITE宏,如前面提到的,那么还会继续创建一个专门用来在进程间传递文件描述的UNIX Socket,该UNIX Socket的Server端和Client端文件描述符分别保存在成员变量fd_pipe_和remote_fd_pipe_中。
这一步执行完成之后,一个Server端IPC通信通道就创建完成了。回到ChannelProxy类的成员函数Init中,它接下来是发送一个消息到Browser进程的IO线程的消息队列中,该消息绑定的是ChannelProxy::Context类的成员函数OnChannelOpened,它的实现如下所示:
- void ChannelProxy::Context::OnChannelOpened() {
- ......
- if (!channel_->Connect()) {
- OnChannelError();
- return;
- }
- ......
- }
从前面的分析可以知道,ChannelProxy::Context类的成员变量channel_指向的是一个ChannelPosix对象,这里调用它的成员函数Connect将它描述的IPC通信通道交给当前进程的IO线程进行监控。
ChannelPosix类的成员函数Connect的实现如下所示:
- bool ChannelPosix::Connect() {
- ......
- bool did_connect = true;
- if (server_listen_pipe_ != -1) {
- ......
- } else {
- did_connect = AcceptConnection();
- }
- return did_connect;
- }
当ChannelPosix类的成员变量server_listen_pipe_的值不等于-1时,表示它描述的是一个用来负责监听IPC通信通道连接消息的Socket中,也就是这个Socket不是真正用来执行Browser进程和Render进程之间的通信的,而是Browser进程首先对ChannelPosix类的成员变量server_listen_pipe_描述的Socket进行listen,接着Render进程通过connect连接到该Socket,使得Browser进程accepet到一个新的Socket,然后再通过这个新的Socket与Render进程执行IPC。
在我们这个情景中,ChannelPosix类的成员变量server_listen_pipe_的值等于-1,因此接下来ChannelPosix类的成员函数Connect调用了另外一个成员函数AcceptConnection,它的实现如下所示:
- bool ChannelPosix::AcceptConnection() {
- base::MessageLoopForIO::current()->WatchFileDescriptor(
- pipe_, true, base::MessageLoopForIO::WATCH_READ, &read_watcher_, this);
- QueueHelloMessage();
- if (mode_ & MODE_CLIENT_FLAG) {
- // If we are a client we want to send a hello message out immediately.
- // In server mode we will send a hello message when we receive one from a
- // client.
- waiting_connect_ = false;
- return ProcessOutgoingMessages();
- } else if (mode_ & MODE_SERVER_FLAG) {
- waiting_connect_ = true;
- return true;
- } else {
- NOTREACHED();
- return false;
- }
- }
ChannelPosix类的成员函数AcceptConnection首先是获得与当前进程的IO线程关联的一个MessageLoopForIO对象,接着再调用该MessageLoopForIO对象的成员函数WatchFileDescriptor对成员变量pipe_ 描述的一个UNIX Socket进行监控。MessageLoopForIO类的成员函数WatchFileDescriptor最终会调用到在前面Chromium多线程模型设计和实现分析一文中提到的MessagePumpLibevent对该UNIX Socket进行监控。这意味着当该UNIX Socket有新的IPC消息需要接收时,当前正在处理的ChannelPosix对象的成员函数OnFileCanReadWithoutBlocking就会被调用。这一点需要理解Chromium的多线程机制,具体可以参考Chromium多线程模型设计和实现分析一文。
接下来,ChannelPosix类的成员函数AcceptConnection还会调用另外一个成员函数QueueHelloMessage创建一个Hello Message,并且将该Message添加到内部的一个IPC消息队列去等待发送给对方进程。执行IPC的双方,就是通过这个Hello Message进行握手的。具体来说,就是Server端和Client端进程建立好连接之后,由Client端发送一个Hello Message给Server端,Server端接收到该Hello Message之后,就认为双方已经准备就绪,可以进行IPC了。
因此,如果当前正在处理的ChannelPosix对象描述的是Client端的通信通道,即它的成员变量mode_的MODE_CLIENT_FLAG位等于1,那么ChannelPosix类的成员函数AcceptConnection就会马上调用另外一个成员函数ProcessOutgoingMessages前面创建的Hello Message发送给Server端。
另一方面,如果当前正在处理的ChannelPosix对象描述的是Server端的通信通道,那么ChannelPosix类的成员函数AcceptConnection就仅仅是将成员变量waiting_connect_的值设置为true,表示正在等待Client端发送一个Hello Message过来。
关于Hello Message的发送和接收,我们在接下来的一篇文章分析IPC消息分发机制时再详细分析。
这一步执行完成之后,Server端的IPC通信通道就创建完成了,也就是Browser进程已经创建好了一个Server端的IPC通信通道。回到RenderProcessHostImpl类的成员函数Init中,它接下来要做的事情就是启动Render进程。
我们首先考虑网页不是在独立的Render进程加载的情况,即在Browser进程加载的情况,这时候并没有真的启动了一个Render进程,而仅仅是在Browser进程中创建了一个RenderProcess对象而已,如下所示:
- bool RenderProcessHostImpl::Init() {
- ......
- // Setup the IPC channel.
- const std::string channel_id =
- IPC::Channel::GenerateVerifiedChannelID(std::string());
- channel_ = IPC::ChannelProxy::Create(
- channel_id,
- IPC::Channel::MODE_SERVER,
- this,
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get());
- ......
- if (run_renderer_in_process()) {
- ......
- in_process_renderer_.reset(g_renderer_main_thread_factory(channel_id));
- base::Thread::Options options;
- ......
- options.message_loop_type = base::MessageLoop::TYPE_DEFAULT;
- in_process_renderer_->StartWithOptions(options);
- g_in_process_thread = in_process_renderer_->message_loop();
- ......
- } else {
- ......
- }
- return true;
- }
前面在分析RenderProcessHostImpl类的成员函数Init时提到,RenderProcessHostImpl类的静态成员变量g_renderer_main_thread_factory描述的是一个函数,通过它可以创建一个类型为InProcessRendererThread的线程。
一个类型为InProcessRendererThread的线程的创建过程如下所示:
- InProcessRendererThread::InProcessRendererThread(const std::string& channel_id)
- : Thread("Chrome_InProcRendererThread"), channel_id_(channel_id) {
- }
从这里就可以看到,InProcessRendererThread类是从Thread类继承下来的,因此这里调用了Thread类的构造函数。
此外,InProcessRendererThread类的构造函数还会将参数channel_id描述的一个UNIX Socket名称保存在成员变量channel_id_中。从前面的分析可以知道,该名称对应的UNIX Socket已经创建出来了,并且它的Client端文件描述符以该名称为键值,保存在一个Pipe Map中。
回到RenderProcessHostImpl类的成员函数Init中,接下来它会调用前面创建的InProcessRendererThread对象的成员函数StartWithOptions启动一个线程。从前面Chromium多线程模型设计和实现分析一文可以知道,当该线程启动起来之后,并且在进入消息循环之前,会被调用InProcessRendererThread类的成员函数Init执行初始化工作。
InProcessRendererThread类的成员函数Init的实现如下所示:
- void InProcessRendererThread::Init() {
- render_process_.reset(new RenderProcessImpl());
- new RenderThreadImpl(channel_id_);
- }
InProcessRendererThread类的成员函数Init首先在当前进程,即Browser进程,创建了一个RenderProcessImpl对象,保存在成员变量render_process_中,描述一个假的Render进程,接着再创建了一个RenderThreadImpl对象描述当前线程,即当前正在处理的InProcessRendererThread对象描述的线程。
在RenderProcessImpl对象的创建中,会创建一个IO线程,该IO线程负责与Browser进程启动时就创建的一个IO线程执行IPC通信。从图6可以知道,RenderProcessImpl类继承了RenderProcess类,RenderProcess类又继承了ChildProcess类,创建IO线程的工作是从ChildProcess类的构造函数中进行的,如下所示:
- ChildProcess::ChildProcess()
- : ...... {
- ......
- // We can't recover from failing to start the IO thread.
- CHECK(io_thread_.StartWithOptions(
- base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
- ......
- }
从这里就可以看到,ChildProcess类的构造函数调用了成员变量io_thread_描述的一个Thread对象的成员函数StartWithOptions创建了一个IO线程。
回到InProcessRendererThread类的成员函数Init中,在RenderThreadImpl对象的创建过程,会创建一个Client端的IPC通信通道,如下所示:
- RenderThreadImpl::RenderThreadImpl(const std::string& channel_name)
- : ChildThread(channel_name) {
- ......
- }
从这里可以看到,RenderThreadImpl类继承了ChildThread类,创建Client端IPC通信通道的过程是在ChildThread类的构造函数中进行的,如下所示:
- ChildThread::ChildThread(const std::string& channel_name)
- : channel_name_(channel_name),
- ..... {
- Init();
- }
ChildThread类的构造函数将参数channel_name描述的一个UNIX Socket的名称保存在成员变量channel_name_之后,就调用了另外一个成员函数Init执行创建Client端IPC通信通道的工作,如下所示:
- void ChildThread::Init() {
- ......
- channel_ =
- IPC::SyncChannel::Create(channel_name_,
- IPC::Channel::MODE_CLIENT,
- this,
- ChildProcess::current()->io_message_loop_proxy(),
- true,
- ChildProcess::current()->GetShutDownEvent());
- ......
- }
Client端IPC通信通道通过IPC::SyncChannel类的静态成员函数Create进行创建,如下所示:
- scoped_ptr<SyncChannel> SyncChannel::Create(
- const IPC::ChannelHandle& channel_handle,
- Channel::Mode mode,
- Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner,
- bool create_pipe_now,
- base::WaitableEvent* shutdown_event) {
- scoped_ptr<SyncChannel> channel =
- Create(listener, ipc_task_runner, shutdown_event);
- channel->Init(channel_handle, mode, create_pipe_now);
- return channel.Pass();
- }
IPC::SyncChannel类的静态成员函数Create首先调用另外一个重载版本的静态成员函数Create创建一个SyncChannel对象,接着再调用该SyncChannel的成员函数Init执行初始化工作。
IPC::SyncChannel类是从IPC::ChannelProxy类继承下来的,它与IPC::ChannelProxy的区别在于,前者既可以用来发送同步的IPC消息,也可以用来发送异步的IPC消息,而后者只可以用来发送异步消息。所谓同步IPC消息,就是发送者发送它给对端之后,会一直等待对方发送一个回复,而对于异步IPC消息,发送者把它发送给对端之后,不会进行等待,而是直接返回。后面分析IPC消息的分发机制时我们再详细分析这一点。
IPC::SyncChannel类的成员函数Init是从父类IPC::ChannelProxy类继承下来的,后者我们前面已经分析过了,主要区别在于这里传递第二个参数mode的值等于IPC::Channel::MODE_CLIENT,表示要创建的是一个Client端的IPC通信通道。
接下来,我们就主要分析IPC::SyncChannel类三个参数版本的静态成员函数Create创建SyncChannel对象的过程,如下所示:
- scoped_ptr<SyncChannel> SyncChannel::Create(
- Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner,
- WaitableEvent* shutdown_event) {
- return make_scoped_ptr(
- new SyncChannel(listener, ipc_task_runner, shutdown_event));
- }
IPC::SyncChannel类三个参数版本的静态成员函数Create创建了一个SyncChannel对象,并且将该SyncChannel对象返回给调用者。
SyncChannel对象的创建过程如下所示:
- SyncChannel::SyncChannel(
- Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner,
- WaitableEvent* shutdown_event)
- : ChannelProxy(new SyncContext(listener, ipc_task_runner, shutdown_event)) {
- ......
- StartWatching();
- }
从前面的调用过程可以知道,参数listener描述的是一个ChildThread对象,参数ipc_task_runner描述的是与前面在ChildProcess类的构造函数中创建的IO线程关联的一个MessageLoopProxy对象,参数shutdown_event描述的是一个ChildProcess关闭事件。
对于第三个参数shutdown_event的作用,我们这里做一个简单的介绍,在接下来一篇文章中分析IPC消息的分发机制时再详细分析。前面提到,SyncChannel可以用来发送同步消息,这意味着发送线程需要进行等待。这个等待过程是通过我们在前面Chromium多线程模型设计和实现分析一文中提到的WaitableEvent类实现的。也就是说,每一个同步消息都有一个关联的WaitableEvent对象。此外,还有一些异常情况需要处理。例如,SyncChannel在等待一个同步消息的过程中,有可能对方已经退出了,这相当于是发生了一个ChildProcess关闭事件。在这种情况下,继续等待是没有意义的。因此,当SyncChannel监控到ChildProcess关闭事件时,就可以执行一些清理工作了。此外,SyncChannel在等待一个同步消息的过程中,也有可能收到对方发送过来的非回复消息。在这种情况下,SyncChannel需要获得通知,以便可以对这些非回复消息进行处理。SyncChannel获得此类非回复消息的事件通知是通过另外一个称为Dispatch Event的WaitableEvent对象获得的。这意味着SyncChannel在发送一个同步消息的过程中,需要同时监控多个WaitableEvent对象。
了解了各个参数的含义之后,我们就开始分析SyncChannel类的构造函数。它首先是创建了一个SyncChannel::SyncContext对象,并且以该SyncChannel::SyncContext对象为参数,调用父类ChannelProxy的构造函数,以便可以对父类ChannelProxy进行初始化。
SyncChannel::SyncContext对象的创建过程如下所示:
- SyncChannel::SyncContext::SyncContext(
- Listener* listener,
- base::SingleThreadTaskRunner* ipc_task_runner,
- WaitableEvent* shutdown_event)
- : ChannelProxy::Context(listener, ipc_task_runner),
- ......,
- shutdown_event_(shutdown_event),
- ...... {
- }
从这里可以看到,SyncChannel::SyncContext类是从ChannelProxy::Context类继承下来的,因此这里会调用ChannelProxy::Context类的构造函数进行初始化。此外,SyncChannel::SyncContext类的构造函数还会将参数shutdown_event描述的一个ChildProcess关闭事件保存在成员变量shutdown_event_中。
回到SyncChannel类的构造函数中,当它创建了一个SyncChannel::SyncContext对象之后,就使用该SyncChannel::SyncContext对象来初始化父类ChannelProxy,如下所示:
- ChannelProxy::ChannelProxy(Context* context)
- : context_(context),
- did_init_(false) {
- }
注意,参数context的类型虽然为一个ChannelProxy::Context指针,但是它实际上指向的是一个SyncChannel::SyncContext对象,该SyncChannel::SyncContext对象保存在成员变量context_中。
继续回到SyncChannel类的构造函数中,它用一个SyncChannel::SyncContext对象初始化了父类ChannelProxy之后,继续调用另外一个成员函数StartWatching监控我们在前面提到的一个Dispatch Event,如下所示:
- void SyncChannel::StartWatching() {
- ......
- dispatch_watcher_callback_ =
- base::Bind(&SyncChannel::OnWaitableEventSignaled,
- base::Unretained(this));
- dispatch_watcher_.StartWatching(sync_context()->GetDispatchEvent(),
- dispatch_watcher_callback_);
- }
SyncChannel类的成员函数StartWatching调用成员变量dispatch_watcher_描述的一个WaitableEventWatcher对象的成员函数StartWatching对Dispatch Event进行监控,从这里就可以看到,Dispatch Event可以通过前面创建的SyncChannel::SyncContext对象的成员函数sync_context获得,并且当该Display Event发生时,SyncChannel类的成员函数OnWaitableEventSignaled就会被调用。
前面在分析ChannelProxy类的成员函数Init时,我们提到,当它调用另外一个成员函数CreateChannel创建了一个IPC通信通道之后,会调用其成员变量context_描述的一个ChannelProxy::Context对象的成员函数OnChannelOpened将已经创建好的的IPC通信通道增加到IO线程的消息队列中去监控。由于在我们这个情景中,ChannelProxy类的成员变量context_指向的是一个SyncChannel::SyncContext对象,因此,当ChannelProxy类的成员函数Init创建了一个IPC通信通道之后,它接下来调用的是SyncChannel::SyncContext类的成员函数OnChanneIOpened将已经创建好的IPC通信通道增加到IO线程的消息队列中去监控。
SyncChannel::SyncContext类的成员函数OnChanneIOpened的实现如下所示:
- void SyncChannel::SyncContext::OnChannelOpened() {
- shutdown_watcher_.StartWatching(
- shutdown_event_,
- base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled,
- base::Unretained(this)));
- Context::OnChannelOpened();
- }
SyncChannel::SyncContext类的成员函数OnChanneIOpened首先是调用成员变量shutdown_watcher_描述的一个WaitableEventWatcher对象的成员函数StartWatching监控成员变量shutdown_event_描述的一个ChildProcess关闭事件。从这里就可以看到,当ChildProcess关闭事件发生时,SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled就会被调用。
最后,SyncChannel::SyncContext类的成员函数OnChanneIOpened调用了父类ChannelProxy的成员函数OnChannelOpened将IPC通信通道增加到IO线程的的消息队列中去监控。
这一步执行完成之后,一个Client端的IPC通信通道就创建完成了。这里我们描述的Client端IPC通信通道的创建过程虽然是发生在Browser进程中的,不过这个过程与在独立的Render进程中创建的Client端IPC通信通道的过程是一样的。这一点在接下来的分析中就可以看到。
回到前面分析的RenderProcessHostImpl类的成员函数Init中,对于需要在独立的Render进程加载网页的情况,它就会启动一个Render进程,如下所示:
- bool RenderProcessHostImpl::Init() {
- ......
- // Setup the IPC channel.
- const std::string channel_id =
- IPC::Channel::GenerateVerifiedChannelID(std::string());
- channel_ = IPC::ChannelProxy::Create(
- channel_id,
- IPC::Channel::MODE_SERVER,
- this,
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get());
- ......
- if (run_renderer_in_process()) {
- ......
- } else {
- ......
- CommandLine* cmd_line = new CommandLine(renderer_path);
- ......
- AppendRendererCommandLine(cmd_line);
- cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
- ......
- child_process_launcher_.reset(new ChildProcessLauncher(
- new RendererSandboxedProcessLauncherDelegate(channel_.get()),
- cmd_line,
- GetID(),
- this));
- ......
- }
- return true;
- }
RenderProcessHostImpl类的成员函数Init创建了一个Server端的IPC通信通道之后,就会通过一个ChildProcessLauncher对象来启动一个Render进程。不过在启动该Render进程之前,首先要构造好它的启动参数,也就是命令行参数。
Render进程的启动命令行参数通过一个CommandLine对象来描述,它包含有很多选项,不过现在我们只关心两个。一个是switches::kProcessType,另外一个是switches::kProcessChannelID。其中,switches::kProcessChannelID选项对应的值设置为本地变量channel_id描述的值,即前面调用IPC::Channel类的静态成员函数GenerateVerifiedChannelID生成的一个UNIX Socket名称。
选项switches::kProcessType的值是通过RenderProcessHostImpl类的成员函数AppendRendererCommandLine设置的,如下所示:
- void RenderProcessHostImpl::AppendRendererCommandLine(
- CommandLine* command_line) const {
- // Pass the process type first, so it shows first in process listings.
- command_line->AppendSwitchASCII(switches::kProcessType,
- switches::kRendererProcess);
- ......
- }
从这里就可以看到,选项switches::kProcessType的值设置为kRendererProcess,这表示接下来我们通过ChildProcessLauncher类启动的进程是一个Render进程。
回到RenderProcessHostImpl类的成员函数Init中,当要启动的Render进程的命令行参数准备好之后,接下来就通过ChildProcessLauncher类的构造函数启动一个Render进程,如下所示:
- ChildProcessLauncher::ChildProcessLauncher(
- SandboxedProcessLauncherDelegate* delegate,
- CommandLine* cmd_line,
- int child_process_id,
- Client* client) {
- context_ = new Context();
- context_->Launch(
- delegate,
- cmd_line,
- child_process_id,
- client);
- }
ChildProcessLauncher类的构造函数首先创建了一个ChildProcessLauncher::Context对象,保存在成员变量context_中,并且调用该ChildProcessLauncher::Context对象的成员函数Launch启动一个Render进程。
ChildProcessLauncher::Context类的成员函数Launch的实现如下所示:
- class ChildProcessLauncher::Context
- : public base::RefCountedThreadSafe<ChildProcessLauncher::Context> {
- public:
- ......
- void Launch(
- SandboxedProcessLauncherDelegate* delegate,
- CommandLine* cmd_line,
- int child_process_id,
- Client* client) {
- client_ = client;
- ......
- BrowserThread::PostTask(
- BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
- base::Bind(
- &Context::LaunchInternal,
- make_scoped_refptr(this),
- client_thread_id_,
- child_process_id,
- delegate,
- cmd_line));
- }
- ......
- };
ChildProcessLauncher::Context类的成员函数Launch通过调用BrowserThread类的静态成员函数PostTask向Browser进程的一个专门用来启动子进程的BrowserThread::PROCESS_LAUNCHER线程的消息队列发送一个任务,该任务绑定了ChildProcessLauncher::Context类的成员函数LaunchInternal。因此,接下来ChildProcessLauncher::Context类的成员函数LaunchInternal就会在BrowserThread::PROCESS_LAUNCHER线程中执行,如下所示:
- class ChildProcessLauncher::Context
- : public base::RefCountedThreadSafe<ChildProcessLauncher::Context> {
- public:
- ......
- static void LaunchInternal(
- // |this_object| is NOT thread safe. Only use it to post a task back.
- scoped_refptr<Context> this_object,
- BrowserThread::ID client_thread_id,
- int child_process_id,
- SandboxedProcessLauncherDelegate* delegate,
- CommandLine* cmd_line) {
- ......
- int ipcfd = delegate->GetIpcFd();
- ......
- std::vector<FileDescriptorInfo> files_to_register;
- files_to_register.push_back(
- FileDescriptorInfo(kPrimaryIPCChannel,
- base::FileDescriptor(ipcfd, false)));
- ......
- StartChildProcess(cmd_line->argv(), child_process_id, files_to_register,
- base::Bind(&ChildProcessLauncher::Context::OnChildProcessStarted,
- this_object, client_thread_id, begin_launch_time));
- ......
- }
- ......
- };
从前面的调用过程可以知道,参数delegate指向的一个SandboxedProcessLauncherDelegate对象封装了前面创建的一个ChannelProxy对象,通过调用它的成员函数GetIpcFd可以获得它所封装的ChannelProxy对象描述的一个UNIX Socket的Client端文件描述符。该Client端文件描述符接下来以kPrimaryIPCChannel为键值封装在一个FileDescriptorInfo对象,并且该FileDescriptorInfo对象最终会保存在本地变量files_to_register描述的一个std::vector。该std::vector最后又会传递给函数StartChildProcess,后者负责执行启动Render进程的工程。
函数StartChildProcess的实现如下所示:
- void StartChildProcess(
- const CommandLine::StringVector& argv,
- int child_process_id,
- const std::vector<content::FileDescriptorInfo>& files_to_register,
- const StartChildProcessCallback& callback) {
- JNIEnv* env = AttachCurrentThread();
- ......
- // Create the Command line String[]
- ScopedJavaLocalRef<jobjectArray> j_argv = ToJavaArrayOfStrings(env, argv);
- size_t file_count = files_to_register.size();
- DCHECK(file_count > 0);
- ScopedJavaLocalRef<jintArray> j_file_ids(env, env->NewIntArray(file_count));
- base::android::CheckException(env);
- jint* file_ids = env->GetIntArrayElements(j_file_ids.obj(), NULL);
- base::android::CheckException(env);
- ScopedJavaLocalRef<jintArray> j_file_fds(env, env->NewIntArray(file_count));
- base::android::CheckException(env);
- jint* file_fds = env->GetIntArrayElements(j_file_fds.obj(), NULL);
- base::android::CheckException(env);
- ScopedJavaLocalRef<jbooleanArray> j_file_auto_close(
- env, env->NewBooleanArray(file_count));
- base::android::CheckException(env);
- jboolean* file_auto_close =
- env->GetBooleanArrayElements(j_file_auto_close.obj(), NULL);
- base::android::CheckException(env);
- for (size_t i = 0; i < file_count; ++i) {
- const content::FileDescriptorInfo& fd_info = files_to_register[i];
- file_ids[i] = fd_info.id;
- file_fds[i] = fd_info.fd.fd;
- file_auto_close[i] = fd_info.fd.auto_close;
- }
- env->ReleaseIntArrayElements(j_file_ids.obj(), file_ids, 0);
- env->ReleaseIntArrayElements(j_file_fds.obj(), file_fds, 0);
- env->ReleaseBooleanArrayElements(j_file_auto_close.obj(), file_auto_close, 0);
- Java_ChildProcessLauncher_start(env,
- base::android::GetApplicationContext(),
- j_argv.obj(),
- child_process_id,
- j_file_ids.obj(),
- j_file_fds.obj(),
- j_file_auto_close.obj(),
- reinterpret_cast<intptr_t>(new StartChildProcessCallback(callback)));
- }
函数StartChildProcess将参数argv转换为一个Java层的String数组,并且将参数files_to_register描述的一个类型为FileDescriptorInfo的std::vector转换为两个Java层的int数组以及一个boolean数组,其中,第一个int数组描述的一组ID,第二个int数组描述的是与第一个int数组描述的ID对应的一组文件描述符,而另外一个boolean数组描述第二个int数组描述的一组文件描述符那些是需要自动关闭的。
最后,函数StartChildProcess最后调用了另外一个函数Java_ChildProcessLauncher_start通过Java层的接口来启动一个子进程,如下所示:
- static void Java_ChildProcessLauncher_start(JNIEnv* env, jobject context,
- jobjectArray commandLine,
- JniIntWrapper childProcessId,
- jintArray fileIds,
- jintArray fileFds,
- jbooleanArray fileAutoClose,
- jlong clientContext) {
- /* Must call RegisterNativesImpl() */
- CHECK_CLAZZ(env, ChildProcessLauncher_clazz(env),
- ChildProcessLauncher_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
- base::android::MethodID::TYPE_STATIC>(
- env, ChildProcessLauncher_clazz(env),
- "start",
- "("
- "Landroid/content/Context;"
- "[Ljava/lang/String;"
- "I"
- "[I"
- "[I"
- "[Z"
- "J"
- ")"
- "V",
- &g_ChildProcessLauncher_start);
- env->CallStaticVoidMethod(ChildProcessLauncher_clazz(env),
- method_id, context, commandLine, as_jint(childProcessId), fileIds,
- fileFds, fileAutoClose, clientContext);
- jni_generator::CheckException(env);
- }
函数Java_ChildProcessLauncher_start调用参数env描述的一个JNI环境对象调用了Java层的ChildProcessLauncher类的成员函数start执行启动子进程的操作。
Java层的ChildProcessLauncher类的成员函数start的实现如下所示:
- public class ChildProcessLauncher {
- ......
- static void start(
- Context context,
- final String[] commandLine,
- int childProcessId,
- int[] fileIds,
- int[] fileFds,
- boolean[] fileAutoClose,
- long clientContext) {
- ......
- FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
- for (int i = 0; i < fileFds.length; i++) {
- filesToBeMapped[i] =
- new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
- }
- assert clientContext != 0;
- int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
- boolean inSandbox = true;
- String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
- if (SWITCH_RENDERER_PROCESS.equals(processType)) {
- callbackType = CALLBACK_FOR_RENDERER_PROCESS;
- } else if (SWITCH_GPU_PROCESS.equals(processType)) {
- callbackType = CALLBACK_FOR_GPU_PROCESS;
- } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
- inSandbox = false;
- }
- ChildProcessConnection allocatedConnection = null;
- synchronized (ChildProcessLauncher.class) {
- if (inSandbox) {
- allocatedConnection = sSpareSandboxedConnection;
- sSpareSandboxedConnection = null;
- }
- }
- if (allocatedConnection == null) {
- allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
- ......
- }
- ......
- triggerConnectionSetup(allocatedConnection, commandLine, childProcessId, filesToBeMapped,
- callbackType, clientContext);
- ......
- }
- ......
- }
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。
ChildProcessLauncher类的成员函数start首先将参数fileIds、fileFds和fileAutoClose描述的文件描述符信息封装在一个FileDescriptorInfo数组中。记住 ,这里面包含了一个ID值为kPrimaryIPCChannel的文件描述符,该文件描述符描述的是用作IPC通信通道的一个UNIX Socket的客户端文件描述符。
接下来,ChildProcessLauncher类的成员函数start判断要启动的子进程需不需要运行在沙箱中。ChildProcessLauncher类负责启动所有的Browser子进程,包括:
1. Render进程;
2. GPU进程;
3. Plugin进程;
4. Broker进程。
其中,前面三种进程都是需要运行在沙箱里面的。这里所谓的沙箱,就是一个android:isolatedProcess属性被设置为true的Service进程,它不具有任何宿主应用程序申请的权限,这一点我们在前面Chromium多进程架构简要介绍和学习计划一文有提及到。
对于第四种进程,它不是运行在沙箱里面的,这意味着它具有宿主应用程序申请的权限。什么情况下Browser进程会启动一个Broker进程呢? Chromium给Pepper Plugin提供了一个PPB_BrokerTrusted接口,通过该接口的成员函数Connect可以请求Browser进程创建一个Broker进程。由于Broker进程是一个具有特权的进程,因此,一个Pepper Plugin在调用PPB_BrokerTrusted接口的成员函数Connect的时候,Chromium会弹出一个infobar提示用户,只有用户同意的情况下,才允许请求Browser进程创建一个Broker进程。Broker进程同样会加载请求启动它的Pepper Plugin对应的模块文件,但是会调用不同的入口点函数。Broker进程启动完成之后,Pepper Plugin可以通过PPB_BrokerTrusted接口的GetHandle获得一个用来与Broker进程执行IPC的UNIX Socket文件描述符。通过该UNIX Socket文件描述符,Pepper Plugin就可以请求Broker进程执行一些特权操作了,这样就可以绕开Pepper Plugin由于运行在沙箱不能执行特权操作的问题。注意,这一切都是在用户允许的前提下才能够进行的。
ChildProcessLauncher类的成员函数start通过获取命令行参数里面的SWITCH_PROCESS_TYPE选项确定要启动的子进程的类型,从而确定是否要将它运行在沙箱中。当要启动的子进程要运行沙箱的时候,本地变量inSandbox的值就会被设置为true。
ChildProcessLauncher类提供了一个静态成员函数warmUp,Browser进程可以调用它提前启动一个运行在沙箱中的Service进程,并且获得该Service进程的一个ServiceConnection对象,该ServiceConnection对象封装在一个ChildProcessConnectionImpl对象中,并且该ChildProcessConnectionImpl对象保存在ChildProcessLauncher类的静态成员变量sSpareSandboxedConnection中。
在要启动的子进程需要运行在沙箱的情况下,ChildProcessLauncher类的成员函数start尝试复用之前预先启动的子进程,因为这样可以加载子进程的启动过程。注意,一旦该预先启动的子进程被复用了,ChildProcessLauncher类的静态成员变量sSpareSandboxedConnection的值就被设置为null,表示它所描述的子进程已经被复用过了。
不过,目前还没有在源码里面发现Browser进程有使用上述的warm up机制,因此,ChildProcessLauncher类的成员函数start接下来会调用另外一个静态成员函数allocateBoundConnection启动一个Service进程,并且调用静态成员函数triggerConnectionSetup将该Service进程的启动参数保存起来,以便等该Service进程启动完成之后,使用保存起来的参数对Service进程进行初始化。因此,接下来我们就分别分析ChildProcessLauncher类的静态成员函数allocateBoundConnection和triggerConnectionSetup。
ChildProcessLauncher类的静态成员函数allocateBoundConnection的实现如下所示:
- public class ChildProcessLauncher {
- ......
- private static ChildProcessConnection allocateBoundConnection(Context context,
- String[] commandLine, boolean inSandbox) {
- ChromiumLinkerParams chromiumLinkerParams = getLinkerParamsForNewConnection();
- ChildProcessConnection connection =
- allocateConnection(context, inSandbox, chromiumLinkerParams);
- if (connection != null) {
- connection.start(commandLine);
- }
- return connection;
- }
- ......
- }
ChildProcessLauncher类的静态成员函数allocateBoundConnection首先调用静态成员函数getLinkerParamsForNewConnection获得Chromium相关的库链接信息,并且保存在一个ChromiumLinkerParams对象中,接下来再调用成员函数allocateConnection创建一个ChildProcessConnection对象,并且通过该ChildProcessConnection对象的成员函数start启动一个Service进程。
关于Chromium相关库的链接信息,涉及到Chromium相关库的加载机制,以后我们分析WebView的加载过程时,再详细分析。接下来我们就主要分析ChildProcessLauncher类的静态成员函数allocateConnection的实现,如下所示:
- public class ChildProcessLauncher {
- ......
- private static ChildProcessConnection allocateConnection(Context context,
- boolean inSandbox, ChromiumLinkerParams chromiumLinkerParams) {
- ChildProcessConnection.DeathCallback deathCallback =
- new ChildProcessConnection.DeathCallback() {
- @Override
- public void onChildProcessDied(ChildProcessConnection connection) {
- if (connection.getPid() != 0) {
- stop(connection.getPid());
- } else {
- freeConnection(connection);
- }
- }
- };
- sConnectionAllocated = true;
- return getConnectionAllocator(inSandbox).allocate(context, deathCallback,
- chromiumLinkerParams);
- }
- ......
- }
ChildProcessLauncher类的静态成员函数allocateConnection首先创建了一个ChildProcessConnection.DeathCallback对象,该ChildProcessConnection.DeathCallback对象是用来监控接下来要启动的Service进程的生命周期的。一旦要启动的Service进程退出,该ChildProcessConnection.DeathCallback对象的成员函数onChildProcessDied就会被调用,并且用来执行清理工作。
ChildProcessLauncher类的静态成员函数allocateConnection接下来调用静态成员函数getConnectionAllocator获得一个ChildConnectionAllocator对象,并且通过该ChildConnectionAllocator对象的成员函数allocate获得一个ChildProcessConnection对象。
ChildProcessLauncher类的静态成员函数getConnectionAllocator的实现如下所示:
- public class ChildProcessLauncher {
- ......
- private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
- new ChildConnectionAllocator(true);
- private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
- new ChildConnectionAllocator(false);
- ......
- private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
- return inSandbox ?
- sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
- }
- ......
- }
ChildProcessLauncher类的静态成员函数getConnectionAllocator根据参数inSandBox值的不同返回不同的ChildConnectionAllocator对象。当参数inSandBox等于true的时候,返回的是静态成员变量sSandboxedChildConnectionAllocator描述的ChildConnectionAllocator对象,而参数inSandBox等于true的时候,返回的是静态成员变量sPrivilegedChildConnectionAllocator描述的ChildConnectionAllocator对象。
其中,ChildProcessLauncher类的静态成员变量sSandboxedChildConnectionAllocator描述的ChildConnectionAllocator对象用来描述运行在沙箱中的Service进程,而ChildProcessLauncher类的静态成员变量sPrivilegedChildConnectionAllocator描述的ChildConnectionAllocator对象用来描述非运行在沙箱中的Service进程。
ChildConnectionAllocator对象的创建过程,即ChildConnectionAllocator类的构造函数的实现,如下所示:
- public class ChildProcessLauncher {
- ......
- private static class ChildConnectionAllocator {
- private final ChildProcessConnection[] mChildProcessConnections;
- ......
- private final ArrayList<Integer> mFreeConnectionIndices;
- private final Object mConnectionLock = new Object();
- private Class<? extends ChildProcessService> mChildClass;
- private final boolean mInSandbox;
- public ChildConnectionAllocator(boolean inSandbox) {
- int numChildServices = inSandbox ?
- MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
- mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices];
- mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
- for (int i = 0; i < numChildServices; i++) {
- mFreeConnectionIndices.add(i);
- }
- setServiceClass(inSandbox ?
- SandboxedProcessService.class : PrivilegedProcessService.class);
- mInSandbox = inSandbox;
- }
- public void setServiceClass(Class<? extends ChildProcessService> childClass) {
- mChildClass = childClass;
- }
- ......
- }
- ......
- }
Browser进程可以启动的最大运行在沙箱中和非运行在沙箱中的Service进程的数量分别为MAX_REGISTERED_SANDBOXED_SERVICES和MAX_REGISTERED_PRIVILEGED_SERVICES,这两个值分别设置为13和3,这意味着Browser进程不能无限地启动Service进程。
ChildConnectionAllocator类的构造函数首先根据允许启动的Service进程的最大数量创建了一个ChildProcessConnectionImpl数组,并且保存在成员变量mChildProcessConnections中。注意,这时候创建的ChildProcessConnectionImpl数组是空的。接下来又创建了一个ArrayList对象,保存在另外一个成员变量mFreeConnectionIndices中,用来描述上述ChildProcessConnectionImpl数组的哪些元素是空的,也就是没有对应的ChildProcessConnectionImpl对象。
最后,ChildConnectionAllocator类的构造函数调有另外一个成员函数setServiceClass设置要启动的Service进程所要运行的Service类,并且该Service类保存在ChildConnectionAllocator类的成员变量mChildClass中。从这里就可以看到,对于运行在沙箱中的Service进程,它所要运行的Service类为SandboxedProcessService,而对于非运行在沙箱中的Service进程,它所要运行的Service类为PrivilegedProcessService。
在我们这个情景中,我们要启动的是一个运行在沙箱中的Render进程,因此,该Render进程要运行的Service类为SandboxedProcessService。
回到ChildProcessLauncher类的静态成员函数allocateConnection中,它根据参数inSandbox获得了一个对应的ChildConnectionAllocator对象之后,就调用它的成员函数allocate获取一个ChildProcessConnectionImpl对象,以便可以通过该ChildProcessConnectionImpl对象启动一个Service进程。
ChildConnectionAllocator类的成员函数allocate的实现如下所示:
- public class ChildProcessLauncher {
- ......
- private static class ChildConnectionAllocator {
- ......
- public ChildProcessConnection allocate(
- Context context, ChildProcessConnection.DeathCallback deathCallback,
- ChromiumLinkerParams chromiumLinkerParams) {
- synchronized (mConnectionLock) {
- if (mFreeConnectionIndices.isEmpty()) {
- Log.w(TAG, "Ran out of service.");
- return null;
- }
- int slot = mFreeConnectionIndices.remove(0);
- assert mChildProcessConnections[slot] == null;
- mChildProcessConnections[slot] = new ChildProcessConnectionImpl(context, slot,
- mInSandbox, deathCallback, mChildClass, chromiumLinkerParams);
- return mChildProcessConnections[slot];
- }
- }
- ......
- }
- ......
- }
ChildConnectionAllocator类的成员函数allocate首先通过成员变量mFreeConnectionIndices描述的一个ArrayList对象找到一个slot,该slot对应另外一个成员变量mChildProcessConnections描述的一个ChildProcessConnectionImpl数组的一个位置,该位置还没有一个对应的ChildProcessConnectionImpl对象。
ChildConnectionAllocator类的成员函数allocate接下来就创建一个ChildProcessConnectionImpl对象,并且将该ChildProcessConnectionImpl对象保存在成员变量mChildProcessConnections描述的一个ChildProcessConnectionImpl数组的slot位置中,表示该位置已经被初始化。相应地,成员变量mFreeConnectionIndices描述的ArrayList对象将描述slot位置的元素移除,表示该slot已经被占用。
最后,前面创建的ChildProcessConnectionImpl对象返回给了调用者,即ChildProcessLauncher类的静态成员函数allocateConnection,后者又将获得的ChildProcessConnectionImpl对象返回给了ChildProcessLauncher类的静态成员函数allocateBoundConnection,最终ChildProcessLauncher类的静态成员函数allocateBoundConnection调用了该ChildProcessConnectionImpl对象的成员函数start,用来启动一个Service进程。
在分析ChildProcessConnectionImpl类的成员函数start之前,我们首先分析ChildProcessConnectionImpl对象的创建过程,即它的构造函数的实现,如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- private final Context mContext;
- private final int mServiceNumber;
- ......
- private final Class<? extends ChildProcessService> mServiceClass;
- ......
- private ChildServiceConnection mInitialBinding = null;
- ......
- ChildProcessConnectionImpl(Context context, int number, boolean inSandbox,
- ChildProcessConnection.DeathCallback deathCallback,
- Class<? extends ChildProcessService> serviceClass,
- ChromiumLinkerParams chromiumLinkerParams) {
- mContext = context;
- mServiceNumber = number;
- ......
- mServiceClass = serviceClass;
- ......
- mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE);
- ......
- }
- ......
- }
ChildProcessConnectionImpl类的构造函数做的最重要的事情是将它要启动的Service进程要运行的Service记录在成员变量mServiceClass中,以及创建一个ChildServiceConnection对象,保存在成员变量mInitialBinding中。在接下来启动Service进程的过程,需要使用到这两个成员变量。
接下来我们就继续分析ChildProcessConnectionImpl类的成员函数start的实现,以便可以了解一个Service进程的启动过程,如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- ......
- @Override
- public void start(String[] commandLine) {
- synchronized (mLock) {
- ......
- if (!mInitialBinding.bind(commandLine)) {
- ......
- } else {
- .....
- }
- ......
- }
- }
- ......
- }
ChildProcessConnectionImpl类的成员函数start调用了成员变量mInitialBinding描述的一个ChildServiceConnection对象的成员函数bind启动一个Service进程,它的实现如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- private final Context mContext;
- ......
- private final int mServiceNumber;
- ......
- private final Class<? extends ChildProcessService> mServiceClass;
- ......
- private class ChildServiceConnection implements ServiceConnection {
- private boolean mBound = false;
- ......
- private Intent createServiceBindIntent() {
- Intent intent = new Intent();
- intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber);
- intent.setPackage(mContext.getPackageName());
- return intent;
- }
- ......
- boolean bind(String[] commandLine) {
- if (!mBound) {
- .....
- final Intent intent = createServiceBindIntent();
- if (commandLine != null) {
- intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
- }
- ......
- mBound = mContext.bindService(intent, this, mBindFlags);
- ......
- }
- return mBound;
- }
- ......
- }
- ......
- }
ChildServiceConnection类的成员函数bind主要就是调用外部类ChildProcessConnectionImpl的成员变量mContext描述的一个Context对象的成员函数bindService启动一个Service,并且在参数commandLine的值不等于null的情况下,将其以EXTRA_COMMND_LINE为键值,保存在用来启动Service的一个Intent中,以便可以将参数commadLine传递给要启动的Service。这里我们需要记住一点,参数commandLine此时包含了一个switches::kProcessType和一个switches:kProcessChannelID选项,前者的值等于switches::kRendererProcess,后者的值为一个UNIX Socket名称。它们分别是在前面分析的RenderProcessHostImpl类的成员函数AppendRendererCommandLine和Init中设置的。
注意,要启动的Service由外部类ChildProcessConnectionImpl的成员变量mServiceClass确定。从前面的分析可以知道,当启动的Service以沙箱模式运行独立的进程中时,ChildProcessConnectionImpl的成员变量mServiceClass描述的类是SandboxedProcessService。
但是从ChildServiceConnection类的成员函数createServiceBindIntent可以知道,实际启动的类并不是SandboxedProcessService,而是一个SandboxedProcessService0、SandboxedProcessService1或者SandboxedProcessService2之类的类。这意味Chromium应用程序要自己定义一系列的SandboxedProcessService<N>类,并且要求这些类像SandboxedProcessService类一样,是从ChildProcessService类继承下来的,并且它们在AndroidManifest.xml文件配置为在单独的进程中启动,以及将android:isolatedProcess属性设置为true。至于这一系列SandboxedProcessService<N>类的个数,就等于前面提到的ChildProcessLauncher类的静态成员变量MAX_REGISTERED_SANDBOXED_SERVICES的值,即13。
这一步执行完成后,Browser进程就会等待指定的Service启动成功。我们知道,当我们通过Context.bindService启动一个Service的时候,需要指定一个ServiceConnection对象。当Service启动起来之后,指定的ServiceConnection对象的成员函数onServiceConnected就会被调用。在我们这个场景中,就是ChildServiceConnection类的成员函数onServiceConnected被调用。
在分析ChildServiceConnection类的成员函数onServiceConnected的实现之前,我们先回到前面分析的ChildProcessLauncher类的成员函数start,它接下来会调用另外一个成员函数triggerConnectionSetup将正在启动的Service进程的启动参数保存起来,如下所示:
- public class ChildProcessLauncher {
- ......
- @VisibleForTesting
- static void triggerConnectionSetup(
- final ChildProcessConnection connection,
- String[] commandLine,
- int childProcessId,
- FileDescriptorInfo[] filesToBeMapped,
- int callbackType,
- final long clientContext) {
- ChildProcessConnection.ConnectionCallback connectionCallback =
- new ChildProcessConnection.ConnectionCallback() {
- @Override
- public void onConnected(int pid) {
- ......
- if (clientContext != 0) { // Will be 0 in Java instrumentation tests.
- nativeOnChildProcessStarted(clientContext, pid);
- }
- }
- };
- ......
- connection.setupConnection(commandLine,
- filesToBeMapped,
- createCallback(childProcessId, callbackType),
- connectionCallback,
- Linker.getSharedRelros());
- }
- ......
- }
ChildProcessLauncher类的成员函数triggerConnectionSetup首先创建了一个ChildProcessConnection.ConnectionCallback对象,并且传递给接下来要调用的参数connection描述的一个ChildProcessConnection对象的成员函数setupConnection。这样当前面要启动的Service启动成功之后,该ChildProcessConnection.ConnectionCallback对象的成员函数onConnected就会被调用。这时候ChildProcessConnection.ConnectionCallback类的成员函数onConnected就会通过JNI方法nativeOnChildProcessStarted通知Browser进程的Native层,它之前要启动的Render进程已经启动完成。
从前面的调用过程可以知道,参数connection指向的实际上是一个ChildProcessConnectionImpl对象,它的成员函数setupConnection的实现如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- ......
- private ConnectionParams mConnectionParams;
- ......
- private ChildProcessConnection.ConnectionCallback mConnectionCallback;
- ......
- @Override
- public void setupConnection(
- String[] commandLine,
- FileDescriptorInfo[] filesToBeMapped,
- IChildProcessCallback processCallback,
- ConnectionCallback connectionCallback,
- Bundle sharedRelros) {
- synchronized (mLock) {
- ......
- mConnectionCallback = connectionCallback;
- mConnectionParams = new ConnectionParams(
- commandLine, filesToBeMapped, processCallback, sharedRelros);
- // Run the setup if the service is already connected. If not, doConnectionSetupLocked()
- // will be called from onServiceConnected().
- if (mServiceConnectComplete) {
- doConnectionSetupLocked();
- }
- ......
- }
- }
- ......
- }
ChildProcessConnectionImpl类的成员函数setupConnection主要做了三件事情。第一件事情是将参数connectionCallback描述的一个ConnectionCallback对象保存在成员变量mConnectionCallback中,这就是为了等到要启动的Service启动成功之后,可以调用该ConnectionCallback对象的成员函数onConnected,以便Browser进程知道它想要启动的Render进程已经启动完成了。第二件事情是将其余的参数封装在一个ConnectionParams对象中,并且这个ConnectionParams对象会保存在ChildProcessConnectionImpl类的成员变量mConnectionParams中。注意,这里有一个重要的参数filesToBeMapped,它里面包含了一个以kPrimaryIPCChannel为键值的UNIX Socket的Client端文件描述符。第三件事情是判断成员变量mServiceConnectComplete的值是否等于true。如果等于true,那么就表示要启动的Service已经启动完成了。在这种情况下,ChildProcessConnectionImpl类的成员函数setupConnection就会调用另外一个成员函数doConnectionSetupLocked对启动起来的Service进程做一些设置工作。
我们假设在ChildProcessConnectionImpl类的成员函数setupConnection的调用期间,要启动的Service还没有启动完成,于是Browser进程就会通过前面提到的ServiceConnection类的成员函数onServiceConnected获得Service启动完成通知。
ServiceConnection类的成员函数onServiceConnected的实现如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- ......
- private IChildProcessService mService = null;
- ......
- private ConnectionParams mConnectionParams;
- ......
- private class ChildServiceConnection implements ServiceConnection {
- ......
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- synchronized (mLock) {
- ......
- mServiceConnectComplete = true;
- mService = IChildProcessService.Stub.asInterface(service);
- // Run the setup if the connection parameters have already been provided. If not,
- // doConnectionSetupLocked() will be called from setupConnection().
- if (mConnectionParams != null) {
- doConnectionSetupLocked();
- }
- ......
- }
- }
- ......
- }
- ......
- }
ServiceConnection类的成员函数onServiceConnected被调用的时候,传递进来的第二个参数service是一个类型为IChildProcessService的Binder代理接口,与它对应的Binder服务运行在已经启动起来的Service进程中。有了这个Binder代理接口之后,Browser进程就可以与启动起来的Service进程进行IPC了。
ServiceConnection类的成员函数onServiceConnected将参数service描述的一个类型为IChildProcessService的Binder代理接口保存在外部类ChildProcessConnectionImpl的成员变量mService之后,就会继续判断外部类ChildProcessConnectionImpl的成员变量mConnectionParams的值是否等于null。如果不等于null,那么就说明它封装了一些参数,这些参数需要用来对刚刚启动起来的Service进程进行设置。在这种情况下,就会调用外部类ChildProcessConnectionImpl的成员函数doConnectionSetupLocked对刚刚启动起来的Service进程进行设置。
ChildProcessConnectionImpl类的成员函数doConnectionSetupLocked的实现如下所示:
- public class ChildProcessConnectionImpl implements ChildProcessConnection {
- ......
- private void doConnectionSetupLocked() {
- ......
- Bundle bundle = new Bundle();
- bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
- FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped;
- ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length];
- for (int i = 0; i < fileInfos.length; i++) {
- ......
- String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
- String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
- if (fileInfos[i].mAutoClose) {
- // Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
- parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd);
- } else {
- try {
- parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd);
- } catch (IOException e) {
- .....
- }
- }
- bundle.putParcelable(fdName, parcelFiles[i]);
- bundle.putInt(idName, fileInfos[i].mId);
- }
- try {
- mPid = mService.setupConnection(bundle, mConnectionParams.mCallback);
- ......
- } catch (android.os.RemoteException re) {
- ......
- }
- ......
- if (mConnectionCallback != null) {
- mConnectionCallback.onConnected(mPid);
- }
- ......
- }
- ......
- }
ChildProcessConnectionImpl类的成员函数doConnectionSetupLocked主要做的事情就是将Browser进程指定的命令行参数和文件描述符等信息,通过成员变量mService描述的一个类型为IChildProcessService的Binder代理接口的成员函数setupConnection,传递给刚刚启动起来的Service进程,也就是一个Render进程。
ChildProcessConnectionImpl类的成员函数doConnectionSetupLocked最后还会判断成员变量mConnectionCallback的值是否等于null。如果不等于null,那么该成员变量就指向了一个ChildProcessConnection.ConnectionCallback对象。在这种情况下,需要调用上述ChildProcessConnection.ConnectionCallback对象的成员函数onConnected,以便通知ChildProcessLauncher类,它所要启动的Service进程已经启动完毕。
前面提到,与ChildProcessConnectionImpl类的成员变量mService所描述的Binder代理对应的Binder服务运行在刚刚启动完成的Service进程中,因此,当我们调用该Binder代理的成员函数setupConnection的时候,实际上调用的是运行在刚刚启动的Service进程的一个Binder服务的成员函数setupConnection。现在,我们就将目光转向Service进程的启动过程,也就是Render进程启动过程的第二部分子过程,如下所示:
图8 Render进程启动的第二部分子过程
我们知道,一个Service在启动的时候,它的成员函数onCreate就会被调用。在我们这个情景中,启动的Service是一个SandboxedProcessService<N>,它是从ChildProcessService继承下来的,我们假设SandboxedProcessService<N>没有重写父类ChildProcessService的成员函数onCreate,那么当SandboxedProcessService<N>在新启动的Service进程中加载的过程中,ChildProcessService类的成员函数onCreate就会被调用。
ChildProcessService类的成员函数onCreate的实现如下所示:
- public class ChildProcessService extends Service {
- ......
- @Override
- public void onCreate() {
- ......
- mMainThread = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- ......
- try {
- LibraryLoader.loadNow(getApplicationContext(), false);
- } catch (ProcessInitException e) {
- ......
- }
- synchronized (mMainThread) {
- while (mCommandLineParams == null) {
- mMainThread.wait();
- }
- }
- LibraryLoader.initialize(mCommandLineParams);
- synchronized (mMainThread) {
- ......
- while (mFileIds == null) {
- mMainThread.wait();
- }
- }
- ......
- int[] fileIds = new int[mFileIds.size()];
- int[] fileFds = new int[mFileFds.size()];
- for (int i = 0; i < mFileIds.size(); ++i) {
- fileIds[i] = mFileIds.get(i);
- fileFds[i] = mFileFds.get(i).detachFd();
- }
- ......
- nativeInitChildProcess(sContext.get().getApplicationContext(),
- ChildProcessService.this, fileIds, fileFds,
- mCpuCount, mCpuFeatures);
- ContentMain.start();
- ......
- } catch (InterruptedException e) {
- ......
- } catch (ProcessInitException e) {
- ......
- }
- }
- }, MAIN_THREAD_NAME);
- mMainThread.start();
- }
- ......
- }
ChildProcessService类的成员函数onCreate启动了一个线程,该线程由成员变量mMainThread描述,它主要做了以下几件事情:
1. 调用LibraryLoader类的静态成员函数loadNow加载了Chromium相关的so库。
2. 等待成员变量mCommandLineParams的值不等于null。当该成员变量的值不等于null的时候,它就指向了一个String数组。该String数组描述的是当前进程的命令行参数,因此,它会通过LibraryLoader类的静态成员函数initialize设置为当前进程的命令参数。
3. 等待成员变量mFileIds的值不等于null。当该成员变量的值不等于null的时候,另外一个成员变量mFileIds的值也不等于null,这时候它们描述了一系列的文件描述符及对应的ID。 这些文件描述符及其对应的ID会通过JNI方法nativeInitChildProcess传递给Native层。
4. 调用ContentMain类的静态成员函数start启动Native层的Chromium。
ChildProcessService类的成员变量mCommandLineParams、mFileIds和mFileIds的值开始的时候都是等于null的,那么它们是什么时候被设置的呢?前面提到,当Browser进程获得了一个可以用来与刚刚启动的Service进程进行Binder IPC的类型为IChildProcessService的Binder代理之后,就会调用该Binder代理的成员函数setupConnection,这会导致运行在刚刚启动的Service进程中的一个对应的Binder服务的成员函数setupConnection被调用。
通过ChildProcessService类的成员函数onBind,我们可以看到上述的Bind服务是什么,如下所示:
- public class ChildProcessService extends Service {
- ......
- @Override
- public IBinder onBind(Intent intent) {
- ......
- return mBinder;
- }
- ......
- }
从这里我们就可以看到,ChildProcessService类的成员函数onBind返回的Binder服务就即为上述提到的Binder服务,它由成员变量mBinder指定,如下所示:
- public class ChildProcessService extends Service {
- ......
- private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() {
- // NOTE: Implement any IChildProcessService methods here.
- @Override
- public int setupConnection(Bundle args, IChildProcessCallback callback) {
- synchronized (mMainThread) {
- // Allow the command line to be set via bind() intent or setupConnection, but
- // the FD can only be transferred here.
- if (mCommandLineParams == null) {
- mCommandLineParams = args.getStringArray(
- ChildProcessConnection.EXTRA_COMMAND_LINE);
- }
- ......
- mFileIds = new ArrayList<Integer>();
- mFileFds = new ArrayList<ParcelFileDescriptor>();
- for (int i = 0;; i++) {
- String fdName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
- + ChildProcessConnection.EXTRA_FILES_FD_SUFFIX;
- ParcelFileDescriptor parcel = args.getParcelable(fdName);
- ......
- mFileFds.add(parcel);
- String idName = ChildProcessConnection.EXTRA_FILES_PREFIX + i
- + ChildProcessConnection.EXTRA_FILES_ID_SUFFIX;
- mFileIds.add(args.getInt(idName));
- }
- mMainThread.notifyAll();
- }
- return Process.myPid();
- }
- }
- ......
- }
从这里就可以看到,当ChildProcessService类的成员变量mBinder描述的Binder服务的成员函数setupConnection被调用的时候,它就会通过参数args获得Browser进程传递过来的命令行参数和文件描述符,分别保存在成员变量mCommandLineParams、mFileIds和mFileFds中,这时候这几个成员变量的值就不等于null。因此当最后ChildProcessService类的成员变量mMainThread描述的线程被唤醒之后,它就能继续往前执行,也就是执行前面提到的ChildProcessService类的JNI方法nativeInitChildProcess和ContentMain类的成员函数start。
接下来我们就分别分析ChildProcessService类的JNI方法nativeInitChildProcess和ContentMain类的成员函数start的实现。
ChildProcessService类的JNI方法nativeInitChildProcess同Native层的函数Java_com_android_org_chromium_content_app_ChildProcessService_nativeInitChildProcess实现,如下所示:
- __attribute__((visibility("default")))
- void
- Java_com_android_org_chromium_content_app_ChildProcessService_nativeInitChildProcess(JNIEnv*
- env, jclass jcaller,
- jobject applicationContext,
- jobject service,
- jintArray extraFileIds,
- jintArray extraFileFds,
- jint cpuCount,
- jlong cpuFeatures) {
- return InitChildProcess(env, jcaller, applicationContext, service,
- extraFileIds, extraFileFds, cpuCount, cpuFeatures);
- }
函数Java_com_android_org_chromium_content_app_ChildProcessService_nativeInitChildProcess调用了另外一个函数InitChildProcess来对当前进程执行一些初始化工作,后者的实现如下所示:
- void InitChildProcess(JNIEnv* env,
- jclass clazz,
- jobject context,
- jobject service,
- jintArray j_file_ids,
- jintArray j_file_fds,
- jint cpu_count,
- jlong cpu_features) {
- std::vector<int> file_ids;
- std::vector<int> file_fds;
- JavaIntArrayToIntVector(env, j_file_ids, &file_ids);
- JavaIntArrayToIntVector(env, j_file_fds, &file_fds);
- InternalInitChildProcess(
- file_ids, file_fds, env, clazz, context, service,
- cpu_count, cpu_features);
- }
函数InitChildProcess首先是将参数j_file_ids和j_file_fds描述的一系列文件描述符及其对应的ID取出来,并且分别保存在本地变量file_ids和file_fds描述的std::vector中,将这两个std::vector传递给另外一个函数InternalInitChildProcess,后者对上述文件描述符及其对应的ID的处理如下所示:
- void InternalInitChildProcess(const std::vector<int>& file_ids,
- const std::vector<int>& file_fds,
- JNIEnv* env,
- jclass clazz,
- jobject context,
- jobject service_in,
- jint cpu_count,
- jlong cpu_features) {
- ......
- // Register the file descriptors.
- // This includes the IPC channel, the crash dump signals and resource related
- // files.
- ......
- for (size_t i = 0; i < file_ids.size(); ++i)
- base::GlobalDescriptors::GetInstance()->Set(file_ids[i], file_fds[i]);
- ......
- }
从这里就可以看到,函数InternalInitChildProcess将参数file_fds描述的文件描述符以参数file_ids描述的ID为键值,注册在当前进程的一个Global Descriptors中。这其中就包含了一个UNIX Socket的Client端文件描述符,该文件描述符的ID值为kPrimaryIPCChannel,并且该UNIX Socket就是Browser进程用来与它请求启动的进程,例如Render进程、GPU进程和Plugin进程,建立IPC通信通道使用的。
前面我们提到,当我们通过IPC::SyncChannel类的静态成员函数Create创建一个Client端的IPC通信通道时,就会从Global Descriptors中取出ID值为kPrimaryIPCChannel的文件描述符,以便可以创建一个用来与Browser进程执行IPC的通信通道。
这一步执行完成之后,接下来我们继续分析ContentMain类的静态成员函数start的实现,如下所示:
- public class ContentMain {
- ......
- public static int start() {
- return nativeStart();
- }
- ......
- private static native int nativeStart();
- }
从这里可以看到,ContentMain类的静态成员函数start调用JNI方法nativeStart启动Native层的Chromium。
ContentMain类的JNI方法nativeStart由Native层的函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart实现,如下所示:
- __attribute__((visibility("default")))
- jint Java_com_android_org_chromium_content_app_ContentMain_nativeStart(JNIEnv*
- env, jclass jcaller) {
- return Start(env, jcaller);
- }
函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart调用了另外一个函数Start执行启动Chromium的工作,后者的实现如下所示:
- static jint Start(JNIEnv* env, jclass clazz) {
- ......
- if (!g_content_runner.Get().get()) {
- ContentMainParams params(g_content_main_delegate.Get().get());
- g_content_runner.Get().reset(ContentMainRunner::Create());
- g_content_runner.Get()->Initialize(params);
- }
- return g_content_runner.Get()->Run();
- }
函数Start首先检查全局变量g_content_runner是否被初始化。如果还没有被初始化,那么就会调用ContentMainRunner类的静态成员函数Create创建一个ContentMainRunnerImpl对象,如下所示:
- ContentMainRunner* ContentMainRunner::Create() {
- return new ContentMainRunnerImpl();
- }
ContentMainRunner类的静态成员函数Create返回的实际上是一个ContentMainRunnerImpl对象,因此,上述全局变量g_content_runner指向的是一个ContentMainRunnerImpl对象。
回到函数Start中,创建了一个ContentMainRunnerImpl对象之后,接下来还会调用它的成员函数Initialize执行初始化工作,以及最后调用它的成员函数Run使得当前进程进入运行状态。
ContentMainRunnerImpl类的成员函数Run的实现如下所示:
- class ContentMainRunnerImpl : public ContentMainRunner {
- public:
- ......
- virtual int Run() OVERRIDE {
- ......
- const CommandLine& command_line = *CommandLine::ForCurrentProcess();
- std::string process_type =
- command_line.GetSwitchValueASCII(switches::kProcessType);
- MainFunctionParams main_params(command_line);
- main_params.ui_task = ui_task_;
- ......
- return RunNamedProcessTypeMain(process_type, main_params, delegate_);
- ......
- }
- ......
- };
ContentMainRunnerImpl类的成员函数Run首先是获得当前进程的命令行参数,并且保存在本地变量comand_line中。从前面的分析可以知道,当前进程的命令行参数是从Browser进程传递过来的,它里面包含了一个kProcessType选项。在我们这个情景中,这个kProcessType选项的值等于kRendererProcess。也就是说,本地变量process_type的值等于kRendererProcess。
最后,ContentMainRunnerImpl类的成员函数Run调用函数RunNamedProcessTypeMain使得当前进程进入运行状态,如下所示:
- int RunNamedProcessTypeMain(
- const std::string& process_type,
- const MainFunctionParams& main_function_params,
- ContentMainDelegate* delegate) {
- static const MainFunction kMainFunctions[] = {
- #if !defined(CHROME_MULTIPLE_DLL_CHILD)
- { "", BrowserMain },
- #endif
- #if !defined(CHROME_MULTIPLE_DLL_BROWSER)
- #if defined(ENABLE_PLUGINS)
- #if !defined(OS_LINUX)
- { switches::kPluginProcess, PluginMain },
- #endif
- { switches::kWorkerProcess, WorkerMain },
- { switches::kPpapiPluginProcess, PpapiPluginMain },
- { switches::kPpapiBrokerProcess, PpapiBrokerMain },
- #endif // ENABLE_PLUGINS
- { switches::kUtilityProcess, UtilityMain },
- { switches::kRendererProcess, RendererMain },
- { switches::kGpuProcess, GpuMain },
- #endif // !CHROME_MULTIPLE_DLL_BROWSER
- };
- ......
- for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {
- if (process_type == kMainFunctions[i].name) {
- if (delegate) {
- int exit_code = delegate->RunProcess(process_type,
- main_function_params);
- #if defined(OS_ANDROID)
- // In Android's browser process, the negative exit code doesn't mean the
- // default behavior should be used as the UI message loop is managed by
- // the Java and the browser process's default behavior is always
- // overridden.
- if (process_type.empty())
- return exit_code;
- #endif
- if (exit_code >= 0)
- return exit_code;
- }
- return kMainFunctions[i].function(main_function_params);
- }
- }
- ......
- }
在函数RunNamedProcessTypeMain内部,定义了一个类型为MainFunction的静态数组kMainFunctions。这个数组记录了每一个进程类型对应的运行入口点函数。例如,对于类型为switches::kRendererProcess的进程来说,即Render进程,它的运行入口点函数为RendererMain。又如,对于类型为switches::kGpuProcess的进程来说,即GPU进程,它的运行入口点函数为RendererMain。注意,Browser进程的类型是一个空字符串,因此它的运行入口点函数为BrowserMain。
从前面的调用过程可以知道,参数delegate指向的ContentMainDelegate对象来自于ContentMainRunnerImpl类的成员变量delegate_。对于非Browser进程来说,ContentMainRunnerImpl类的成员变量delegate_的值是等于NULL的。对于Chromium浏览器来说,它的Browser进程中的ContentMainRunnerImpl类的成员变量delegate_指向的是一个ChromeMainDelegateChromeShellAndroid对象,而对于WebView来说,它的Browser进程中的ContentMainRunnerImpl类的成员变量delegate_指向的是一个AwMainDelegate对象。由于现在我们分析的是非Browser进程,因此,参数delegate的值就等于NULL。
函数RunNamedProcessTypeMain通过遍历数组kMainFunctions,找到与参数process_type对应的进程运行入口点函数,并且调用它。不过,如果参数delegate的值不等于NULL,那么就会先调用它指向的一个ContentMainDelegate对象的成员函数RunProcess。只有当该ContentMainDelegate对象的成员函数RunProcess的返回值为负值的情况下,才会调用在数组中kMainFunctions中找到的运行入口点函数。
但是,对于Android平台的Chromium的Browser进程来说,当它从参数delegate指向的ContentMainDelegate对象的成员函数RunProcess返回来之后,不管返回值是什么,都会直接退出,而不会执行数组kMainFunctions中对应的运行入口点函数。这是因为Android平台的Chromium的Browser进程即为Android应用程序的主进程,它在Java层有着自己的消息循环,因此就不能在Native层进入运行状态,否则的话,就会影响到Android应用程序主进程的正常执行。
由于当前运行的进程为Render进程,即参数delegate的值等于NULL,因此函数RunNamedProcessTypeMain就会直接调用函数RendererMain使得当前进程进入运行状态,如下所示:
- int RendererMain(const MainFunctionParams& parameters) {
- ......
- base::MessageLoop main_message_loop;
- ......
- {
- bool run_loop = true;
- ......
- RenderProcessImpl render_process;
- new RenderThreadImpl();
- ......
- if (run_loop) {
- ......
- base::MessageLoop::current()->Run();
- ......
- }
- }
- return 0;
- }
函数RendererMain首先是创建了一个MessageLoop对象。从前面Chromium多线程模型设计和实现分析一文可以知道,通过默认构造函数创建的MessageLoop对象的消息循环类型为TYPE_DEFAULT,这意味着Render进程的主线程通过类型为MessagePumpDefault的消息泵进入运行状态。
在Render进程的主线程,也就是当前线程,通过类型为MessagePumpDefault的消息泵进入运行状态之前,会创建一个RenderProcessImpl对象和一个RenderThreadImpl对象。前面分析网页不在单独的Render进程中加载时,我们已经分析过RenderProcessImpl对象和RenderThreadImpl对象的创建过程了。其中,RenderProcessImpl对象的创建过程将会触发在当前进程中启动一个IO线程,用来执行IPC,而RenderThreadImpl对象的创建过程会触发在当前进程中创建一个Client端的IPC通信通道,并且该IPC通信通道是通过从Global Descriptors中获取ID值为kPrimaryIPCChannel的文件描述符创建的,具体可以参考前面分析的ChannelPosix类的成员函数CreatePipe的实现。
至此,我们就分析完成了Chromium的Render进程的启动过程。对于Chromium的GPU进程和Plugin进程来说,它们的启动过程也是类似的,最主要的区别就是最后执行函数RunNamedProcessTypeMain时,通过不同的入口点函数进入运行状态。因此,后面我们分析GPU进程和Plugin进程的启动过程时,会跳过中间的过程直接进入到对应的运行入口点函数进行分析。例如,对于GPU进程,直接进入到GpuMain函数分析,而对于Pepper Plugin进程来说,直接进入到PpapiPluginMain函数分析。
回到Chromium的Render进程的启动过程来,总的来说,它主要做的事情就是与Browser进程建立IPC通信通道。这个建立过程如下所示:
1. Browser进程在启动Render进程之前,会创建一个UNIX Socket,并且使用该UNIX Socket的Server端文件描述符创建一个Server端的IPC通信通道。
2. Browser进程在启动Render进程之后,会通过Binder IPC将前面创建的UNIX Socket的Client端文件描述符传递给Render进程。
3. Render进程在进入运行状态之前,会使用前面获得的Client端文件描述符创建一个Client端的IPC通信通道。
由于Browser进程和Render进程创建的IPC通信通道使用的是同一个UNIX Socket的Server端和Client端文件描述符,因此它们就可以通过该UNIX Socket进行相互通信了。Browser进程和Render进程之间是通过传递IPC消息进行通信的,也就是通过UNIX Socket来传递IPC消息。了解这些IPC消息的传递过程,对阅读Chromium的源代码是非常有帮助的,因为Chromium的源代码到处充斥着IPC消息发送、接收和分发处理逻辑,就如同Android系统里面的Binder IPC一样普遍。因此,在接下来的一篇文章中,我们就将分析Chromium的IPC消息发送、接收和分发过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。