您的位置:首页 > 其它

WebKit之Chromium的Render进程分析

2016-01-13 00:22 751 查看
配置多进程的情况下,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类的构造函数的实现如下所示:

[cpp] view
plaincopy

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),

...... {

......

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。

这里我们主要关注类型为SiteInstance的参数instance,它指向的实际上是一个SiteInstanceImpl对象,用来描述Chromium当前加载的一个网站实例。RenderViewHostImpl类的构造函数调用该SiteInstanceImpl对象的成员函数GetProcess获得一个RenderProcessHostImpl对象,如下所示:

[cpp] view
plaincopy

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_;

}

这个函数定义在文件external/chromium_org/content/browser/site_instance_impl.cc中。

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,如下所示:

[cpp] view
plaincopy

RenderWidgetHostImpl::RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,

RenderProcessHost* process,

int routing_id,

bool hidden)

: ......,

process_(process),

...... {

......

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。

参数process指向的RenderProcessHostImpl对象保存在RenderWidgetHostImpl类的成员变量process_中,以后就可以通过RenderWidgetHostImpl类的成员函数GetProcess获得该RenderProcessHostImpl对象,如下所示:

[cpp] view
plaincopy

RenderProcessHost* RenderWidgetHostImpl::GetProcess() const {

return process_;

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。

有了RenderProcessHostImpl之后,接下来我们就开始分析RenderViewHostImpl类的成员函数CreateRenderView创建一个新的Render进程的过程了,如下所示:

[cpp] view
plaincopy

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的实现如下所示:

[cpp] view
plaincopy

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;

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

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的实现如下所示:

[cpp] view
plaincopy

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());

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

IPC::Channel类的静态成员函数GenerateVerifiedChannelID实际上是调用另外一个静态成员函数GenerateUniqueRandomChannelID生成一个唯一的随机名字,后者的实现如下所示:

[cpp] view
plaincopy

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()));

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel.cc中。

从这里就可以看到,这个用来创建UNIX Socket的名字由当前进程的PID、一个顺序数和一个随机数通过"."符号连接而成的。

回到RenderProcessHostImpl类的成员函数Init中,有了用来创建UNIX Socket的名字之后,就可以调用IPC::ChannelProxy类的静态成员函数Create创建一个Channel了,如下所示:

[cpp] view
plaincopy

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对象的创建过程如下所示:

[cpp] view
plaincopy

ChannelProxy::ChannelProxy(Listener* listener,

base::SingleThreadTaskRunner* ipc_task_runner)

: context_(new Context(listener, ipc_task_runner)), did_init_(false) {

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc。

ChannelProxy类的构造函数主要是创建一个ChannelProxy::Context对象,并且将该ChannelProxy::Context对象保存在成员变量context_中。

ChannelProxy::Context对象的创建过程如下所示:

[cpp] view
plaincopy

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()),

...... {

......

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

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进行初始化,如下所示:

[cpp] view
plaincopy

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()));

......

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面的调用过程知道,参数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的实现如下所示:

[cpp] view
plaincopy

void ChannelProxy::Context::CreateChannel(const IPC::ChannelHandle& handle,

const Channel::Mode& mode) {

......

channel_ = Channel::Create(handle, mode, this);

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelProxy::Context类的成员函数CreateChannel调用Channel类的成员函数Create创建了一个IPC通信通道,如下所示:

[cpp] view
plaincopy

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>();

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

从这里可以看到,对于Android平台来说,IPC通信通道通过一个ChannelPosix对象描述,该ChannelPosix对象的创建过程如下所示:

[cpp] view
plaincopy

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)) {

......

}

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

从前面的调用过程可以知道,参数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通信通道,如下所示:

[cpp] view
plaincopy

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;

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

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,它的实现如下所示:

[cpp] view
plaincopy

