现在我要关注的问题是:
一个RenderProcess的消息怎么发送到BrowserProcess进程中的,这个消息走了那些路?注意,所说的IO线程是Browser进程中的IO线程,非Render进程中发的IO。所谓的IO线程主要用来接受网络消息和进程间的消息,我先把网络消息抛到一边,单独分析下RenderProcess进程给BrowserProcess的消息是怎样流动的?
通常把网络包分为上行和下行,在这里我把RenderProcess到BrowserProcess称为上行消息,BrowserProcess到RenderProcess的消息称为下行消息,这章只讨论上行消息。
需要特别注意的几个类分别是:
SiteInstance: 每个SiteInstance对象对应于一个RenderProcessHost,当创建RenderViewHost时会传递一个SiteInstance实例,该RenderViewHost保存在SiteInstance实例的RenderProcessHost对象中。SiteInstance对应于四种启动方式。RenderProcessHost:每个RenderProcessHost对应于一个RenderProcess进程,RenderProcess进程负责渲染所有的RenderProcessHost中的RenderViewHost的页面。
WebContents: WebContents包括了RenderViewHost和NavigationController。
RenderViewHostManager:存在于WebContents主要保存一个RenderViewHost对象,但是有一种情况比较复杂,那就是当一个Interstitial webpage存在时,它负责加载Interstitial webpage,并切换到用户请求页面。关于Interstitial webpage请参看上上篇文章的解释。由于这种复杂度,这里没有直接采用RenderViewHost而是采用RenderViewHostManager来控制管理。
NavigationController: 存在于WebContents中,控制一个TAB的后退,前进,刷新,加载等操作。
RenderViewHost:对应于一个HTML页面
Channel: 用于进程间通讯。
ChannelProxy: 对Channel的代理,并且附加了一些过滤信息
ChannelProxy::Context: 封装的Channel
SyncChannel: 扩展了ChannelProxy,使之能支持同步信息。
SyncChannel::SyncContext: 对ChannelProxy::Context的同步支持的封装。
ResourceDispatcherHost:所有消息将通过ResourceDispatcherHost来过滤,ResourceMessageFilter的消息也会通过ResourceDispatcherHost,ResourceDispatcherHost还过滤网络消息,并且做相应的转发。
ResourceMessageFilter: 过滤ChannelProxy的一些相关信息。
MessageLoopForIO: 提供支持APC 事件通知的消息循环,这里是支持命名管道的消息。
先大致走一趟上行消息的简单流程,然后结合代码仔细分析。
消息最先达到的地方当然是Channel这个对象了,Channel是被ChannelProxy::Context所封装,所以消息会被分派到ChannelProxy::Context中,ChannelProxy::Context对象是存在于ChannelProxy中的,ChannelProxy::Contex又把消息提交给了ChannelProxy,ChannelProxy先过滤消息,如果消息没有被过滤掉,把消息推给RenderProcessHost,然后由RenderProcessHost把消息分配给每个相应的RenderViewHost。
上面是上行消息的一个简单的路由过程。其中细节很多,问题也是很多。
0:一个TAB页面是怎么被创建的出来的?
1:ChannelProxy怎么由UI线程给分派到IO线程中去运行?
2:一个命名管道的消息怎么被通知到BrowserProcess进程中的?
3:消息是通过什么方法逐渐往上一层传递的?
4:ResourceMessageFilter是怎么加进ChannelProxy中的,它会过滤掉什么样的消息,什么消息它会继续转发?
5:ResourceMessageFilter和ResourceDispatcherHost有什么瓜葛?
一:先来分析第一个问题:
一个TAB页面是怎么被创建的出来的?
当用户点击浏览器上的添加按钮或者是从一个网页的一个连接来打开一个新的页面,这样一个新的页面就被创建了。一个WebContents对象对应于一个HTML页面和关联该页面的标签,创建一个页面其实就是创建一个WebContents对象。
上面提到过RenderViewHostManager 存在于WebContents中,RenderViewHostManager 是用来保RenderViewHost对象的,RenderViewHost对象也是在RenderViewHostManager中被创建的。WebContents的构造函数中会传递一个SiteInstance参数, RenderViewHostManager会创建RenderViewHost,然后把创建的RenderViewHost对象关联到SiteInstance相对应的RenderProcessHost中。
仔细看RenderProcessHost的结构会发现,RenderProcessHost中有两个方法:
void Attach(IPC::Channel::Listener* listener, int routing_id);
void Release(int listener_id);
用这两个方法来关联和取消关联,具体细节参见前面的UML 类图。
这样一个RenderViewHost的对象就被创建并关联到相应的RenderProcessHost中了。
二:分析第二个问题:
ChannelProxy怎么由UI线程给分派到IO线程中去运行?
根据chrome官方技术文档可以看出来,每个RenderProcessHost对应一个RenderProcess,他们之间通过ChannelProxy来通讯的,也就是说RenderProcessHost是通过ChannelProxy和RenderProcess的ChannelProxy来通讯的。RenderProcessHost和RenderProcess分别都拥有一个ChannelProxy,RenderProcessHost和RenderProcess都是通过他们的大使ChannelProxy来沟通的。先抛开RenderProcess和它的大使ChannelProxy,专心来分析RenderProcessHost和它的大使ChannelProxy。
先来分析RenderProcessHost是怎么创建的?
上面分析过,RenderProcessHost是从SiteInstance中获取的,先分析一段SiteInstance获取RenderProcessHost的代码:
RenderProcessHost* SiteInstance::GetProcess()
{
RenderProcessHost* process = NULL;
if (process_host_id_ != -1)
// 首先从一个全局的RenderProcessHost对象列表中获取和当前ID相匹配的RenderProcessHost对象
process = RenderProcessHost::FromID(process_host_id_);
//如果没有,先恢复,如果恢复不了,那么创建一个行的对象
if (!process)
{
//判断是否恢复以前创建的RenderProcessHost对象
if (RenderProcessHost::ShouldTryToUseExistingProcessHost())
process = RenderProcessHost::GetExistingProcessHost(browsing_instance_->profile());
//恢复不了,那么创建一个行的对象
if (!process)
process = new RenderProcessHost(browsing_instance_->profile());
// 更新这个RenderProcessHost对象的ID
process_host_id_ = process->host_id();
// Make sure the process starts at the right max_page_id
process->UpdateMaxPageID(max_page_id_);
}
DCHECK(process);
//返回改对象
return process;
}
这样一个对象就创建了,这里采用的策略是lazycreate,即是到了需要创建的时候才创建该对象。
注意了,channel并不是在这个时候被创建的,channel是在加载时被创建的,
RenderProcessHost中的Init函数中创建的。
RenderProcess也是在该函数中创建的,关于该函数,很长,我选出其中比较重要的几段,为了不影响阅读,我把一些sandbox的代码给去掉了,简化后的代码如下:
bool RenderProcessHost::Init() {
// 如果 channel_已经被创建了,那么直接返回,不需要后续处理了。
if (channel_.get())
return true;
// 安装ResourceMessageFilter
base::Thread* io_thread = g_browser_process->io_thread();
scoped_refptr<ResourceMessageFilter> resource_message_filter =
new ResourceMessageFilter(g_browser_process->resource_dispatcher_host(),
PluginService::GetInstance(),
g_browser_process->print_job_manager(),
host_id_,
profile_,
widget_helper_,
profile_->GetSpellChecker());
CommandLine browser_command_line;
// 安装IPC channel
std::wstring channel_id = GenerateRandomChannelID(this);
channel_.reset(
new IPC::SyncChannel(channel_id, IPC::Channel::MODE_SERVER, this,
resource_message_filter,
io_thread->message_loop(), true,
g_browser_process->shutdown_event()));
{
一堆生成命令行的代码、被我注释掉了。
}
//是否是单进程模式
bool run_in_process = RenderProcessHost::run_renderer_in_process();
if (run_in_process)
{
base::Thread *render_thread = new RendererMainThread(channel_id);
base::Thread::Options options;
options.message_loop_type = MessageLoop::TYPE_IO;
render_thread->StartWithOptions(options);
}
else
{ HANDLE process;
if (!process_util::LaunchApp(cmd_line, false, false, &process))
return false;
process_.set_handle(process);
watcher_.StartWatching(process_.handle(), this);
}
}
return true;
}
当我开始启动一个浏览器的时候,调试堆栈如下,可以看出Init函数是什么时候第一次被调用的。
上面的代码还是运行在UI线程中的,我还没有看出来channel对象是怎么给 分派带IO线程中去运行的?
这需要进一步跟踪代码SyncChannel的创建方式,
SyncChannel是派生之ChannelProxy类的,在ChannelProxy的构造函数中会调用这个函数:
void ChannelProxy::Init(const std::wstring& channel_id, Channel::Mode mode,
MessageLoop* ipc_thread_loop, bool create_pipe_now)
{
//先创建一个Channel对象
context_->CreateChannel(channel_id, mode);
//然后发送消息到IO线程中去执OnOpenChannel的命令,
context_->ipc_message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
context_.get(), &Context::OnOpenChannel));
}
关于PostTask 中的NewRunnableMethod如果时间不多可以先理解为把 xxx转换为Task对象供线程的消息循环来调用,后面会重点介绍,主要用了一些泛型编程上面的伎俩,对C++泛型编程比较了解的同学就会觉得很简单。要不理解不多,目前可以不做过多的了解也没问题。
上面那个PostTask的意思可以理解为:当执行到这个Task时,消息循环就会调用Context::OnOpenChannel这个函数来执行。
继续分析当消息循环执行接这段Context::OnOpenChannel的代码时
// 调用 IPC::Channel thread,这段代码是运行在IO线程中的了,因为context_ipc_message_loop 就是IO线程的消息循环。
void ChannelProxy::Context::OnOpenChannel()
{
if (!channel_->Connect())
{
OnChannelError();
return;
}
}
继续往下分析:
bool Channel::Connect()
{
if (pipe_ == INVALID_HANDLE_VALUE)
return false;
//这个函数是为IO线程所专门定制的。当命名管道的事件到来时将调用this对象中的几个函数,其实就是Listener的接口,因为Channel是派生于Listener接口的,有消息通知时就会调用该接口的相关函数。
MessageLoopForIO::current()->RegisterIOHandler(pipe_, this);
//现在的目的已经达到了,下面是一堆细节代码,暂时不作分析了,
// Check to see if there is a client connected to our pipe...
if (waiting_connect_)
ProcessConnection();
if (!input_state_.is_pending) {
// Complete setup asynchronously. By not setting input_state_.is_pending
// to true, we indicate to OnIOCompleted that this is the special
// initialization signal.
MessageLoopForIO::current()->PostTask(FROM_HERE, factory_.NewRunnableMethod(
&Channel::OnIOCompleted, &input_state_.overlapped, 0, 0));
}
if (!waiting_connect_)
ProcessOutgoingMessages(NULL, 0);
return true;
}
这个一个ChannelProxy的对象就由UI线程被送到IO线程中去执行了。
这个问题得到了圆满解答。
三:现在开始第三个问题:
消息是通过什么方法逐渐往上一层传递的?
这个被chrome用烂的手法---委托, 不得不再次重提。
一个来之RenderProcess上行消息在chrome中传递的所有路径都用到了改手法。
来点题外话,前几天和同事们在讨论老外的软件代码有个优点------那就是大量使用同一种风格的代码。这样有一个最大的好处,那就是大大提高了代码的可读性。理解了一种模式,阅读代码所向霹雳,因为都是这种风格,我都知道了。反观中国软件,我所接触到的基本每个人的风格不一样,导致一个项目的代码融入了N种风格,这样就苦了后来维护代码的人了。
Chrome中的这种委托的伎俩基本每个角落都能找到它的影子。现在要分析的这个问题就是一个委托使用问题,理解了委托,要了解的就是流程问题了。 还得结合代码和我上章画的类图来分析这个问题。
先看ChannelProxy这个类的组成:
class ChannelProxy : public Message::Sender {
public:
class MessageFilter : public base::RefCountedThreadSafe<MessageFilter> {
public:
virtual ~MessageFilter() {}
virtual void OnFilterAdded(Channel* channel) {}
virtual void OnFilterRemoved() {}
virtual void OnChannelConnected(int32 peer_pid) {}
virtual void OnChannelClosing() {}
virtual bool OnMessageReceived(const Message& message) {
return false;
}
};
ChannelProxy(const std::wstring& channel_id, Channel::Mode mode,
Channel::Listener* listener, MessageFilter* filter,
MessageLoop* ipc_thread_loop);
~ChannelProxy() {
Close();
}
void Close();
virtual bool Send(Message* message);
void AddFilter(MessageFilter* filter);
void RemoveFilter(MessageFilter* filter);
protected:
Channel::Listener* listener() const { return context_->listener(); }
class Context;
ChannelProxy(const std::wstring& channel_id, Channel::Mode mode,
MessageLoop* ipc_thread_loop, Context* context,
bool create_pipe_now);
class Context : public base::RefCountedThreadSafe<Context>,
public Channel::Listener {
public:
Context(Channel::Listener* listener, MessageFilter* filter,
MessageLoop* ipc_thread);
virtual ~Context() { }
protected:
// IPC::Channel::Listener methods:
virtual void OnMessageReceived(const Message& message);
virtual void OnChannelConnected(int32 peer_pid);
virtual void OnChannelError();
Channel::Listener* listener() const { return listener_; }
const std::wstring& channel_id() const { return channel_id_; }
// Gives the filters a chance at processing |message|.
// Returns true if the message was processed, false otherwise.
bool TryFilters(const Message& message);
private:
friend class ChannelProxy;
// Create the Channel
void CreateChannel(const std::wstring& id, const Channel::Mode& mode);
// Methods called via InvokeLater:
void OnOpenChannel();
void OnCloseChannel();
void OnSendMessage(Message* message_ptr);
void OnAddFilter(MessageFilter* filter);
void OnRemoveFilter(MessageFilter* filter);
void OnDispatchMessage(const Message& message);
void OnDispatchConnected(int32 peer_pid);
void OnDispatchError();
MessageLoop* ipc_message_loop() const { return ipc_message_loop_; }
void clear() { listener_ = NULL; }
MessageLoop* listener_message_loop_;
Channel::Listener* listener_;
std::vector<scoped_refptr<MessageFilter>> filters_;
MessageLoop* ipc_message_loop_;
Channel* channel_;
std::wstring channel_id_;
};
Context* context() { return context_; }
private:
void Init(const std::wstring& channel_id, Channel::Mode mode,
MessageLoop* ipc_thread_loop, bool create_pipe_now);
scoped_refptr<Context> context_;
};
对外开放了四个函数分别是:
void Close();
virtual bool Send(Message* message);
void AddFilter(MessageFilter* filter);
void RemoveFilter(MessageFilter* filter);
只有一个对象 :scoped_refptr<Context> context_;
这个类是个名副其实的代理类,所有操作都封装到了Contenxt里面去了。
先来看看这个ChannelProxy这个对象是怎么创建的?
在bool RenderProcessHost::Init()中,我们可以找到下面的代码:
channel_.reset(new IPC::SyncChannel(channel_id, IPC::Channel::MODE_SERVER, this, resource_message_filter,
io_thread->message_loop(), true, g_browser_process->shutdown_event()));
先看一张类图:
虚线的部分是聚合了外部的接口,等有需要时就调用改接口,这也就是传说中的委托.可以看到图中有三条虚线:
第一个是channel聚合了Context的Listener。
第二条是Context聚合RenderProcessHost的Listener。
第三条是Context聚合RescouceMessageFilter的接口。
注意:rederprocesshost在创建的时候将自己作为委托的实现方传到了channelproxy::context, channelproxy::context在创建channel 时,将自己作为参数传入。
我们先来找到消息其实的地方,上面讨论过了,所有消息的入口函数都被映射到Channel::OnIOCompleted这个函数中。这个函数是运行在IO线程中的。
OnIOCompleted会把消息分派到第一个第一个聚合了Context的Listener,即是Context中的实现。
所有消息都被转发到了Context的Listener中的三个函数中,Context相当于一个路由器,它会过滤掉一些消息,通过第三条是Context聚合RescouceMessageFilter的接口,没有被过滤的会通过第二条是Context聚合RenderProcessHost的Listener把消息转发给RenderProcessHost,然后由RenderProcessHost负责把消息分别发送到相应的RenderViewHost中。
看下Context中的路由代码:
void ChannelProxy::Context::OnMessageReceived(const Message& message) {
if (TryFilters(message))
return;
listener_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
this, &Context::OnDispatchMessage, message));
}
void ChannelProxy::Context::OnDispatchMessage(const Message& message) {
if (!listener_)
return;
listener_->OnMessageReceived(message);
}
这样一个上行消息的路由就基本完成了。
关于ResourceMessageFilter和ResourceDispatcherHost的问题,有兴趣的同学可以自己跟踪源码去看看,我就不做介绍了。
总结:整个ipc的流程是依靠委托实现的,进程通信的的中间点,及消息中心是在MessageLoopForIO::IOHandler的OnIOCompleted。
主进程的renderprocesshost在创建管道服务端的时候将自己作为委托实现传到了channelproxy,channelproxy(实际是channelproxy::context)封装及代理了channel。
在OnIOCompleted接受的消息后通过委托传到renderpresshost,renderprocesshost自己维护了一个map,map其实就是viewid和委托(如:renderviewhost ,renderwighthost等)。
子进程中的主线程是处理进程间通信的额,子线程也有一个messageroute对象,用来记录委托和id的,委托有renderview等。注意就完成了进程间的通信,消息由控制消息和路由消息。