轉:
Chromium的Render程序啟動過程分析
chromium browser process 與 render process 間通信通道的建立
羅升陽 2015-08-24 01:06:51 24918 收藏 4
分類專欄: 老羅的Android之旅
版權
在配置多程序的情況下,Chromium的網頁渲染和JS執行在一個單獨的程序中進行。這個程序稱為Render程序,由Browser程序啟動。在Android平台中,Browser程序就是Android應用程式的主程序,而Render程序就是Android應用程式的Service程序,它們通過UNIX Socket進行通信。本文就詳細分析Chromium的Browser程序啟動Render程序的過程。
老羅的新浪微網誌:http://weibo.com/shengyangluo,歡迎關注!
《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!
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),
...... {
......
}
這個函數定義在檔案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 {
這個函數定義在檔案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程序。
接下來,我們主要分析第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());
這個函數定義在檔案external/chromium_org/ipc/ipc_channel_posix.cc中。
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()));
這個函數定義在檔案external/chromium_org/ipc/ipc_channel.cc中。
從這裡就可以看到,這個用來建立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) {
這個函數定義在檔案external/chromium_org/ipc/ipc_channel_proxy.cc。
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);
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的實作如下所示:
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通信通道,如下所示:
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對象的建立過程如下所示:
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) {
local_pipe = PipeMap::GetInstance()->Lookup(pipe_name_);
if (mode_ & MODE_CLIENT_FLAG) {
if (local_pipe != -1) {
......
local_pipe = HANDLE_EINTR(dup(local_pipe));
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_);
}
// 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;
pipe_ = local_pipe;
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;
這個函數定義在檔案external/chromium_org/ipc/ipc_channel_proxy.cc中。
從前面的分析可以知道,ChannelProxy::Context類的成員變量channel_指向的是一個ChannelPosix對象,這裡調用它的成員函數Connect将它描述的IPC通信通道交給目前程序的IO線程進行監控。
ChannelPosix類的成員函數Connect的實作如下所示:
bool ChannelPosix::Connect() {
bool did_connect = true;
if (server_listen_pipe_ != -1) {
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,它的實作如下所示:
bool ChannelPosix::AcceptConnection() {
base::MessageLoopForIO::current()->WatchFileDescriptor(
pipe_, true, base::MessageLoopForIO::WATCH_READ, &read_watcher_, this);
QueueHelloMessage();
// 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;
NOTREACHED();
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對象而已,如下所示:
前面在分析RenderProcessHostImpl類的成員函數Init時提到,RenderProcessHostImpl類的靜态成員變量g_renderer_main_thread_factory描述的是一個函數,通過它可以建立一個類型為InProcessRendererThread的線程。
一個類型為InProcessRendererThread的線程的建立過程如下所示:
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的實作如下所示:
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類的構造函數中進行的,如下所示:
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通信通道,如下所示:
RenderThreadImpl::RenderThreadImpl(const std::string& channel_name)
: ChildThread(channel_name) {
這個函數定義在檔案external/chromium_org/content/renderer/render_thread_impl.cc中。
從這裡可以看到,RenderThreadImpl類繼承了ChildThread類,建立Client端IPC通信通道的過程是在ChildThread類的構造函數中進行的,如下所示:
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通信通道的工作,如下所示:
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進行建立,如下所示:
scoped_ptr<SyncChannel> SyncChannel::Create(
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);
這個函數定義在檔案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對象的過程,如下所示:
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對象的建立過程如下所示:
SyncChannel::SyncChannel(
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對象的建立過程如下所示:
SyncChannel::SyncContext::SyncContext(
: 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程序,如下所示:
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);
這個函數定義在檔案external/chromium_org/content/browser/child_process_launcher.cc中。
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線程中執行,如下所示:
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,
CommandLine* cmd_line) {
int ipcfd = delegate->GetIpcFd();
std::vector<FileDescriptorInfo> files_to_register;%
————————————————
版權聲明:本文為CSDN部落客「羅升陽」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/luoshengyang/article/details/47433765