void ChannelProxy::Context::OnChannelOpened() {

......

if (!channel_->Connect()) {

OnChannelError();

return;

}

......

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面的分析可以知道,ChannelProxy::Context类的成员变量channel_指向的是一个ChannelPosix对象,这里调用它的成员函数Connect将它描述的IPC通信通道交给当前进程的IO线程进行监控。

ChannelPosix类的成员函数Connect的实现如下所示:

[cpp] view
plaincopy

bool ChannelPosix::Connect() {

......

bool did_connect = true;

if (server_listen_pipe_ != -1) {

......

} else {

did_connect = AcceptConnection();

}

return did_connect;

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

当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,它的实现如下所示:

[cpp] view
plaincopy

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;

}

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

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对象而已,如下所示:

[cpp] view
plaincopy

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;

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

前面在分析RenderProcessHostImpl类的成员函数Init时提到,RenderProcessHostImpl类的静态成员变量g_renderer_main_thread_factory描述的是一个函数,通过它可以创建一个类型为InProcessRendererThread的线程。

一个类型为InProcessRendererThread的线程的创建过程如下所示:

[cpp] view
plaincopy

InProcessRendererThread::InProcessRendererThread(const std::string& channel_id)

: Thread("Chrome_InProcRendererThread"), channel_id_(channel_id) {

}

这个函数定义在文件external/chromium_org/content/renderer/in_process_renderer_thread.cc中。

从这里就可以看到,InProcessRendererThread类是从Thread类继承下来的,因此这里调用了Thread类的构造函数。

此外,InProcessRendererThread类的构造函数还会将参数channel_id描述的一个UNIX Socket名称保存在成员变量channel_id_中。从前面的分析可以知道,该名称对应的UNIX Socket已经创建出来了,并且它的Client端文件描述符以该名称为键值,保存在一个Pipe Map中。

回到RenderProcessHostImpl类的成员函数Init中,接下来它会调用前面创建的InProcessRendererThread对象的成员函数StartWithOptions启动一个线程。从前面Chromium多线程模型设计和实现分析一文可以知道,当该线程启动起来之后,并且在进入消息循环之前,会被调用InProcessRendererThread类的成员函数Init执行初始化工作。

InProcessRendererThread类的成员函数Init的实现如下所示:

[cpp] view
plaincopy

void InProcessRendererThread::Init() {

render_process_.reset(new RenderProcessImpl());

new RenderThreadImpl(channel_id_);

}

这个函数定义在文件external/chromium_org/content/renderer/in_process_renderer_thread.cc中。

InProcessRendererThread类的成员函数Init首先在当前进程,即Browser进程,创建了一个RenderProcessImpl对象,保存在成员变量render_process_中,描述一个假的Render进程,接着再创建了一个RenderThreadImpl对象描述当前线程,即当前正在处理的InProcessRendererThread对象描述的线程。

在RenderProcessImpl对象的创建中,会创建一个IO线程,该IO线程负责与Browser进程启动时就创建的一个IO线程执行IPC通信。从图6可以知道,RenderProcessImpl类继承了RenderProcess类,RenderProcess类又继承了ChildProcess类,创建IO线程的工作是从ChildProcess类的构造函数中进行的,如下所示:

[cpp] view
plaincopy

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)));

......

}

这个函数定义在文件external/chromium_org/content/child/child_process.cc中。

从这里就可以看到,ChildProcess类的构造函数调用了成员变量io_thread_描述的一个Thread对象的成员函数StartWithOptions创建了一个IO线程。

回到InProcessRendererThread类的成员函数Init中,在RenderThreadImpl对象的创建过程,会创建一个Client端的IPC通信通道,如下所示:

[cpp] view
plaincopy

RenderThreadImpl::RenderThreadImpl(const std::string& channel_name)

: ChildThread(channel_name) {

......

}

这个函数定义在文件external/chromium_org/content/renderer/render_thread_impl.cc中。

从这里可以看到,RenderThreadImpl类继承了ChildThread类,创建Client端IPC通信通道的过程是在ChildThread类的构造函数中进行的,如下所示:

