天天看點

Chromium的Render程序啟動過程分析

轉:

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