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

  • 相关阅读:
    套接字I/O函数write/read writev/readv send/recv sendto/recvfrom sendmsg/recvmsg
    套接字之recvmsg系统调用
    套接字之recv系统调用
    套接字之recvfrom系统调用
    套接字之sendmsg系统调用
    套接字之send系统调用
    数据类型
    简单的c程序分析
    c语言函数分析
    堆栈图
  • 原文地址:https://www.cnblogs.com/bigben0123/p/13355057.html
Copyright © 2020-2023  润新知