[cpp] view
plaincopy

ChildThread::ChildThread(const std::string& channel_name)

: channel_name_(channel_name),

..... {

Init();

}

这个函数定义在文件external/chromium_org/content/child/child_thread.cc中。

ChildThread类的构造函数将参数channel_name描述的一个UNIX Socket的名称保存在成员变量channel_name_之后,就调用了另外一个成员函数Init执行创建Client端IPC通信通道的工作,如下所示:

[cpp] view
plaincopy

void ChildThread::Init() {

......

channel_ =

IPC::SyncChannel::Create(channel_name_,

IPC::Channel::MODE_CLIENT,

this,

ChildProcess::current()->io_message_loop_proxy(),

true,

ChildProcess::current()->GetShutDownEvent());

......

}

这个函数定义在文件external/chromium_org/content/child/child_thread.cc中。

Client端IPC通信通道通过IPC::SyncChannel类的静态成员函数Create进行创建,如下所示:

[cpp] view
plaincopy

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();

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

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对象的过程,如下所示:

[cpp] view
plaincopy

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));

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

IPC::SyncChannel类三个参数版本的静态成员函数Create创建了一个SyncChannel对象,并且将该SyncChannel对象返回给调用者。

SyncChannel对象的创建过程如下所示:

[cpp] view
plaincopy

SyncChannel::SyncChannel(

Listener* listener,

base::SingleThreadTaskRunner* ipc_task_runner,

WaitableEvent* shutdown_event)

: ChannelProxy(new SyncContext(listener, ipc_task_runner, shutdown_event)) {

......

StartWatching();

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

从前面的调用过程可以知道,参数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对象的创建过程如下所示:

[cpp] view
plaincopy

SyncChannel::SyncContext::SyncContext(

Listener* listener,

base::SingleThreadTaskRunner* ipc_task_runner,

WaitableEvent* shutdown_event)

: ChannelProxy::Context(listener, ipc_task_runner),

......,

shutdown_event_(shutdown_event),

...... {

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

从这里可以看到,SyncChannel::SyncContext类是从ChannelProxy::Context类继承下来的,因此这里会调用ChannelProxy::Context类的构造函数进行初始化。此外,SyncChannel::SyncContext类的构造函数还会将参数shutdown_event描述的一个ChildProcess关闭事件保存在成员变量shutdown_event_中。

回到SyncChannel类的构造函数中,当它创建了一个SyncChannel::SyncContext对象之后,就使用该SyncChannel::SyncContext对象来初始化父类ChannelProxy,如下所示:

[cpp] view
plaincopy

ChannelProxy::ChannelProxy(Context* context)

: context_(context),

did_init_(false) {

}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc。

注意,参数context的类型虽然为一个ChannelProxy::Context指针,但是它实际上指向的是一个SyncChannel::SyncContext对象,该SyncChannel::SyncContext对象保存在成员变量context_中。

继续回到SyncChannel类的构造函数中,它用一个SyncChannel::SyncContext对象初始化了父类ChannelProxy之后,继续调用另外一个成员函数StartWatching监控我们在前面提到的一个Dispatch Event,如下所示:

[cpp] view
plaincopy

void SyncChannel::StartWatching() {

......

dispatch_watcher_callback_ =

base::Bind(&SyncChannel::OnWaitableEventSignaled,

base::Unretained(this));

dispatch_watcher_.StartWatching(sync_context()->GetDispatchEvent(),

dispatch_watcher_callback_);

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

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的实现如下所示:

[cpp] view
plaincopy

void SyncChannel::SyncContext::OnChannelOpened() {

shutdown_watcher_.StartWatching(

shutdown_event_,

base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled,

base::Unretained(this)));

Context::OnChannelOpened();

}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

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进程,如下所示:

[cpp] view
plaincopy

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;

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

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设置的,如下所示:

[cpp] view
plaincopy

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);

......

}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

