轉:
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 {
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進程。
接下來,我們主要分析第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()),
...... {
......
}
這個函數定義在文件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進行初始化,如下所示:
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的實現如下所示:
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)) {
......
}
}
這個函數定義在文件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通信通道,如下所示:
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,它的實現如下所示:
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) {
......
} 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,它的實現如下所示:
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對象而已,如下所示:
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的線程的創建過程如下所示:
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(
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對象的過程,如下所示:
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對象的創建過程如下所示:
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對象的創建過程如下所示:
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,如下所示:
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,如下所示:
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的實現如下所示:
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進程,如下所示:
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設置的,如下所示:
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進程,如下所示:
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));
}
......
};
這個函數定義在文件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線程中執行,如下所示:
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;%
————————————————
版權聲明:本文為CSDN博主「羅升陽」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/luoshengyang/article/details/47433765
羅升陽 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 {
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進程。
接下來,我們主要分析第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()),
...... {
......
}
這個函數定義在文件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進行初始化,如下所示:
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的實現如下所示:
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)) {
......
}
}
這個函數定義在文件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通信通道,如下所示:
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,它的實現如下所示:
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) {
......
} 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,它的實現如下所示:
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對象而已,如下所示:
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的線程的創建過程如下所示:
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(
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對象的過程,如下所示:
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對象的創建過程如下所示:
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對象的創建過程如下所示:
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,如下所示:
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,如下所示:
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的實現如下所示:
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進程,如下所示:
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設置的,如下所示:
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進程,如下所示:
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));
}
......
};
這個函數定義在文件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線程中執行,如下所示:
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;%
————————————————
版權聲明:本文為CSDN博主「羅升陽」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/luoshengyang/article/details/47433765