Render 进程启动过程分析1
在配置多进程的情况下,Chromium 的网页渲染和 JS 执行在一个单独的进程中进行。这个进程称为 Render 进程,由 Browser 进程启动。在 Android 平台中,Browser 进程就是 Android 应用程序的主进程,而 Render 进程就是 Android 应用程序的 Service 进程,它们通过 UNIX Socket 进行通信。本文就详细分析 Chromium 的 Browser 进程启动 Render 进程的过程。
Render 进程启动完成之后,将与 Browser 进程建立以下的 IPC 通道,如图1所示:
在 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 对象的类关系图如下所示:
RenderViewHostImpl 类多重继承了 RenderViewHost 类和 RenderWidgetHostImpl 类,后面这两个类又有一个共同的虚基类 RenderWidgetHost,该虚基类又实现了一个 Sender 接口,该接口定义了一个重要的成员函数 Send,用来执行 IPC 通信。
RenderWidgetHostImpl 类还实现了一个 Listener 接口,该接口定义了两个重要的成员函数 OnMessageReceived 和 OnChannelConnected。前者用来接收 IPC 消息并且进行分发,后者用来在 IPC 通道建立时执行一些初始化工作。
实际上,当 RenderViewHostImpl 类需要发起一次 IPC 时,它是通过父类 RenderWidgetHostImpl 的成员变量 process_指向的一个 RenderProcessHost 接口进行的。该 RenderProcessHost 接口指向的实际上是一个 RenderProcessHostImpl 对象,它的类关系图如图 3 所示:
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 对象的类关系图如下所示:
RenderViewImpl 类多重继承了 RenderView 类和 RenderWidget 类。RenderView 类实现了 Sender 接口。RenderWidget 类也实现了 Sender 接口,同时也实现了 Listener 接口,因此它可以用来发送和接收 IPC 消息。
RenderWidget 类实现了接口 Sender 的成员函数 Send,RenderViewImpl 类就是通过它来发送 IPC 消息的。RenderWidget 类的成员函数 Send 又是通过一个用来描述 Render 线程的 RenderThreadImpl 对象来发送 IPC 类的。这个 RenderThreadImpl 对象可以通过调用 RenderThread 类的静态成员函数 Get 获得。
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 对象的类关系图,如下所示:
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 进程启动过程的第一部分子过程如下所示:
上图列出的仅仅是一些核心过程,接下来我们通过代码来分析这些核心过程。
我们首先了解什么情况下 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),
...... {
......
}
这个函数定义在文件 external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc 中。
这里我们主要关注类型为 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_;
}
这个函数定义在文件 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,如下所示:
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 对象,如下所示:
RenderProcessHost* RenderWidgetHostImpl::GetProcess() const {
return process_;
}
这个函数定义在文件 external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc 中。
有了 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;
}
这个函数定义在文件 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 进程。