从这里就可以看到,选项switches::kProcessType的值设置为kRendererProcess,这表示接下来我们通过ChildProcessLauncher类启动的进程是一个Render进程。

回到RenderProcessHostImpl类的成员函数Init中,当要启动的Render进程的命令行参数准备好之后,接下来就通过ChildProcessLauncher类的构造函数启动一个Render进程,如下所示:

[cpp] view
plaincopy

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);

}

这个函数定义在文件external/chromium_org/content/browser/child_process_launcher.cc中。

ChildProcessLauncher类的构造函数首先创建了一个ChildProcessLauncher::Context对象,保存在成员变量context_中,并且调用该ChildProcessLauncher::Context对象的成员函数Launch启动一个Render进程。

ChildProcessLauncher::Context类的成员函数Launch的实现如下所示:

[cpp] view
plaincopy

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));

}

......

};

这个函数定义在文件external/chromium_org/content/browser/child_process_launcher.cc中。

ChildProcessLauncher::Context类的成员函数Launch通过调用BrowserThread类的静态成员函数PostTask向Browser进程的一个专门用来启动子进程的BrowserThread::PROCESS_LAUNCHER线程的消息队列发送一个任务,该任务绑定了ChildProcessLauncher::Context类的成员函数LaunchInternal。因此,接下来ChildProcessLauncher::Context类的成员函数LaunchInternal就会在BrowserThread::PROCESS_LAUNCHER线程中执行,如下所示:

[cpp] view
plaincopy

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));

......

}

......

};

这个函数定义在文件external/chromium_org/content/browser/child_process_launcher.cc中。

从前面的调用过程可以知道,参数delegate指向的一个SandboxedProcessLauncherDelegate对象封装了前面创建的一个ChannelProxy对象,通过调用它的成员函数GetIpcFd可以获得它所封装的ChannelProxy对象描述的一个UNIX Socket的Client端文件描述符。该Client端文件描述符接下来以kPrimaryIPCChannel为键值封装在一个FileDescriptorInfo对象,并且该FileDescriptorInfo对象最终会保存在本地变量files_to_register描述的一个std::vector。该std::vector最后又会传递给函数StartChildProcess,后者负责执行启动Render进程的工程。

函数StartChildProcess的实现如下所示:

[cpp] view
plaincopy

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)));

}

这个函数定义在文件external/chromium_org/content/browser/android/child_process_launcher_android.cc中。

函数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层的接口来启动一个子进程,如下所示:

[cpp] view
plaincopy

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);

}

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ChildProcessLauncher_jni.h中。

函数Java_ChildProcessLauncher_start调用参数env描述的一个JNI环境对象调用了Java层的ChildProcessLauncher类的成员函数start执行启动子进程的操作。

Java层的ChildProcessLauncher类的成员函数start的实现如下所示:

[java] view
plaincopy

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的实现如下所示:

[java] view
plaincopy

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;

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

ChildProcessLauncher类的静态成员函数allocateBoundConnection首先调用静态成员函数getLinkerParamsForNewConnection获得Chromium相关的库链接信息,并且保存在一个ChromiumLinkerParams对象中,接下来再调用成员函数allocateConnection创建一个ChildProcessConnection对象,并且通过该ChildProcessConnection对象的成员函数start启动一个Service进程。

关于Chromium相关库的链接信息,涉及到Chromium相关库的加载机制,以后我们分析WebView的加载过程时,再详细分析。接下来我们就主要分析ChildProcessLauncher类的静态成员函数allocateConnection的实现,如下所示:

[java] view
plaincopy

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);

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

ChildProcessLauncher类的静态成员函数allocateConnection首先创建了一个ChildProcessConnection.DeathCallback对象,该ChildProcessConnection.DeathCallback对象是用来监控接下来要启动的Service进程的生命周期的。一旦要启动的Service进程退出,该ChildProcessConnection.DeathCallback对象的成员函数onChildProcessDied就会被调用,并且用来执行清理工作。

ChildProcessLauncher类的静态成员函数allocateConnection接下来调用静态成员函数getConnectionAllocator获得一个ChildConnectionAllocator对象,并且通过该ChildConnectionAllocator对象的成员函数allocate获得一个ChildProcessConnection对象。

ChildProcessLauncher类的静态成员函数getConnectionAllocator的实现如下所示:

[java] view
plaincopy

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;

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

ChildProcessLauncher类的静态成员函数getConnectionAllocator根据参数inSandBox值的不同返回不同的ChildConnectionAllocator对象。当参数inSandBox等于true的时候,返回的是静态成员变量sSandboxedChildConnectionAllocator描述的ChildConnectionAllocator对象,而参数inSandBox等于true的时候,返回的是静态成员变量sPrivilegedChildConnectionAllocator描述的ChildConnectionAllocator对象。

其中,ChildProcessLauncher类的静态成员变量sSandboxedChildConnectionAllocator描述的ChildConnectionAllocator对象用来描述运行在沙箱中的Service进程,而ChildProcessLauncher类的静态成员变量sPrivilegedChildConnectionAllocator描述的ChildConnectionAllocator对象用来描述非运行在沙箱中的Service进程。

ChildConnectionAllocator对象的创建过程,即ChildConnectionAllocator类的构造函数的实现,如下所示:

[java] view
plaincopy

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;

}

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

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的实现如下所示:

[java] view
plaincopy

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];

}

}

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

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对象的创建过程,即它的构造函数的实现,如下所示:

[java] view
plaincopy

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);

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

ChildProcessConnectionImpl类的构造函数做的最重要的事情是将它要启动的Service进程要运行的Service记录在成员变量mServiceClass中,以及创建一个ChildServiceConnection对象,保存在成员变量mInitialBinding中。在接下来启动Service进程的过程,需要使用到这两个成员变量。

接下来我们就继续分析ChildProcessConnectionImpl类的成员函数start的实现,以便可以了解一个Service进程的启动过程,如下所示:

[java] view
plaincopy

public class ChildProcessConnectionImpl implements ChildProcessConnection {

......

@Override

public void start(String[] commandLine) {

synchronized (mLock) {

......

if (!mInitialBinding.bind(commandLine)) {

......

} else {

.....

}

......

}

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

ChildProcessConnectionImpl类的成员函数start调用了成员变量mInitialBinding描述的一个ChildServiceConnection对象的成员函数bind启动一个Service进程,它的实现如下所示:

[java] view
plaincopy

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;

}

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

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进程的启动参数保存起来,如下所示:

[java] view
plaincopy

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());

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。

ChildProcessLauncher类的成员函数triggerConnectionSetup首先创建了一个ChildProcessConnection.ConnectionCallback对象,并且传递给接下来要调用的参数connection描述的一个ChildProcessConnection对象的成员函数setupConnection。这样当前面要启动的Service启动成功之后,该ChildProcessConnection.ConnectionCallback对象的成员函数onConnected就会被调用。这时候ChildProcessConnection.ConnectionCallback类的成员函数onConnected就会通过JNI方法nativeOnChildProcessStarted通知Browser进程的Native层,它之前要启动的Render进程已经启动完成。

从前面的调用过程可以知道,参数connection指向的实际上是一个ChildProcessConnectionImpl对象,它的成员函数setupConnection的实现如下所示:

[java] view
plaincopy

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();

}

......

}

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

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的实现如下所示:

[java] view
plaincopy

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();

}

......

}

}

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

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的实现如下所示:

[java] view
plaincopy

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);

}

......

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessConnectionImpl.java中。

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的实现如下所示:

[java] view
plaincopy

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();

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java。

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服务是什么,如下所示:

[java] view
plaincopy

public class ChildProcessService extends Service {

......

@Override

public IBinder onBind(Intent intent) {

......

return mBinder;

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java。

从这里我们就可以看到,ChildProcessService类的成员函数onBind返回的Binder服务就即为上述提到的Binder服务,它由成员变量mBinder指定,如下所示:

[java] view
plaincopy

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();

}

}

......

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java。

从这里就可以看到,当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实现,如下所示:

[cpp] view
plaincopy

__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);

}

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ChildProcessService_jni.h中。

函数Java_com_android_org_chromium_content_app_ChildProcessService_nativeInitChildProcess调用了另外一个函数InitChildProcess来对当前进程执行一些初始化工作,后者的实现如下所示:

[cpp] view
plaincopy

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);

}

这个函数定义在文件external/chromium_org/content/app/android/child_process_service.cc中。

函数InitChildProcess首先是将参数j_file_ids和j_file_fds描述的一系列文件描述符及其对应的ID取出来,并且分别保存在本地变量file_ids和file_fds描述的std::vector中,将这两个std::vector传递给另外一个函数InternalInitChildProcess,后者对上述文件描述符及其对应的ID的处理如下所示:

[cpp] view
plaincopy

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]);

......

}

这个函数定义在文件external/chromium_org/content/app/android/child_process_service.cc中。

从这里就可以看到,函数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的实现,如下所示:

[cpp] view
plaincopy

public class ContentMain {

......

public static int start() {

return nativeStart();

}

......

private static native int nativeStart();

}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ContentMain.java中。

从这里可以看到,ContentMain类的静态成员函数start调用JNI方法nativeStart启动Native层的Chromium。

ContentMain类的JNI方法nativeStart由Native层的函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart实现,如下所示:

[cpp] view
plaincopy

__attribute__((visibility("default")))

jint Java_com_android_org_chromium_content_app_ContentMain_nativeStart(JNIEnv*

env, jclass jcaller) {

return Start(env, jcaller);

}

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentMain_jni.h中。

函数Java_com_android_org_chromium_content_app_ContentMain_nativeStart调用了另外一个函数Start执行启动Chromium的工作,后者的实现如下所示:

[cpp] view
plaincopy

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();

}

这个函数定义在文件external/chromium_org/content/app/android/content_main.cc中。

函数Start首先检查全局变量g_content_runner是否被初始化。如果还没有被初始化,那么就会调用ContentMainRunner类的静态成员函数Create创建一个ContentMainRunnerImpl对象,如下所示:

[cpp] view
plaincopy

ContentMainRunner* ContentMainRunner::Create() {

return new ContentMainRunnerImpl();

}

这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

ContentMainRunner类的静态成员函数Create返回的实际上是一个ContentMainRunnerImpl对象,因此,上述全局变量g_content_runner指向的是一个ContentMainRunnerImpl对象。

回到函数Start中,创建了一个ContentMainRunnerImpl对象之后,接下来还会调用它的成员函数Initialize执行初始化工作,以及最后调用它的成员函数Run使得当前进程进入运行状态。

ContentMainRunnerImpl类的成员函数Run的实现如下所示:

[cpp] view
plaincopy

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_);

......

}

......

};

这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

ContentMainRunnerImpl类的成员函数Run首先是获得当前进程的命令行参数,并且保存在本地变量comand_line中。从前面的分析可以知道,当前进程的命令行参数是从Browser进程传递过来的,它里面包含了一个kProcessType选项。在我们这个情景中,这个kProcessType选项的值等于kRendererProcess。也就是说,本地变量process_type的值等于kRendererProcess。

最后,ContentMainRunnerImpl类的成员函数Run调用函数RunNamedProcessTypeMain使得当前进程进入运行状态,如下所示:

[cpp] view
plaincopy

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);

}

}

......

}

这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

在函数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使得当前进程进入运行状态,如下所示:

[cpp] view
plaincopy

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;

}

这个函数定义在文件external/chromium_org/content/renderer/renderer_main.cc。

函数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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: