Chromium的IPC消息发送、接收和分发机制分析

由于Chromium采用多进程架构,因此会涉及到进程间通信问题。通过前面一文的学习,我们知道Browser进程在启动Render进程的过程中会建立一个以UNIX Socket为基础的IPC通道。有了IPC通道之后,接下来Browser进程与Render进程就以消息的形式进行通信。我们将这种消息称为IPC消息,以区别于线程消息循环中的消息。本文就分析Chromium的IPC消息发送、接收和分发机制。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

Chromium提供了一个IPC::Sender和IPC::Listener接口,分别用来抽象IPC消息的发送者和接收者。IPC消息从IPC::Sender传递到IPC::Listener的过程如图1所示:

图1 IPC消息从发送者传递到接收者的过程

以Render进程向Browser进程发送IPC消息的情景为例,IPC::Sender即为RenderThreadImpl,而IPC::Listener即为RenderProcessHostImpl。IPC::Sender既可以通过ChannelProxy发送IPC消息,也可以通过SyncChannel发送IPC消息。两者的区别在于后者可以发送同步IPC消息。所谓同步IPC消息就是发送者将IPC消息发送给接收者之后,会等待接收者回复一个IPC消息。虽然原则上我们建议使用异步IPC消息,但是某些情况下,使用同步IPC消息还是可以简化编程模型的。

ChannelProxy或者SyncChannel接收到发送IPC消息的请求之后,将要发送的IPC消息转发给ChannelPosix处理。ChannelPosix会调用系统API接口write或者sendmsg将要发送的消息写入到一个UNIX Socket去。这个UNIX Socket即为用来创建IPC通道的那个UNIX Socket,具体可以参考前面Chromium的Render进程启动过程分析一文。调用write和调用sendmsg发送消息有什么不一样的呢?我们知道,通过设置cmsghdr,sendmsg可以将一个文件描述符从一个进程传递到另外一个进程,这个功能类似Binder IPC。因此,当要发送的IPC消息包含有文件描述符时,ChannelPosix就会通过sendmsg将其写入到UNIX Socket去。

由于IPC::Listener通过IO线程监控了用来创建IPC通道的UNIX Socket,因此当该UNIX Socket有新的数据可读时,也就是IPC::Sender往该UNIX Socket写入IPC消息之后,IPC::Listener就可以通过Libevent获得一个EV_READ事件通知。该EV_READ事件通知被ChannelPosix捕获之后,会交给ChannelReader处理。ChannelReader通过解析UNIX Socket流数据获得IPC消息之后,再透过ChannelProxy或者SyncChannel将获得的IPC消息转发给IPC::Listener处理。

有某些情况下,IPC消息不会传递给IPC::Listener处理。我们可以定义一个Message Filter,并且注册到ChannelProxy或者SyncChannel。当ChannelProxy或者SyncChannel接收到一个IPC消息的时候,它们会首先循问注册到它们里面的Message Filter是否需要处理该IPC消息。如果需要处理,那么就将交给它们处理,并且不再传递给IPC::Listener处理。另一方面,当IPC消息传递到IPC::Listener的时候,IPC::Listener也只会处理自己感兴趣的那部分IPC消息。对于自己不感兴趣的IPC消息,IPC::Listener会将转发给注册到它内部的一系列Route处理。这意味着我们可以注册一些Route到IPC::Listener中,以便可以处理自己感兴趣的IPC消息。

从上面的分析就可以看出,Chromium的进程间通信主要就是涉及到IPC消息的发送、接收和分发过程。其中,IPC消息是主角,因此,在分析IPC消息的发送、接收和分发之前,我们首先分析一下IPC消息的格式。

IPC消息由IPC::Message类描述,它由三部分组成,分别为Header、Payload和File Descriptor Set,如图2所示:

图2 IPC消息格式

IPC消息的Header的定义如下所示:

class IPC_EXPORT Message : public Pickle {
 .....

 protected:
  ......

#pragma pack(push, 4)
  struct Header : Pickle::Header {
    int32 routing;  // ID of the view that this message is destined for
    uint32 type;    // specifies the user-defined message type
    uint32 flags;   // specifies control flags for the message
#if defined(OS_POSIX)
    uint16 num_fds; // the number of descriptors included with this message
    uint16 pad;     // explicitly initialize this to appease valgrind
#endif
  };
#pragma pack(pop)

  ......
};

这个类定义在文件external/chromium_org/ipc/ipc_message.h中。

IPC::Message::Header类继承了IPC::Pickle::Header类,后者的定义如下所示:

class BASE_EXPORT Pickle {
 public:
  ......

  // Payload follows after allocation of Header (header size is customizable).
  struct Header {
    uint32 payload_size;  // Specifies the size of the payload.
  };

  ......
};

这个类定义在文件external/chromium_org/base/pickle.h中。

IPC消息的Header中正是由于有payload_size这个类型为uint32的成员变量,使得我们可以从UNIX Socket流中找到IPC消息边界。这一点很容易理解,因为IPC消息的Header的长度是固定的,再加上Payload的大小,就可以得到一个完整的IPC消息的长度。

IPC消息的Header中的routing是一个类型为int32的成员变量,它用来定义IPC消息要发送给哪一个Route处理。我们可以在执行IPC的两个进程里面定义两个Route,这两个Route具有相同的Route ID。这两个Route发送的IPC消息的Header里面的routing的值就设置为它们的Route ID,这样它们就可以相互通信了。

IPC消息的Header中的routing可以设置一个特殊的值MSG_ROUTING_CONTROL,这时候这个routing描述的不是一个Route的ID,而是表示对应的IPC消息是一个控制消息。

IPC消息的Header中的type是一个类型为uint32的成员变量,它用来定义IPC消息的类型。IPC消息类型由CLASS和LINE两部分组成。其中,CLASS占据高16位,称为IPC消息类别,LINE占据低16位,称为行号。后者只要是用来保证每一个IPC消息的type都是唯一的。

IPC消息的type通过IPC_MESSAGE_ID、IPC_MESSAGE_ID_CLASS和IPC_MESSAGE_ID_LINE三个宏描述,它们的定义如下所示:

// Message IDs
// Note: we currently use __LINE__ to give unique IDs to messages within
// a file.  They‘re globally unique since each file defines its own
// IPC_MESSAGE_START.
#define IPC_MESSAGE_ID() ((IPC_MESSAGE_START << 16) + __LINE__)
#define IPC_MESSAGE_ID_CLASS(id) ((id) >> 16)
#define IPC_MESSAGE_ID_LINE(id) ((id) & 0xffff)

这三个宏定义在文件external/chromium_org/ipc/ipc_message_macros.h中。

其中,IPC消息的Header中的type的CLASS值是最重要的,用来区为不同类型的消息。Chromium通过枚举类型IPCMessageStart定义了IPC消息的CLASS值,如下所示:

enum IPCMessageStart {
  AutomationMsgStart = 0,
  FrameMsgStart,
  ViewMsgStart,
  ......,
  UtilityPrintingMsgStart,
  AecDumpMsgStart,
  LastIPCMsgStart  // Must come last.
};

这个枚举类型定义在文件external/chromium_org/ipc/ipc_message_start.h。

这意味着我们在一个文件中定义一个IPC消息时,需要将宏IPC_MESSAGE_START指定为枚举类型IPCMessageStart的其中一个值,以便可以通过宏IPC_MESSAGE_ID来定义它的type。

IPC消息的Header中的flags是一个类型为uint32的成员变量,用来描述IPC消息的引用计数和控制信息。其中,高24位描述的是IPC消息的引用计数,而低8位描述的是IPC消息的控制信息,它的取值范围如下所示:

class IPC_EXPORT Message : public Pickle {
 public:
  ......

  // Bit values used in the flags field.
  // Upper 24 bits of flags store a reference number, so this enum is limited to
  // 8 bits.
  enum {
    PRIORITY_MASK     = 0x03,  // Low 2 bits of store the priority value.
    SYNC_BIT          = 0x04,
    REPLY_BIT         = 0x08,
    REPLY_ERROR_BIT   = 0x10,
    UNBLOCK_BIT       = 0x20,
    PUMPING_MSGS_BIT  = 0x40,
    HAS_SENT_TIME_BIT = 0x80,
  };

  ......
};

这个枚举类型定义在文件external/chromium_org/ipc/ipc_message.h中。

在这些控制信息中,与同步IPC消息相关的三个控制信息分别是SYNC_BIT、REPLY_BIT、UNBLOCK_BIT和PUMPING_MSGS_BIT。其中,SYNC_BIT描述的是一个同步IPC消息,REPLY_BIT描述的是对一个同步IPC消息的回复。IPC::Sender发送了一个同步IPC消息之后,在等待对方回复的过程中,如果收到了一个设置了UNBLOCK_BIT控制信息的IPC消息,那么它就需要先处理该IPC消息,然后再继续等待回复。另一方面,如果IPC::Sender在等待回复的过程中,收到了一个没有设置UNBLOCK_BIT控制信息的IPC消息,那么该消息就会被延后处理,也就是会等到IPC::Sender获得回复之后才会处理。

PUMPING_MSGS_BIT表示IPC::Sender是否需要在一个嵌套的消息循环中等待回复。为什么IPC::Sender需要在一个嵌套的消息循环中等待回复呢?想象这样一个场景。IPC::Sender发送一个同步消息给IPC::Listener之后,IPC::Listener要经过若干中间处理后,才可以回复前面IPC::Sender发送过来的同步IPC消息。在上述的中间处理过程,IPC::Listener可能会要求IPC::Sender弹出一个对话框获取用户的输入。试想,如果现在IPC::Sender没有运行在一个嵌套的消息循环中,那么它就没机会获取用户的输入,因为此时IPC::Sender所运行在的线程正处于睡眠等待状态。

我们在设置一个同步IPC消息的PUMPING_MSGS_BIT时,需要同时设置一个Pump Messages Event,只有当该Pump Messages Event被Signal时,IPC::Sender才会进入到一个嵌套的消息循环中去行运。关于Chromium的嵌套消息循环,可以参考前面Chromium多线程模型设计和实现分析一文。

IPC消息的Header中的num_fds是一个类型为uint16的成员变量,用来描述IPC消息携带的文件描述符的数量。前面我们提到,我们可以通过IPC消息在进程间传递文件描述符。这些描述符是通过sendmsg接口传递的,它们并不占据UNIX Socket流位置,而是通过cmsghdr传递的。IPC::Listener接收到携带有文件描述符的IPC消息之后,会将传递过来的文件描述符保存在IPC::Message类的成员变量file_descriptor_set_描述的一个FileDescriptorSet。这个成员变量的定义如下所示:

class IPC_EXPORT Message : public Pickle {
 ......

 protected:
  ......

  // The set of file descriptors associated with this message.
  scoped_refptr<FileDescriptorSet> file_descriptor_set_;

  ......
};

这个成员变量定义在文件external/chromium_org/ipc/ipc_message.h中。

同样,一个待发送的IPC消息如果需要携带文件描述符,那么这些文件描述符也会事先保存在上述成员变量中。

通过前面的分析可以知道,IPC消息的Header是通过结构体定义的。由于不同的编译器对结构体的对齐处理不同,并且IPC消息最终是要序列化到UNIX Socket流去传递给对方处理的,因此为了保证通信的双方能够以相同的对齐方式取出一个IPC消息的Header,Chromium通过#pragma pack属性告诉编译器,以4字节方式对齐IPC消息的Header的大小。我们看到,在IPC消息的Header中,除了num_fds这个成员变量是2个字节大小之外,其余的成员变量都已经是4个字节大小的。为了使得整个IPC消息的Heade的大小是4字节对齐的,Chromium在定义IPC::Message::Header的时候,在最后添加了一个类型为uint16的pad成员变量,目的就是为了把整个IPC消息的Heade的大小对齐到4字节的。

IPC::Message类描述的IPC消息默认情况下是异步的,为了方便描述同步IPC消息,Chromium定义了另外一个类IPC::SyncMessage,用来描述同步IPC消息,IPC::SyncMessage类是从IPC::Message类继承下来的,它在构造函数里面会同时设置IPC消息的flags的SYNC_BIT和UNBLOCK_BIT位为1,如下所示:

SyncMessage::SyncMessage(
    int32 routing_id,
    uint32 type,
    PriorityValue priority,
    MessageReplyDeserializer* deserializer)
    : Message(routing_id, type, priority),
      deserializer_(deserializer),
      pump_messages_event_(NULL)
      {
  set_sync();
  set_unblock(true);

  ......
}

这个类定义在文件external/chromium_org/ipc/ipc_sync_message.cc。

IPC::SyncMessage类的构造函数调用了从父类IPC::Message继承下来的成员函数set_sync和set_unblock就是设置IPC消息的flags的SYNC_BIT和UNBLOCK_BIT位为1。

从这里还可以看到,IPC::SyncMessage类有一个成员变量pump_messages_event_。如前所述,当一个IPC::SyncMessage的PUMPING_MSGS_BIT位被设置时,需要同时设置一个Pump Messages Event。这个Pump Messages Event就是保存在IPC::SyncMessage类的成员变量pump_messages_event_中,如下所示:

class IPC_EXPORT SyncMessage : public Message {
 public:
  ......

  void set_pump_messages_event(base::WaitableEvent* event) {
    pump_messages_event_ = event;
    if (event) {
      header()->flags |= PUMPING_MSGS_BIT;
    } else {
      header()->flags &= ~PUMPING_MSGS_BIT;
    }
  }

  ......
};

这个函数定义在文件external/chromium_org/ipc/ipc_sync_message.h中。

Chromium在external/chromium_org/ipc/ipc_message_macros.h文件中定义了一系列的IPC_MESSAGE_CONTROL/IPC_SYNC_MESSAGE_CONTROL、IPC_MESSAGE_ROUTED/IPC_SYNC_MESSAGE_ROUTED宏方便定义不同类型的消息。接下来我们以FrameMsg_ChildFrameProcessGone的定义为例说明IPC_MESSAGE_ROUTED宏的使用过程。

FrameMsg_ChildFrameProcessGone类描述的是一个Render进程结束消息,它的定义如下所示:

#define IPC_MESSAGE_START FrameMsgStart

......

// Notifies the embedding frame that the process rendering the child frame‘s
// contents has terminated.
IPC_MESSAGE_ROUTED0(FrameMsg_ChildFrameProcessGone)

这个类定义在文件external/chromium_org/content/common/frame_messages.h中。

我们首先注意到,上述头文件一开始就将宏IPC_MESSAGE_START的值设置为FrameMsgStart,这意味在该文件中定义的IPC消息的type的CLASS值均为FrameMsgStart。

FrameMsg_ChildFrameProcessGone类通过宏IPC_MESSAGE_ROUTED0定义,如下所示:

#define IPC_MESSAGE_ROUTED0(msg_class)   IPC_MESSAGE_DECL(EMPTY, ROUTED, msg_class, 0, 0, (), ())

这个宏定义在文件external/chromium_org/ipc/ipc_message_macros.h中。

通过宏IPC_MESSAGE_ROUTED0定义的IPC消息是不带参数,它进一步通过宏IPC_MESSAGE_DECL定义,如下所示:

#define IPC_MESSAGE_DECL(sync, kind, msg_class,                                                        in_cnt, out_cnt, in_list, out_list)                    IPC_##sync##_##kind##_DECL(msg_class, in_cnt, out_cnt, in_list, out_list)     IPC_MESSAGE_EXTRA(sync, kind, msg_class, in_cnt, out_cnt, in_list, out_list)

这个宏定义在文件external/chromium_org/ipc/ipc_message_macros.h中。

其中,IPC_##sync##_##kind##_DECL宏开之后得到另外一个宏IPC_EMPTY_ROUTED_DECL,它的定义如下所示:

#define IPC_EMPTY_ROUTED_DECL(msg_class, in_cnt, out_cnt, in_list, out_list)    class IPC_MESSAGE_EXPORT msg_class : public IPC::Message {                     public:                                                                        typedef IPC::Message Schema;                                                  enum { ID = IPC_MESSAGE_ID() };                                               msg_class(int32 routing_id)                                                       : IPC::Message(routing_id, ID, PRIORITY_NORMAL) {}                        static void Log(std::string* name, const Message* msg, std::string* l);     };

这个宏定义在文件external/chromium_org/ipc/ipc_message_macros.h中。

这样,我们就得到FrameMsg_ChildFrameProcessGone类的定义,其中,它描述的IPC消息的type被设置为内部定义的ID值。这个ID值就是通过我们前面描述的IPC_MESSAGE_ID宏定义的。

FrameMsg_ChildFrameProcessGone类的实现由另外一个宏IPC_MESSAGE_EXTRA定义,这个宏我们留给读者自行分析。

理解了IPC消息的Header的定义之后,接下来我们就以它定义里面的type和routing来分析IPC消息的Filter和Route的注册过程,其中,Filter是基于type注册的,而Route是基于routing注册的。

当我们创建了一个ChannelProxy或者SyncChannel对象之后,就可以调用它的成员函数AddFilter注册一个Filter。其中,SyncChannel类的成员函数AddFilter是从父类ChannelProxy继承下来的,因此接下来我们就分析ChannelProxy类的成员函数AddFilter的实现,如下所示:

void ChannelProxy::AddFilter(MessageFilter* filter) {
  DCHECK(CalledOnValidThread());

  context_->AddFilter(filter);
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面Chromium的Render进程启动过程分析一文可以知道,ChannelProxy类的成员变量context_指向的是一个ChannelProxy::Context对象,这里调用它的成员函数AddFilter注册一个Filter。注意,IPC消息的Filter通过类MessageFilter类描述。

ChannelProxy::Context类的成员函数AddFilter的实现如下所示:

// Called on the listener‘s thread
void ChannelProxy::Context::AddFilter(MessageFilter* filter) {
  base::AutoLock auto_lock(pending_filters_lock_);
  pending_filters_.push_back(make_scoped_refptr(filter));
  ipc_task_runner_->PostTask(
      FROM_HERE, base::Bind(&Context::OnAddFilter, this));
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelProxy类的成员函数AddFilter首先将要注册的Filter保存在成员变量pending_filters_描述的一个std::vector中,接下来通过成员变量ipc_task_runner描述的一个SingleThreadTaskRunner对象向IO线程发送一个Task,该Task绑定了ChannelProxy::Context类的成员函数OnAddFilter,它的实现如下所示:

// Called on the IPC::Channel thread
void ChannelProxy::Context::OnAddFilter() {
  ......
  std::vector<scoped_refptr<MessageFilter> > new_filters;
  {
    base::AutoLock auto_lock(pending_filters_lock_);
    new_filters.swap(pending_filters_);
  }

  for (size_t i = 0; i < new_filters.size(); ++i) {
    filters_.push_back(new_filters[i]);

    message_filter_router_->AddFilter(new_filters[i].get());

    ......
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelProxy::Context类的成员函数OnAddFilter将前面保存在成员变量pending_filters_描述的一个std::vector中的Filter依次注册到另外一个成员变量message_filter_router描述的一个MessageFilterRouter对象中,这是通过调用MessageFilterRouter类的成员函数AddFilter实现的。

MessageFilterRouter类的成员函数AddFilter的实现如下所示:

void MessageFilterRouter::AddFilter(MessageFilter* filter) {
  // Determine if the filter should be applied to all messages, or only
  // messages of a certain class.
  std::vector<uint32> supported_message_classes;
  if (filter->GetSupportedMessageClasses(&supported_message_classes)) {
    DCHECK(!supported_message_classes.empty());
    for (size_t i = 0; i < supported_message_classes.size(); ++i) {
      const int message_class = supported_message_classes[i];
      ......
      message_class_filters_[message_class].push_back(filter);
    }
  } else {
    global_filters_.push_back(filter);
  }
}

这个函数定义在文件/external/chromium_org/ipc/message_filter_router.cc中。

我们自定义的Filter类都是从MessageFilter类继承下来的。我们可以通过重写父类MessageFilter的成员函数GetSupportedMessageClasses,告诉MessageFilterRouter类的成员函数AddFilter,我们注册的Filter所关注的IPC消息的Header的type的CLASS值。这样MessageFilterRouter类的成员函数AddFilter就会将要注册的Filter按照其关注的IPC消息的Header的type的CLASS值保存在成员变量message_class_filters_描述的一个MessageFilters数组中。

另一方面,如果要注册的Filter没有重写父类MessageFilter的成员函数GetSupportedMessageClasses,那么它就将会被保存在MessageFilterRouter类的成员变量global_filters_描述的一个全局MessageFilters中。

当ChannelProxy接收到一个IPC消息时,首先会转发给它的成员变量MessageFilterRouter描述的一个MessageFilterRouter对象处理。这个MessageFilterRouter对象首先会依次转发给保存在它的成员变量global_filters_描述的一个全局MessageFilters对象中的Filter处理。如果没有Filter处理接收到的IPC消息,那么再根据接收到的IPC消息的Header的type的CLASS值,在成员变量message_class_filters_描述的一个MessageFilters数组快速地检查到有没有Filter关注该IPC消息。如果有的话,那么就转给它们处理。这个过程我们在接下来分析IPC消息的分发过程时就会看到。

上面描述的就是IPC消息Filter的注册过程,它是基于IPC消息的Header的type值进行的。接下来我们继续分析IPC消息Route的注册过程。IPC消息Route是注册在IPC::Listener中的。以Render进程向Browser进程发送IPC消息的情景为例,IPC::Listener即为RenderProcessHostImpl。RenderProcessHostImpl类提供了一个成员函数AddRoute,用来注册Route,它的实现如下所示:

void RenderProcessHostImpl::AddRoute(
    int32 routing_id,
    IPC::Listener* listener) {
  listeners_.AddWithID(listener, routing_id);
}

这个函数定义在external/chromium_org/content$/browser/renderer_host/render_process_host_impl.cc中。

与IPC消息的Filter不一样,IPC消息的Route是通过类IPC::Listener来描述的。这些IPC::Listener以参数routing_id为键值保存在RenderProcessHostImpl类的成员变量listeners_描述的一个Map中。参数routing_id描述的就是IPC消息的Header的routing值。从图1可以知道,IPC消息的Filter是先于Route获得机会处理接收到的IPC消息的。这一点我们在接下来分析IPC消息的分发过程时就会看到。

了解了IPC消息的Filter和Route的注册过程之后,接下来我们就开始分析IPC消息的发送过程。

以Render进程向Browser进程发送IPC消息的情景为例,IPC::Sender即为RenderThreadImpl,它提供了一个成员函数Send,用来向Browser进程发送一个IPC消息的,如下所示:

bool RenderThreadImpl::Send(IPC::Message* msg) {
  ......

  bool rv = ChildThread::Send(msg);

  return rv;
}

这个函数定义在文件external/chromium_org/content/renderer/render_thread_impl.cc中。

RenderThreadImpl类的成员函数Send调用了父类ChildThread的成员函数Send来发送一个IPC消息,后者的实现如下所示:

bool ChildThread::Send(IPC::Message* msg) {
  ......

  return channel_->Send(msg);
}

这个函数定义在文件external/chromium_org/content/child/child_thread.cc中。

从前面Chromium的Render进程启动过程分析一文可以知道,ChildThread类的成员变量channel_指向的是一个SyncChannel对象,这里调用它的成员函数Send发送一个IPC消息。

SyncChannel类的成员函数Send的实现如下所示:

bool SyncChannel::Send(Message* message) {
  ......

  if (!message->is_sync()) {
    ChannelProxy::Send(message);
    return true;
  }

  // *this* might get deleted in WaitForReply.
  scoped_refptr<SyncContext> context(sync_context());
  if (context->shutdown_event()->IsSignaled()) {
    ......
    delete message;
    return false;
  }

  SyncMessage* sync_msg = static_cast<SyncMessage*>(message);
  context->Push(sync_msg);
  WaitableEvent* pump_messages_event = sync_msg->pump_messages_event();

  ChannelProxy::Send(message);

  // Wait for reply, or for any other incoming synchronous messages.
  // *this* might get deleted, so only call static functions at this point.
  WaitForReply(context.get(), pump_messages_event);

  return context->Pop();
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

如果要发送的消息不是一个同步IPC消息,那么就直接调用父类ChannelProxy的成员函数Send发送该消息。这说明ChannelProxy只可以用来发送异步IPC消息,而SyncChannel类既可以发送同步IPC消息,也可以发送异步IPC消息。

SyncChannel类的成员函数Send接下来的处理逻辑都是针对同步IPC消息的,过程如下所示:

1. 判断IPC消息接收者是否已经退出了。如果已经退出,那么再给它送同步IPC消息就没有意义 了,因为发送者永远都不会得回复。

从前面Chromium的Render进程启动过程分析一文可以知道,在创建一个SyncChannel对象的过程,需要指定一个Shut Down Event。这个Shut Down Event最后保存在SyncChannel类的成员变量context_指向的一个SyncChannel::SyncContext对象中。这个SyncChannel::SyncContext对象可以通过调用SyncChannel类的成员函数snc_context获得。获得了一个SyncChannel::SyncContext对象之后,调用它的成员函数shutdwon_event就可以获得上述的Shut Down Event。

从前面Chromium的Render进程启动过程分析一文还可以知道,当Render进程和Browser进程建立好IOC通道之后,SyncChannel::SyncContext类会对设置给它的Shut Down Event进行监控。一旦该Shut Down Event被Signaled,就意味着要发送的同步IPC消息的接收者已经退出了。因此这时候就没有必要再向接收者发送同步IPC消息,于是SyncChannel类的成同函数Send就直接返回了。

2. 调用SyncChannel::SyncContext类的成员函数Push将发送的同步IPC消息保存起来,以便等到该消息的回复过来时,可以进行相应的处理。

SyncChannel::SyncContext类的成员函数Push的实现如下所示:

void SyncChannel::SyncContext::Push(SyncMessage* sync_msg) {
  ......
  PendingSyncMsg pending(SyncMessage::GetMessageId(*sync_msg),
                         sync_msg->GetReplyDeserializer(),
                         new WaitableEvent(true, false));
  base::AutoLock auto_lock(deserializers_lock_);
  deserializers_.push_back(pending);
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::SyncContext类的成员函数Push将即将要发送的同步IPC消息的ID取出来,也就是将该消息的Header的type值取出来,并且会创建一个WaitableEvent对象,最后将前面获得的ID和WaitableEvent对象封装在一个PendingSyncMsg对象中,并且将这个PendingSyncMsg对象增加到成员变量deserializers_描述的一个std::deque末尾。

3. 检查要发送的同步IPC消息是否设置有Pump Messages Event。如果设置有,那么就将它获取回来,并且保存在本地变量pump_messages_event中。

4. 调用父类ChannelProxy的成员函数Send发送参数message描述的同步IPC消息。

5. 调用SyncChannel类的成员函数WaitForReply等待IPC消息接收者发回一个回复消息。

SyncChannel类的成员函数WaitForReply的实现如下所示:

void SyncChannel::WaitForReply(
    SyncContext* context, WaitableEvent* pump_messages_event) {
  context->DispatchMessages();
  while (true) {
    WaitableEvent* objects[] = {
      context->GetDispatchEvent(),
      context->GetSendDoneEvent(),
      pump_messages_event
    };

    unsigned count = pump_messages_event ? 3: 2;
    size_t result = WaitableEvent::WaitMany(objects, count);
    if (result == 0 /* dispatch event */) {
      // We‘re waiting for a reply, but we received a blocking synchronous
      // call.  We must process it or otherwise a deadlock might occur.
      context->GetDispatchEvent()->Reset();
      context->DispatchMessages();
      continue;
    }

    if (result == 2 /* pump_messages_event */)
      WaitForReplyWithNestedMessageLoop(context);  // Run a nested message loop.

    break;
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel类的成员函数WaitForReply首先调用参数context描述的一个SyncContext对象的成员函数DispatchMessages分发那些已经接收到但是还没分发出去的IPC消息,因为接下来当前线程需要进入睡眠状态等待刚刚发送出去的同步IPC消息的回复。

在等待同步IPC消息的回复的过程中,当前线程有一些特殊情况需要唤醒回来处理:

1. 当前进程的IO线程收到了一个IPC消息,但是该IPC消息不是刚刚发送出去的同步IPC消息的回复,这时候当前线程(当前进程的主线程,除了负责发送IPC消息,也负责接收IPC消息),需要唤醒过来处理接收到的IPC消息。我们将此唤醒事件称为Dispatch Event。

2. 如前所述,当前进程的IO线程接收到了一个IPC消息,该IPC消息不是刚刚发送出去的同步IPC消息的回复,并且它要求当前线程唤醒过来,并且进入一个嵌套消息循环,以便它可以完成显示一个对话框出来获取用户的输入的操作。我们将此唤醒事件称为Pump Messages Event。

这意味着在等待同步IPC消息的回复的过程中,有三个事件会将当前线程唤醒过来。除了两述两个特殊情况对应的事件之外,还有一个事件就是刚刚发送出去的同步IPC消息得到了回复,我们将此唤醒事件称为Send Done Event。

Dispatch Event可以通过调用参数context描述的一个SyncContext对象的成员函数GetDispatchEvent获得,如下所示:

WaitableEvent* SyncChannel::SyncContext::GetDispatchEvent() {
  return received_sync_msgs_->dispatch_event();
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncContext类的成员函数GetDispatchEvent调用成员变量received_sync_msgs描述的一个SyncChannel::ReceivedSyncMsgQueue对象的成员函数dispatch_event获得一个Dispatch Event,并且将该Dispatch Event返回给调用者。

SyncChannel::ReceivedSyncMsgQueue类的成员函数dispatch_event的实现如下所示:

class SyncChannel::ReceivedSyncMsgQueue :
    public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> {
 public:
  ......

  WaitableEvent* dispatch_event() { return &dispatch_event_; }

 private:
  ......

  WaitableEvent dispatch_event_;

  ......
};

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::ReceivedSyncMsgQueue类的成员函数dispatch_event返回的是成员变量dispatch_event描述的一个Waitable Event。

Send Done Event可以通过调用参数context描述的一个SyncContext对象的成员函数GetSendDoneEvent获得,如下所示:

WaitableEvent* SyncChannel::SyncContext::GetSendDoneEvent() {
  base::AutoLock auto_lock(deserializers_lock_);
  return deserializers_.back().done_event;
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

回忆前面执行SyncChannel::SyncContext类的成员函数Push的时候,我们为即将要发送的同步IPC消息创建了一个PendingSyncMsg对象。创建出来的PendingSyncMsg对象封装了一个WaitableEvent对象在成员变量done_event中。最后创建的PendingSyncMsg对象保存在SyncChannel::SyncContext类的成员变量deserializers_描述的一个std::deque的末尾位置。

因此,保存在SyncChannel::SyncContext类的成员变量deserializers_描述的一个std::deque的末尾位置的PendingSyncMsg对象描述的就是刚刚发送出去的还未得到回复的IPC消息,这时候保存在该PendingSyncMsg对象的成员变量done_event描述的一个Waitable Event就是Send Done Event,SyncContext类的成员函数GetSendDoneEvent将它返回给调用者,

Pump Messages Event直接就由参数pump_messages_event描述。

有了上述三个Waitable Event之后,SyncChannel类的成员函数WaitForReply将它们封装在一个objects数组中,并且通过WaitableEvent类的静态成同函数WaitMany等待它们其中之一变为Signaled状态。这个等待过程可能参考前面Chromium多线程模型设计和实现分析一文。

当前线程从WaitableEvent类的静态成同函数WaitMany返回来的时候,就表示保存在objects数组中的某一个Waitable Event的状态变为Signaled了。至于是哪一个Waitable Event,就由WaitableEvent类的静态成同函数WaitMany返回的objects数组索引result指定。

因此,当result的值等于0的时候,就表示Dispatch Event的状态变为Signaled了,这时候SyncChannel类的成员函数WaitForReply需要调用参数context描述的一个SyncChannel::SyncContext对象的成员函数DispatchMessages对接收到的IPC消息进行处理。

在分析SyncChannel::SyncContext类的成员函数DispatchMessages之前,我们首先要了解Dispatch Event状态变为Signaled的过程。从图1可以知道,IO线程接收到的IPC消息会分发给SyncChannel对象处理。具体来说,是分发给SyncChannel类从父类ChannelProxy继承下来的成员变量context_描述的一个SyncChannel::SyncContext对象的成员函数OnMessageReceived处理。这一点我们后面分析IPC消息的分发过程时就会看到。

SyncChannel::SyncContext类的成员函数OnMessageReceived的实现如下所示:

bool SyncChannel::SyncContext::OnMessageReceived(const Message& msg) {
  // Give the filters a chance at processing this message.
  if (TryFilters(msg))
    return true;

  if (TryToUnblockListener(&msg))
    return true;

  if (msg.is_reply()) {
    received_sync_msgs_->QueueReply(msg, this);
    return true;
  }

  if (msg.should_unblock()) {
    received_sync_msgs_->QueueMessage(msg, this);
    return true;
  }

  return Context::OnMessageReceivedNoFilter(msg);
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::SyncContext类的成员函数OnMessageReceived的执行过程如下所示:

1. 调用从父类ChannelProxy::Context继承下来的成员函数TryFilters将接收到的IPC消息优先分给IPC消息Filter处理。这一点在后面分析IPC消息的分发过程时就会看到。

2. 如果IPC消息Filter不处理接收到的IPC消息,那么接下来再调用成员函数TryToUnblockListener检查接收到的IPC消息是否就是刚刚发送出去的同步IPC消息的回复。

3. 如果接收到的IPC消息不是刚刚发送出去的同步IPC消息的回复,那么接下来再检查它是不是一个回复类型的消息。如果是的话,就调用成员变量received_sync_msgs_描述的一个SyncChannel::ReceivedSyncMsgQueue对象的成员函数QueueReply进行处理,后者仅仅是将接收到的IPC回复消息保存在其成员变量received_replies_描述的一个std::vector中。

4. 如果接收到的IPC消息不是回复类型的IPC消息,那么接下来检查它是否设置了前面所述的UNBLOCK_BIT位。如果设置了,那么就调用成员变量描述的一个SyncChannel::ReceivedSyncMsgQueue对象的成员函数QueueMessage对接收到的IPC消息进行处理。

5. 如果接收到的IPC消息没有设置BLOCK_BIT位,那么接下来就会调用父类ChannelProxy::Context的成员函数OnMessageReceivedNoFilter进行处理。这个处理过程在后面分析IPC消息的分发过程时会看到。不过我们这里需要注意的一点是,ChannelProxy::Context类的成员函数OnMessageReceivedNoFilter不会马上就去处理接收到的消息,它仅仅是向负责处理接收到的IPC消息的线程的消息队列发送了一个Task。等到该Task被消息循环执行的时候,它所描述的IPC消息才会被处理。这一点有别于上述第4点提到的SyncChannel::ReceivedSyncMsgQueue对象的成员函数QueueMessage,因为该函数会马上对接收到的IPC消息进行处理。

接下来,我们只关注上述第4点提到的SyncChannel::ReceivedSyncMsgQueue类的成员函数QueueMessage,它的实现如下所示:

class SyncChannel::ReceivedSyncMsgQueue :
    public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> {
 public:
  ......

  void QueueMessage(const Message& msg, SyncChannel::SyncContext* context) {
    bool was_task_pending;
    {
      base::AutoLock auto_lock(message_lock_);

      ......
      message_queue_.push_back(QueuedMessage(new Message(msg), context));
      ......
    }

    dispatch_event_.Signal();
    ......
  }

  ......
};

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::ReceivedSyncsgQueue类的成员函数QueueMessage首先将接收到的IPC消息封装成一个QueuedMessage对象,并且保存在成员变量message_queue_描述的一个std::list中,接下来再将成员变量dispatch_event_描述的Dispatch Event的状态设置为Signaled。在我们这个情景中,就会导致SyncChannel类的成员函数WaitForReply从WaitableEvent类的静态成员函数WaitMany返回来,并且这时候会调用参数context描述的一个SyncChannel ::SyncContext对象的成员函数DispatchMessages进行处理。

SyncChannel ::SyncContext类的成员函数DispatchMessages的实现如下所示:

void SyncChannel::SyncContext::DispatchMessages() {
  received_sync_msgs_->DispatchMessages(this);
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel ::SyncContext类的成员函数DispatchMessages调用了成员变量received_sync_msgs_描述的一个SyncChannel::ReceivedSyncMsgQueue对象的成员函数DispatchMessages对前面接收到的IPC消息进行分发处理。

SyncChannel::ReceivedSyncMsgQueue类的成员函数DispatchMessages的实现如下所示:

class SyncChannel::ReceivedSyncMsgQueue :
    public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> {
 public:
  ......

  void DispatchMessages(SyncContext* dispatching_context) {
    bool first_time = true;
    uint32 expected_version = 0;
    SyncMessageQueue::iterator it;
    while (true) {
      Message* message = NULL;
      scoped_refptr<SyncChannel::SyncContext> context;
      {
        base::AutoLock auto_lock(message_lock_);
        if (first_time || message_queue_version_ != expected_version) {
          it = message_queue_.begin();
          first_time = false;
        }
        for (; it != message_queue_.end(); it++) {
          int message_group = it->context->restrict_dispatch_group();
          if (message_group == kRestrictDispatchGroup_None ||
              message_group == dispatching_context->restrict_dispatch_group()) {
            message = it->message;
            context = it->context;
            it = message_queue_.erase(it);
            message_queue_version_++;
            expected_version = message_queue_version_;
            break;
          }
        }
      }

      if (message == NULL)
        break;
      context->OnDispatchMessage(*message);
      delete message;
    }
  }

  ......
};

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::ReceivedSyncMsgQueue类的成员函数DispatchMessages主要做的工作就是依次将保存在成员变量message_queue_描述的一个std::list中的IPC消息取出来,并且进行分发处理。

注意,我们前面分析的SyncChannel::ReceivedSyncMsgQueue类的成员函数QueueMessage会记录保存在成员变量message_queue_描述的一个std::list中的每一个IPC消息所对应的SyncChannel ::SyncContext对象。我们可以给一个SyncChannel ::SyncContext对象设置一个Restrict Dispatch Group,以限制属于该Restrict Dispatch Group的IPC消息才会在Display Event状态变为Signaled时被分发处理。

因此,SyncChannel::ReceivedSyncMsgQueue类的成员函数DispatchMessages会依次检查保存在成员变量message_queue_描述的一个std::list中的每一个IPC消息,只当它关联的SyncChannel ::SyncContext对象设置的Restrict Dispatch Group与参数dispatching_context描述的一个SyncChannel ::SyncContext对象设置的estrict Dispatch Group一致时,才会对它进行分发处理。此外,如果一个IPC消息关联的SyncChannel ::SyncContext对象没有设置Restrict Dispatch Group,那么它就总是会被分发处理。

最后,每一个需要分发处理的IPC消息都通过调用与它关联的一个SyncChannel ::SyncContext对象的成员函数OnDispatchMessage进行分发处理。SyncChannel ::SyncContext类的成员函数OnDispatchMessage是从父类ChannelProxy::Context继承下来的,后面我们分析IPC消息的分发过程再详细分析。

这样,关于当前线程被Display Event唤醒的处理过程就分析到这里。接下来我们考虑当前线程被Send Done Event唤醒的处理过程。

前面提到,当IO线程接收到一个IPC消息时,会分发给SyncChannel::SyncContext类的成员函数OnMessageReceived进处理。SyncChannel::SyncContext类的成员函数OnMessageReceived在处理的过程中,会调用另外一个成员函数TryToUnblockListener检查当前接收到的IPC消息是否是刚刚发送出去的同步IPC消息的回复,如下所示:

bool SyncChannel::SyncContext::TryToUnblockListener(const Message* msg) {
  base::AutoLock auto_lock(deserializers_lock_);
  if (deserializers_.empty() ||
      !SyncMessage::IsMessageReplyTo(*msg, deserializers_.back().id)) {
    return false;
  }

  ......

  deserializers_.back().done_event->Signal();

  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

从前面的分析可以知道,刚刚发送出去的同步IPC消息就保存在SyncChannel::SyncContext类的成员变量deserializers_描述的一个std::deque的末尾,这里通过调用SyncMessage类的静态成员函数IsMessageReplyTo判断当前接收到的IPC消息是否就是它的回复消息。如果是的话,那么就将与它关联的一个Waitable Event的状态设置为Signaled,也就是将Send Done Event的状态设置为Signaled。这样就会导致前面分析的SyncChannel类的成员函数WaitForReply跳出while循环,也就是当前线程完成了发送同步IPC消息的过程,因为得到了发送出去的IPC消息的回复。

这样,关于当前线程被Display Event唤醒的处理过程就分析到这里。接下来我们考虑当前线程被Pump Messages Event唤醒的处理过程。

从前面分析的SyncChannel类的成员函数WaitForReply的实现可以知道,当前线程被Pump Messages Event唤醒时,SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop会被调用,使得当前线程进入到一个嵌套的消息循环中去。

SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop的实现如下所示:

void SyncChannel::WaitForReplyWithNestedMessageLoop(SyncContext* context) {
  base::WaitableEventWatcher send_done_watcher;

  ReceivedSyncMsgQueue* sync_msg_queue = context->received_sync_msgs();
  DCHECK(sync_msg_queue != NULL);

  base::WaitableEventWatcher* old_send_done_event_watcher =
      sync_msg_queue->top_send_done_watcher();

  base::WaitableEventWatcher::EventCallback old_callback;
  base::WaitableEvent* old_event = NULL;

  // Maintain a local global stack of send done delegates to ensure that
  // nested sync calls complete in the correct sequence, i.e. the
  // outermost call completes first, etc.
  if (old_send_done_event_watcher) {
    old_callback = old_send_done_event_watcher->callback();
    old_event = old_send_done_event_watcher->GetWatchedEvent();
    old_send_done_event_watcher->StopWatching();
  }

  sync_msg_queue->set_top_send_done_watcher(&send_done_watcher);

  send_done_watcher.StartWatching(context->GetSendDoneEvent(),
                                  context->MakeWaitableEventCallback());

  {
    base::MessageLoop::ScopedNestableTaskAllower allow(
        base::MessageLoop::current());
    base::MessageLoop::current()->Run();
  }

  sync_msg_queue->set_top_send_done_watcher(old_send_done_event_watcher);
  if (old_send_done_event_watcher && old_event) {
    old_send_done_event_watcher->StartWatching(old_event, old_callback);
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop主要就是做两件事情:

第一件事情是通过以下代码使得当前线程进入到一个嵌套的消息循环中去:

  {
    base::MessageLoop::ScopedNestableTaskAllower allow(
        base::MessageLoop::current());
    base::MessageLoop::current()->Run();
  }

第二件事情是通过以下代码监控刚刚发送出去的同步IPC消息的回复的接收事件,即监控Send Done Event的状态变为Signaled:

  base::WaitableEventWatcher send_done_watcher;

  ReceivedSyncMsgQueue* sync_msg_queue = context->received_sync_msgs();

  ......

  sync_msg_queue->set_top_send_done_watcher(&send_done_watcher);

  send_done_watcher.StartWatching(context->GetSendDoneEvent(),
                                  context->MakeWaitableEventCallback());

关于第二件事情,我们注意到两点:

1. SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop在进入嵌套的消息循环之前,通过在栈上创建的一个WaitableEventWatcher对象send_done_watcher来监控Send Done Event的状态变为Signaled。关于WaitableEventWatcher的作用和实现,可以参考前面Chromium多线程模型设计和实现分析一文。

当Send Done Event的状态变为Signaled时,调用参数context描述的一个SyncChannel::SyncContext对象的成员函数MakeWaitableEventCallback获得的一个类型为EventCallback的Callback便会被调用。关于Callback的作用和实现,可以参考前面Chromium多线程通信的Closure机制分析一文,

SyncChannel::SyncContext类的成员函数MakeWaitableEventCallback的实现如下所示:

base::WaitableEventWatcher::EventCallback
    SyncChannel::SyncContext::MakeWaitableEventCallback() {
  return base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled, this);
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

这意味在当前线程进入到嵌套的消息循环时,如果Send Done Event的状态变为Signaled,那么SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled就会被调用。

2. 在栈上创建的一个WaitableEventWatcher对象send_done_watcher同时会被保存在一个SyncChannel::ReceivedSyncMsgQueue对象中。这个ReceivedSyncMsgQueue对象是通过调用参数描述的一个SyncChannel::SyncContext对象的成员函数received_sync_msgs获得的。

将一个WaitableEventWatcher对象保存在一个SyncChannel::ReceivedSyncMsgQueue对象中是通过调用SyncChannel::ReceivedSyncMsgQueue类的成员函数set_top_send_done_watcher实现的,如下所示:

class SyncChannel::ReceivedSyncMsgQueue :
    public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> {
 public:
  ......

  base::WaitableEventWatcher* top_send_done_watcher() {
    return top_send_done_watcher_;
  }

  void set_top_send_done_watcher(base::WaitableEventWatcher* watcher) {
    top_send_done_watcher_ = watcher;
  }

 private:
  ......

  base::WaitableEventWatcher* top_send_done_watcher_;
};

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

SyncChannel::ReceivedSyncMsgQueue类的成员函数set_top_send_done_watcher将参数watcher描述的WaitableEventWatcher对象保存在成员变量top_send_done_watcher_中。

同时,SyncChannel::ReceivedSyncMsgQueue类还提供了另外一个成员函数top_send_done_watcher用来获得成员变量top_send_done_watcher_描述的WaitableEventWatcher对象。

回到SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop中,为什么它需要将一个在栈上创建的WaitableEventWatcher对象保存在一个SyncChannel::ReceivedSyncMsgQueue对象中呢?

这是因为当前线程在一个嵌套的消息循环中运行时,它有可能又会通过SyncChannel类的成员函数Send发送另外一个同步IPC消息。依次类推,这意味着当前线程可能需要等待一系列的同步IPC消息的回复。为了保证顺序性,当前线程同一时刻只能一个同步IPC消息的回复,这个同步IPC消息就是最后发送的那一个同步IPC消息。当最后发送的一个同步IPC消息得到回复之后,再等待次最后发送的同步IPC消息。依次类推,直到所有的同步IPC消息都得到回复为止。

为了达到以上目的,每次SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop被调用时,都会先通过SyncChannel::ReceivedSyncMsgQueue类成员函数top_send_done_watcher检查当前线程是否正在等待上一个同步IPC消息的回复。如果是的话,那么SyncChannel::ReceivedSyncMsgQueue类成员函数top_send_done_watcher就会返回一个WaitableEventWatcher对象。于是SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop就需要先停止等待上一个同步IPC消息的回复。等到当前正在发送的同步IPC消息得到回复之后,再恢复等待上一个同步IPC消息的回复。这就是以下代码段的作用:

  base::WaitableEventWatcher* old_send_done_event_watcher =
      sync_msg_queue->top_send_done_watcher();

  base::WaitableEventWatcher::EventCallback old_callback;
  base::WaitableEvent* old_event = NULL;

  // Maintain a local global stack of send done delegates to ensure that
  // nested sync calls complete in the correct sequence, i.e. the
  // outermost call completes first, etc.
  if (old_send_done_event_watcher) {
    old_callback = old_send_done_event_watcher->callback();
    old_event = old_send_done_event_watcher->GetWatchedEvent();
    old_send_done_event_watcher->StopWatching();
  }

  ......

  sync_msg_queue->set_top_send_done_watcher(old_send_done_event_watcher);
  if (old_send_done_event_watcher && old_event) {
    old_send_done_event_watcher->StartWatching(old_event, old_callback);
  }

从前面的分析可以知道,当最后发送的同步IPC消息得到回复时,即Send Done Event的状态变为Signaled时,SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled会被调用,如下所示:

void SyncChannel::SyncContext::OnWaitableEventSignaled(WaitableEvent* event) {
  if (event == shutdown_event_) {
    // Process shut down before we can get a reply to a synchronous message.
    // Cancel pending Send calls, which will end up setting the send done event.
    CancelPendingSends();
  } else {
    // We got the reply, timed out or the process shutdown.
    DCHECK_EQ(GetSendDoneEvent(), event);
    base::MessageLoop::current()->QuitNow();
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

从这里可以看出,SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled不仅在Send Done Event的状态变为Signaled时被调用,当Shutdown Event的状态变为Signaled时也会被调用。

从前面Chromium的Render进程启动过程分析一文可以知道,当Render进程创建了一个用来与Browser进程通信的SyncChannel对象之后,会通过SyncChannel::SyncContext::OnChannelOpened监控一个Shutdown Event,如下所示:

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中。

这个Shutdown Event描述的是Browser进程的关闭事件。考虑一个情景,Render进程发送一个同步IPC消息给Browser进程,但是在Render进程等待回复的过程中,Browser进程意外地退出了。在这种情况下,Render进程应该结束等待前面发送的同步IPC消息的回复。这就是通过在Shutdown Event的状态变为Signaled时调用SyncChannel::SyncContext类的成员函数CancelPendingSends实现的。

SyncChannel::SyncContext类的成员函数CancelPendingSends的实现如下所示:

void SyncChannel::SyncContext::CancelPendingSends() {
  base::AutoLock auto_lock(deserializers_lock_);
  PendingSyncMessageQueue::iterator iter;
  ......
  for (iter = deserializers_.begin(); iter != deserializers_.end(); iter++)
    iter->done_event->Signal();
}

这个函数定义在文件external/chromium_org/ipc/ipc_sync_channel.cc中。

从前面的分析可以知道,已经发送出去的、但是还没得到回复的同步IPC消息都保存在SyncChannel::SyncContext类的成员变量deserializers_描述的一个std::deque中,SyncChannel::SyncContext类的成员函数CancelPendingSends依次将与它们关联的Waitable Event的状态设置为Signaled。根据我们前面的分析,这就会导致SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled被调用。

回到前面分析的SyncChannel::SyncContext类的成员函数OnWaitableEventSignaled中,当参数event描述的WaitableEvent不是Shutdown Event时,那么它就是一个Send Done Event,这时候需要做的事情就是使得当前线程退出当前所运行在的嵌套消息循环。这样就会使得当前线程从SyncChannel类的成员函数WaitForReplyWithNestedMessageLoop返回到SyncChannel类的成员函数WaitForReply。SyncChannel类的成员函数WaitForReply接着又会返回到SyncChannel类的成员函数Send中,从而完成一个同步IPC消息的发送过程。

回到SyncChannel类的成员函数Send中,真正向对方发送一个同步IPC消息是通过调用父类ChannelProxy的成员函数Send完成的,它的实现如下所示:

bool ChannelProxy::Send(Message* message) {
  ......

  context_->ipc_task_runner()->PostTask(
      FROM_HERE,
      base::Bind(&ChannelProxy::Context::OnSendMessage,
                 context_, base::Passed(scoped_ptr<Message>(message))));
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面Chromium的Render进程启动过程分析一文可以知道,ChannelProxy的成员变量context_指向的是一个ChannelProxy::Context对象,调用它的成员函数ipc_task_runner获得是一个与当前进程的IO线程关联的一个SingleThreadTaskRunner对象。因此,ChannelProxy类的成员函数Send实际上是向当前进程的IO线程的消息队列发送一个Task,该Task绑定的是ChannelProxy::Context类的成员函数OnSendMessage,它的实现如下所示:

// Called on the IPC::Channel thread
void ChannelProxy::Context::OnSendMessage(scoped_ptr<Message> message) {
  ......

  if (!channel_->Send(message.release()))
    OnChannelError();
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面Chromium的Render进程启动过程分析一文可以知道,ChannelProxy类的成员变量channel_指向的是一个ChannelPosix对象,ChannelProxy::Context类的成员函数OnSendMessage调用它的成员函数Send发送一个IPC消息,如下所示:

bool ChannelPosix::Send(Message* message) {
  ......

  output_queue_.push(message);
  if (!is_blocked_on_write_ && !waiting_connect_) {
    return ProcessOutgoingMessages();
  }

  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelPosix类的成员函数Send首先将要发送的IPC消息保存在成员变量output_queue_描述的一个待发送IPC消息队列中,接着在同时满足以下两个条件的情况下,调用另外一个成员函数ProcessOutgoingMessages将保存在待发送IPC消息队列中的IPC消息发送出去:

1. ChannelPosix类的成员变量is_blocked_on_write_的值等于false。ChannelPosix类是通过一个UNIX Socket向对方发送IPC消息的,也就是将要发送的IPC消息写入到一个UNIX Socket中去。当写入UNIX Socket的IPC消息过多,而对方又没有来得及处理时,再向该UNIX Socket写入IPC消息就会失败。这时候ChannelPosix类的成员变量is_blocked_on_write_的值就会设置为true,并且ChannelPosix类会监用来写入IPC消息的UNIX Socket的可写事件。一旦该可写事件发生时,再将ChannelPosix类的成员变量is_blocked_on_write_的值重置为false,并且调用ChannelPosix类的成员函数ProcessOutgoingMessages继续发送之前剩下来还没有发送出去的IPC消息。

2. ChannelPosix类的成员变量waiting_connect_等于false。这个成员变量用来描述执行IPC的双方是否已经建立连接。当执行IPC的双方已经建立好边接的时候,它的值就会等于false。

当第1个条件不满足时,也就是ChannelPosix类的成员变量is_blocked_on_write_的值等于true时,ChannelPosix类的成员函数ProcessOutgoingMessages会在合适的时候被调用,因此ChannelPosix类的成员函数Send现在就不需要调用它。

当第2个条件不满足时,也就是ChannelPosix类的成员变量waiting_connect_等于true时,执行IPC的双方还没有建立好连接,这时候是无法调用ChannelPosix类的成员函数ProcessOutgoingMessages发送IPC消息的。

我们假设以上两个条件都能满足,那么接下来ChannelPosix类的成员函数ProcessOutgoingMessages就会被调用,它的实现如下所示:

bool ChannelPosix::ProcessOutgoingMessages() {
  ......

  while (!output_queue_.empty()) {
    Message* msg = output_queue_.front();

    size_t amt_to_write = msg->size() - message_send_bytes_written_;
    ......
    const char* out_bytes = reinterpret_cast<const char*>(msg->data()) +
        message_send_bytes_written_;

    struct msghdr msgh = {0};
    struct iovec iov = {const_cast<char*>(out_bytes), amt_to_write};
    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;
    char buf[CMSG_SPACE(
        sizeof(int) * FileDescriptorSet::kMaxDescriptorsPerMessage)];

    ssize_t bytes_written = 1;
    ......

    if (message_send_bytes_written_ == 0 &&
        !msg->file_descriptor_set()->empty()) {
      // This is the first chunk of a message which has descriptors to send
      struct cmsghdr *cmsg;
      const unsigned num_fds = msg->file_descriptor_set()->size();

      .....

      msgh.msg_control = buf;
      msgh.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds);
      cmsg = CMSG_FIRSTHDR(&msgh);
      cmsg->cmsg_level = SOL_SOCKET;
      cmsg->cmsg_type = SCM_RIGHTS;
      cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds);
      msg->file_descriptor_set()->GetDescriptors(
          reinterpret_cast<int*>(CMSG_DATA(cmsg)));
      msgh.msg_controllen = cmsg->cmsg_len;

      msg->header()->num_fds = static_cast<uint16>(num_fds);

#if defined(IPC_USES_READWRITE)
      if (!IsHelloMessage(*msg)) {
        // Only the Hello message sends the file descriptor with the message.
        // Subsequently, we can send file descriptors on the dedicated
        // fd_pipe_ which makes Seccomp sandbox operation more efficient.
        struct iovec fd_pipe_iov = { const_cast<char *>(""), 1 };
        msgh.msg_iov = &fd_pipe_iov;
        fd_written = fd_pipe_;
        bytes_written = HANDLE_EINTR(sendmsg(fd_pipe_, &msgh, MSG_DONTWAIT));
        msgh.msg_iov = &iov;
        msgh.msg_controllen = 0;
        ......
      }
#endif  // IPC_USES_READWRITE
    }

    if (bytes_written == 1) {
      ......
#if defined(IPC_USES_READWRITE)
      ......
      if (!msgh.msg_controllen) {
        bytes_written = HANDLE_EINTR(write(pipe_, out_bytes, amt_to_write));
      } else
#endif  // IPC_USES_READWRITE
      {
        bytes_written = HANDLE_EINTR(sendmsg(pipe_, &msgh, MSG_DONTWAIT));
      }
    }

    ......

    if (static_cast<size_t>(bytes_written) != amt_to_write) {
      if (bytes_written > 0) {
        // If write() fails with EAGAIN then bytes_written will be -1.
        message_send_bytes_written_ += bytes_written;
      }

      // Tell libevent to call us back once things are unblocked.
      is_blocked_on_write_ = true;
      base::MessageLoopForIO::current()->WatchFileDescriptor(
          pipe_,
          false,  // One shot
          base::MessageLoopForIO::WATCH_WRITE,
          &write_watcher_,
          this);
      return true;
    } else {
      message_send_bytes_written_ = 0;

      ......
      delete output_queue_.front();
      output_queue_.pop();
    }
  }
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

ChannelPosix类的成员函数ProcessOutgoingMessages依次取出保存在成员变量output_queue_描述的队列中的待发送IPC消息,并且将它们写入到成员变量pipe_描述的一个UNIX Socket中去。这个UNIX Socket就是用来在两个进程之间进行IPC的,它的创建过程可以参考前面Chromium的Render进程启动过程分析一文。

在正常情况下,从ChannelPosix类的成员变量output_queue_描述的队列中取出来的IPC消息都能成功地写入到成员变量pipe_描述的UNIX Socket中,然后再从队列中移除。但是如果接收方没有及时读取UNIX Socket中的数据,那么就会导致UNIX Socket在内核空间的缓冲区被用完,从而使得发送方不能够再往UNIX Socket写入更多的数据。这意味着一个IPC消息可能只有部分被写入到UNIX Socket中去,并且这个IPC消息不能从队列中移除,也就是仍然位于队列的头部。

ChannelPosix类的成员变量message_send_bytes_written_记录的就是队列头部的IPC消息已经写入到UNIX Socket的那部分内容的末尾,因此以下代码就是计算位于队列头部的IPC消息接下来要发送的那部分内容:

    size_t amt_to_write = msg->size() - message_send_bytes_written_;
    DCHECK_NE(0U, amt_to_write);
    const char* out_bytes = reinterpret_cast<const char*>(msg->data()) +
        message_send_bytes_written_;

本地变量amt_to_write描述的是接下来要发送的IPC消息的内容缓冲区的长度,另外一个本地变量out_bytes指向的要发送的IPC消息的内容缓冲区的起始地址。

下面这段代码将要发送的IPC消息的内容缓冲区封装在一个msghdr结构体msgh中,以便接下来可以通过调用系统接口sendmsg将它发送出去:

    struct msghdr msgh = {0};
    struct iovec iov = {const_cast<char*>(out_bytes), amt_to_write};
    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;

再接下来的代码段初始化两个本地变量:

    char buf[CMSG_SPACE(
        sizeof(int) * FileDescriptorSet::kMaxDescriptorsPerMessage)];

    ssize_t bytes_written = 1;

本地变量buf描述的是一个用来保存文件描述符的缓冲区,这些文件描述符将会从源进程传递到目标进程去。注意,一个IPC消息最多可以携带的文件描述符的个数为FileDescriptorSet::kMaxDescriptorsPerMessage。

本地变量bytes_written描述的本次成功地向UNIX Socket写入了多少个字节的内容,当它的值等于本地变量amt_to_write的值的时候,就说明位于队列头部的IPC消息的内容已经全部写入到UNIX Socket中去了。

接下来的if语句判断队列头部的IPC消息是否是第一次被写入到UNIX Socket中,即判断ChannelPosix类的成员变量message_send_bytes_written_的值是否等于0,并且队列头部的IPC消息是否携带有文件描述符要发送给对方:

    if (message_send_bytes_written_ == 0 &&
        !msg->file_descriptor_set()->empty()) {
      ......
    }

如果以上述两个条件均满足,那么接下来就需要通过系统接口sendmsg将队列头部的IPC消息写入到UNIX Socket中。

接下来的这段代码将队列头部的IPC消息携带的文件描述符封装在msghdr结构体msgh中:

      msgh.msg_control = buf;
      msgh.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds);
      cmsg = CMSG_FIRSTHDR(&msgh);
      cmsg->cmsg_level = SOL_SOCKET;
      cmsg->cmsg_type = SCM_RIGHTS;
      cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds);
      msg->file_descriptor_set()->GetDescriptors(
          reinterpret_cast<int*>(CMSG_DATA(cmsg)));
      msgh.msg_controllen = cmsg->cmsg_len;

      msg->header()->num_fds = static_cast<uint16>(num_fds);

要传递的文件描述符保存在msghdr结构体msgh的成员变量msg_control描述的一个缓冲区,并且它的成员变量msg_controllen描述的是该缓冲区的实际使用大小。

接下来这段代码是在编译时定义了宏IPC_USES_READWRITE,才会执行:

#if defined(IPC_USES_READWRITE)
      if (!IsHelloMessage(*msg)) {
        // Only the Hello message sends the file descriptor with the message.
        // Subsequently, we can send file descriptors on the dedicated
        // fd_pipe_ which makes Seccomp sandbox operation more efficient.
        struct iovec fd_pipe_iov = { const_cast<char *>(""), 1 };
        msgh.msg_iov = &fd_pipe_iov;
        ......
        bytes_written = HANDLE_EINTR(sendmsg(fd_pipe_, &msgh, MSG_DONTWAIT));
        msgh.msg_iov = &iov;
        msgh.msg_controllen = 0;
        ......
      }
#endif  // IPC_USES_READWRITE

在前面Chromium的Render进程启动过程分析一文中,我们提到,如果定义了宏IPC_USES_READWRITE,那么在创建用来执行IPC的UNIX Socket的时候,就会额外再创建另外一个UNIX Socket,专门用来发送携带有文件描述符的IPC消息。

这个专门用来发送携带有文件描述符的UNIX Socket的Server端描述符保存在ChannelPosix类的成员变量fd_pipe_中。注意,现在有一个问题,上述专用UNIX Socket的Client端描述符是如何传递给对方的呢?这是通过一个称为Hello Message的IPC消息传递给的。只有当对方通过Hello Message得到了上述专用UNIX Socket的Client端描述符之后,双方才可以通过它来发送携带有文件描述的IPC消息。这意味着以下两点信息:

1. Hello Message携带有文件描述符。

2. 只有队列头部的携带有文件描述符的IPC消息不是Hello Message的情况下,才可以通过专用的UNIX Socket来发送它。

关于Hello Message,我们稍后再分析。一旦确认了队列头部的IPC消息可以通过专用的UNIX Socket发送,那么就调用系统接口sendmsg进行发送。注意,这时候通过系统接口sendmsg发送的内容为:1个空字符,以及要发送的IPC消息携带的文件描述符。这又意味着以下两点信息:

1. 要发送的IPC消息的非文件描述符内容还没有发送出去。

2. 本地变量bytes_written的值等于1。

接下来这段代码判断本地变量bytes_written的值是否等于1,如下所示:

    if (bytes_written == 1) {
      ......
#if defined(IPC_USES_READWRITE)
      ......
      if (!msgh.msg_controllen) {
        bytes_written = HANDLE_EINTR(write(pipe_, out_bytes, amt_to_write));
      } else
#endif  // IPC_USES_READWRITE
      {
        bytes_written = HANDLE_EINTR(sendmsg(pipe_, &msgh, MSG_DONTWAIT));
      }
    }
    if (bytes_written > 0)
      CloseFileDescriptors(msg);

本地变量bytes_written的初始化值也是1,因此当它现在的值也等于1的时候,结合前面的分析,我们知道,有三种可能情况出现:

1. 要发送的IPC消息没有携带有文件描述符。

2. 要发送的IPC消息携带有文件描述符,但是这些文件描述符已经发送出去了。

3. 要发送的IPC消息是一个Hello Message,但是它携带的文件描述符还没有发送出去。

对于第1种情况,msgh.msg_controllen的值等于0,因此这时候需要调用系统接口write将要发送的IPC消息的内容写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去。

对于第2种情况,由于要发送的IPC消息携带的文件描述符已经发送出去,即msgh.msg_controllen的值也等于0,因此这时候调用系统接口write将要发送的IPC消息的非文件描述符内容写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去即可。

对于第3种情况,即Hello Message,由于它携带的文件描述符还没有发送出去,即msgh.msg_controllen的值不等于0,因此这时候要调用系统接口sendmsg将它写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去。

这意味在定义了宏IPC_USES_READWRITE的情况下,IPC消息通过以下三种方式之一进行发送:

1. 对于Hello Message,通过系统接口write将它写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去。

2. 对于其它没有携带文件描述符的IPC消息,通过系统接口write将它写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去。

3. 对于其它携带有文件描述符的IPC消息,首先通过系统接口sendmsg将文件描述符写入到ChannelPosix类的成员变量fd_pipe_描述的UNIX Socket中去,再通过系统接口write将非文件描述符内容写入到ChannelPosix类的成员变量pipe_描述的UNIX Socket中去。

最后这段代码判断是否已经将队列头部的IPC消息完整地发送出去了:

    if (static_cast<size_t>(bytes_written) != amt_to_write) {
      if (bytes_written > 0) {
        // If write() fails with EAGAIN then bytes_written will be -1.
        message_send_bytes_written_ += bytes_written;
      }

      // Tell libevent to call us back once things are unblocked.
      is_blocked_on_write_ = true;
      base::MessageLoopForIO::current()->WatchFileDescriptor(
          pipe_,
          false,  // One shot
          base::MessageLoopForIO::WATCH_WRITE,
          &write_watcher_,
          this);
      return true;
    } else {
      message_send_bytes_written_ = 0;

      ......
      delete output_queue_.front();
      output_queue_.pop();
    }

如果队列头部的IPC消息已经完整地发送出去,那么接下来的处理逻辑就很简单,只需要将它从队列中移除,并且将ChannelPosix类的成员变量message_send_bytes_written_重置为0即可。

另一方面,如果队列头部的IPC消息只是部分地发送出去,即本地变量bytes_written和amt_to_write的值不相等,那么就说明用来发送IPC消息的UNIX Socket在内核空间的缓冲区已经满了,不能够写入更多的数据,这时候就需要等待该缓冲区的数据被接收方读取之后,才能继续发送队列头部的IPC消息的剩余部分内容。

那么,发送方是如何知道接收方已经读取了用来发送IPC消息的UNIX Socket在内核空间的缓冲区的数据的呢?当接收方读取了一个UNIX Socket在内核空间的缓冲区的内容的时候,如果发送方监控了该UNIX Socket的可写事件,即base::MessageLoopForIO::WATCH_WRITE事件,那么发送方就会获得一个通知。

从前面Chromium多线程模型设计和实现分析一文可以知道,我们可以通过当前进程的IO线程来监控一个UNIX Socket的base::MessageLoopForIO::WATCH_WRITE事件,只要调用与该IO线程关联的一个MessageLoopForIO对象的成员函数WatchFileDescriptor,并且指定要监控的文件描述符和负责接收通知的一个base::MessageLoopForIO::Watcher对象即可。

我们很容易知道,要监控的文件描述符即为ChannelPosix类的成员变量pipe_描述的文件描述符,并且指定的base::MessageLoopForIO::Watcher对象即为当前正在处理的一个ChannelPosix对象,因此,当用来发送IPC消息的UNIX Socket在内核空间的缓冲区有空闲的空间可写入时,当前正在处理的一个ChannelPosix对象的成员函数OnFileCanWriteWithoutBlocking就会被调用。

ChannelPosix类的成员函数OnFileCanWriteWithoutBlocking的实现如下所示:

// Called by libevent when we can write to the pipe without blocking.
void ChannelPosix::OnFileCanWriteWithoutBlocking(int fd) {
  DCHECK_EQ(pipe_, fd);
  is_blocked_on_write_ = false;
  if (!ProcessOutgoingMessages()) {
    ClosePipeOnError();
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

ChannelPosix类的成员函数OnFileCanWriteWithoutBlocking要做的事情就是调用成员函数ProcessOutgoingMessages继续发送保存在成员变量output_queue_描述的队列的IPC消息。

这样,Chromium的IPC消息的发送过程,我们就分析完成了。简单来说,就是通过SyncChannel类可以发送同步IPC消息,而通过ChannelProxy类可以发送异步IPC消息,并且SyncChannel类是在ChannelProxy类的基础上实现发送同步IPC消息的功能的。ChannelProxy类在发送IPC消息的时候,实际上就是将要发送的IPC消息的内容写入到一个用于IPC的UNIX Socket中。当要发送的IPC消息携带有文件描述的时候,就通过系统接口sendmsg将其写入到UNIX Socket中,否则的话就通过系统接口write将其写入到UNIX Socket中。

前面我们提到,当定义了宏IPC_USES_READWRITE的时候,会通过一个专门的UNIX Socket来发送携带有文件描述符的IPC消息。这意味着我们需要将这个专门的UNIX Socket相关联的一个文件描述符从一个进程传递到另外一个进程。这个传递过程是通过一个Hello Message进行的。

从前面Chromium的Render进程启动过程分析一文可以知道,Render进程被Browser进程启动之后,会创建一个UNIX Socket,并且将该UNIX Socket的Server端和Client端描述符保存在一个ChannelPosix对象的成员变量fd_pipe_和remote_fd_pipe_中。当Render进程要建立与Browser进程的连接的时候,就会调用上述ChannelPosix对象的成员函数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();
  } 

  ......
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

ChannelPosix类的成员函数AcceptConnection首先是调用成员函数QueueHelloMessage创建一个Hello Message,接着再调用我们前面分析过的成员函数ProcessOutgoingMessages将该Hello Message发送给Browser进程。

ChannelPosix类的成员函数QueueHelloMessage的实现如下所示:

void ChannelPosix::QueueHelloMessage() {
  // Create the Hello message
  scoped_ptr<Message> msg(new Message(MSG_ROUTING_NONE,
                                      HELLO_MESSAGE_TYPE,
                                      IPC::Message::PRIORITY_NORMAL));
  if (!msg->WriteInt(GetHelloMessageProcId())) {
    NOTREACHED() << "Unable to pickle hello message proc id";
  }
#if defined(IPC_USES_READWRITE)
  scoped_ptr<Message> hello;
  if (remote_fd_pipe_ != -1) {
    if (!msg->WriteFileDescriptor(base::FileDescriptor(remote_fd_pipe_,
                                                       false))) {
      NOTREACHED() << "Unable to pickle hello message file descriptors";
    }
    DCHECK_EQ(msg->file_descriptor_set()->size(), 1U);
  }
#endif  // IPC_USES_READWRITE
  output_queue_.push(msg.release());
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

ChannelPosix类的成员函数QueueHelloMessage首先是创建了一个类型为HELLO_MESSAGE_TYPE的IPC消息,即一个Hello Message,接着往这个Hello Message写入了当前进程的PID,即Render进程的PID,以及在定义了宏IPC_USES_READWRITE的情况下,写入了成员变量remote_fd_pipe_描述的一个专用用来传递文件描述符的UNIX Socket的Client端描述符,最后将这个Hello Message保存在成员变量output_queue_描述的一个队列中,以便后面调用成员函数ProcessOutgoingMessages的时候,可以将这个Hello Message发送给对方,即Browser进程。

接下来我们就以Browser进程为接收方,分析IPC消息的接收和分发过程。在这个过程中,我们可以看到Hello Message的处理过程。

从前面Chromium的Render进程启动过程分析一文可以知道,当Render进程向Browser进程发送了一个IPC消息的时候,ChannelPosix类的成员函数OnFileCanReadWithoutBlocking就会被调用,如下所示:

// Called by libevent when we can read from the pipe without blocking.
void ChannelPosix::OnFileCanReadWithoutBlocking(int fd) {
  if (fd == server_listen_pipe_) {
    ......
  } else if (fd == pipe_) {
    ......
    if (!ProcessIncomingMessages()) {
      ......
      return;
    }
  } 

  ......
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

这时候参数fd的值等于ChannelPosix类的成员变量pipe_的值,因为Browser进程是通过ChannelPosix类的成员变量pipe_描述的UNIX Socket在IO线程中通过Libevent的EV_READ事件得知Render进程发送了一个IPC消息给它的。

ChannelPosix类的成员函数OnFileCanReadWithoutBlocking最后通过调用从父类ChannelReader继承下来的成员函数ProcessIncomingMessages接收Render进程发送过来的IPC消息。

ChannelReader类的成员函数ProcessIncomingMessages的实现如下所示:

bool ChannelReader::ProcessIncomingMessages() {
  while (true) {
    int bytes_read = 0;
    ReadState read_state = ReadData(input_buf_, Channel::kReadBufferSize,
                                    &bytes_read);
    if (read_state == READ_FAILED)
      return false;
    if (read_state == READ_PENDING)
      return true;

    DCHECK(bytes_read > 0);
    if (!DispatchInputData(input_buf_, bytes_read))
      return false;
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_reader.cc中。

ChannelReader类的成员函数ProcessIncomingMessages首先是调用另外一个成员函数ReadData从用来执行IPC的UNIX Socket中读取数据,并且保存在成员变量input_buf_ 描述的一个缓冲区中,接着再将该缓冲区分发成员函数DispatchInputData处理。这个过程一直持续执行,直到目前从用来执行IPC的UNIX Socket的数据都读取完成为止,即ChannelReader类的成员函数ReadData的返回值为READ_PENDING,或者读取失败,即ChannelReader类的成员函数ReadData的返回值为READ_FAILED,又或者分发给ChannelReader类的成员函数DispatchInputData处理失败。

接下来我们就分别分析ChannelReader类的成员函数ReadData和DispatchInputData的实现。

ChannelReader类的成员函数ReadData由子类ChannelPosix实现,如下所示:

ChannelPosix::ReadState ChannelPosix::ReadData(
    char* buffer,
    int buffer_len,
    int* bytes_read) {
  ......

  struct msghdr msg = {0};

  struct iovec iov = {buffer, static_cast<size_t>(buffer_len)};
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  msg.msg_control = input_cmsg_buf_;

  // recvmsg() returns 0 if the connection has closed or EAGAIN if no data
  // is waiting on the pipe.
#if defined(IPC_USES_READWRITE)
  if (fd_pipe_ >= 0) {
    *bytes_read = HANDLE_EINTR(read(pipe_, buffer, buffer_len));
    msg.msg_controllen = 0;
  } else
#endif  // IPC_USES_READWRITE
  {
    msg.msg_controllen = sizeof(input_cmsg_buf_);
    *bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT));
  }
  if (*bytes_read < 0) {
    if (errno == EAGAIN) {
      return READ_PENDING;
    } else if (errno == ECONNRESET || errno == EPIPE) {
      return READ_FAILED;
    } else {
      PLOG(ERROR) << "pipe error (" << pipe_ << ")";
      return READ_FAILED;
    }
  } else if (*bytes_read == 0) {
    // The pipe has closed...
    return READ_FAILED;
  }

  ......

  // Read any file descriptors from the message.
  if (!ExtractFileDescriptorsFromMsghdr(&msg))
    return READ_FAILED;
  return READ_SUCCEEDED;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

我们假设Chromium在编译时定义了IPC_USES_READWRITE宏,这意味着ChannelPosix类的成员函数ReadData通过另外一个专用的UNIX Socket来获得对方发送过来的文件描述符。这个专用的UNIX Socket通过ChannelPosix类的成员变量fd_pipe_来描述,并且是通过Hello Message发送过来的。于是我们就分两种情况考虑。

第一种情况是ChannelPosix类还没有接收过Hello Message。这意味着ChannelPosix类的成员变量fd_pipe_的值为-1。这时候所有的IPC消息,不管有没有携带有文件描述符,均需要通过用来执行IPC的UNIX Socket来读取,也就是必须通过ChannelPosix类的成员变量pipe_描述的UNIX Socket来读取,并且是通过系统接口recvmsg来读取的,也就是以下这两行代码:

    msg.msg_controllen = sizeof(input_cmsg_buf_);
    *bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT));

第二种情况是ChannelPosix类已经接收过Hello Message了。这意味着ChannelPosix类的成员变量fd_pipe_的值大于等于0。按照前面分析的IPC消息发送过程,这时候所有的IPC消息:

1. 如果携带有文件描述符,那么文件描述符通过ChannelPosix类的成员变量fd_pipe_描述的UNIX Socket来读取,而非文件描述符内容通过ChannelPosix类的成员变量pipe_描述的UNIX Socket来读取。

2. 如果没有携带有文件描述符,那么就通过ChannelPosix类的成员变量pipe_描述的UNIX Socket来读取即可。

对于携带有文件描述符的IPC消息,它所携带的文件描述符是延后读取的,也就是不在ChannelPosix类的成员函数ReadData中读取,因此现在我们通过系统接口read统一地读出上述两种类型的IPC消息即可,也就是以下这两行代码:

    *bytes_read = HANDLE_EINTR(read(pipe_, buffer, buffer_len));
    msg.msg_controllen = 0;

注意,无论是通过系统接口recvmsg还是read从ChannelPosix类的成员变量fd_pipe_描述的UNIX Socket来读取IPC消息,它们的返回值描述的都是读取出来的数据的大小。当这个返回值小于等于0的时候,就说明出现了异常情况,例如双方建立的UNIX Socket连接已经断开等,这时候ChannelPosix类的成员函数ReadData的返回值就等于READ_FAILED。

但是有一种例外情况,即上述返回值小于0,并且错误号errno被设置为EAGAIN,这时候一般意味着ChannelPosix类的成员变量fd_pipe_描述的UNIX Socket没有更多的数据可以读取,因此ChannelPosix类的成员函数ReadData的返回值就等于READ_PENDING,用来告诉调用者,继续耐心等待下一次数据可读事件发生,再来读取数据。

最后,ChannelPosix类的成员函数ReadData还需要继续处理Hello Message,以及在Hello Message之前的携带有文件描述符的IPC消息,这是通过调用ChannelPosix类的成员函数ExtractFileDescriptorsFromMsghdr进行的。

ChannelPosix类的成员函数ExtractFileDescriptorsFromMsghdr的实现如下所示:

bool ChannelPosix::ExtractFileDescriptorsFromMsghdr(msghdr* msg) {
  // Check that there are any control messages. On OSX, CMSG_FIRSTHDR will
  // return an invalid non-NULL pointer in the case that controllen == 0.
  if (msg->msg_controllen == 0)
    return true;

  for (cmsghdr* cmsg = CMSG_FIRSTHDR(msg);
       cmsg;
       cmsg = CMSG_NXTHDR(msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
      unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
      ......
      const int* file_descriptors = reinterpret_cast<int*>(CMSG_DATA(cmsg));
      unsigned num_file_descriptors = payload_len / 4;
      input_fds_.insert(input_fds_.end(),
                        file_descriptors,
                        file_descriptors + num_file_descriptors);

      ......

      return true;
    }
  }

  // No file descriptors found, but that‘s OK.
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

根据前面的分析,对于Hello Message之前没有携带文件描述符的IPC消息,以及之后的所有IPC消息(无论有没有携带有文件描述符),这时候msg->msg_controllen的值都等于0,因此ChannelPosix类的成员函数ExtractFileDescriptorsFromMsghdr就不会继续往下执行。这里需要再次强调的是,对于Hello Message之后的携带有文件描述符的IPC消息,它们携带的文件描述符是后面再从另外一个UNIX Socket中读取的。

对于Hello Message,以及在它之前的携带有文件描述符的IPC消息,这时候msg->msg_controllen的值都大于0,并且它们携带的文件描述符已经通过接口recvmsg读取出来了,因此接下来ChannelPosix类的成员函数ExtractFileDescriptorsFromMsghdr就将它们解析出来,并且保存在成员变量input_fds_描述的一个std::vector中,等待后续处理。

这一步执行完成之后,返回到ChannelReader类的成员函数ProcessIncomingMessages中,这时候IPC消息的接收过程就基本完毕,接下来主要执行的就是IPC消息分发过程。这个分发过程是通过调用ChannelReader类的成员函数DispatchInputData开始的,如下所示:

bool ChannelReader::DispatchInputData(const char* input_data,
                                      int input_data_len) {
  const char* p;
  const char* end;

  // Possibly combine with the overflow buffer to make a larger buffer.
  if (input_overflow_buf_.empty()) {
    p = input_data;
    end = input_data + input_data_len;
  } else {
    ......
    input_overflow_buf_.append(input_data, input_data_len);
    p = input_overflow_buf_.data();
    end = p + input_overflow_buf_.size();
  }

  // Dispatch all complete messages in the data buffer.
  while (p < end) {
    const char* message_tail = Message::FindNext(p, end);
    if (message_tail) {
      int len = static_cast<int>(message_tail - p);
      Message m(p, len);
      if (!WillDispatchInputMessage(&m))
        return false;

      ......

      if (IsInternalMessage(m))
        HandleInternalMessage(m);
      else
        listener_->OnMessageReceived(m);
      if (m.dispatch_error())
        listener_->OnBadMessageReceived(m);
      p = message_tail;
    } else {
      // Last message is partial.
      break;
    }
  }

  // Save any partial data in the overflow buffer.
  input_overflow_buf_.assign(p, end - p);

  if (input_overflow_buf_.empty() && !DidEmptyInputBuffers())
    return false;
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_reader.cc中。

参数input_data和input_data_len描述了当前从UNIX Socket中读取到的数据及其长度。由于这些数据是以流的形式传输的,因此它们有可能包含有若干个完速的IPC消息,也有可能包含有若干个完整的IPC消息再加上一个不完整的IPC消息。对于不完整的IPC消息,是暂时无法进行处理的,要等到下一次从UNIX Socket中读取到更多的数据时,才能够进行处理。

既然不完整的IPC消息暂时无法处理,那么就需要将它缓存起来。ChannelReader类的成员函数DispatchInputData将不完整的IPC消息缓存在成员变量input_overflow_buf_描述的一个Buffer中。明白了这一点之后,我们就很容易理解下面这段代码的逻辑:

  // Possibly combine with the overflow buffer to make a larger buffer.
  if (input_overflow_buf_.empty()) {
    p = input_data;
    end = input_data + input_data_len;
  } else {
    ......
    input_overflow_buf_.append(input_data, input_data_len);
    p = input_overflow_buf_.data();
    end = p + input_overflow_buf_.size();
  }

也就是说,如果ChannelReader类的成员函数DispatchInputData上次调用时,遗留有不完整的IPC消息,那么现在就需要将参数input_data描述的数据合并在它的后面进行处理。

接下来的一个while循环依次从UNIX Socket读取出来的数据流解析出一个个IPC消息,这就需要能够找到IPC消息边界,这是通过调用Message类的静态成员函数FindNext实现的。根据图2所示的IPC消息格式,从一个UNIX Socket数据流中找到IPC消息的边界是比较容易的,这是因为一个IPC消息的Header是固定的,并且它的Payload长度又记录在了header中。

对于从UNIX Socket数据流中解析出来的每一个IPC消息,都进行以下处理:

1. 创建一个Message对象m来描述它。

2. 调用成员函数WillDispatchInputMessage检查它是否携带有文件描述符。如果有的话,那么就将它们设置到Message对象m里面去。

3. 调用成员函数IsInternalMessage判断它是否是一个内部的IPC消息。所谓的内部的IPC消息,就是由ChannelReader类的成员函数HandleInternalMessage进行处理的IPC消息,这些IPC消息不会分发出去处理。

4. 如果它不是一个内部处理的IPC消息,那么就会调用成员变量listener_指向的一个Listener对象的成员函数OnMessageReceived进行分发处理。从前面Chromium的Render进程启动过程分析一文可以知道,这个成员变量指向的是一个ChannelProxy::Context对象,或者一个SyncChannel::SyncContext对象。

5. 如果它在分发处理的过程中出现错误,那么就会调用成员变量listener_指向的一个Listener对象的成员函数OnBadMessageReceived告诉该Listener对象接收到了一个错误的IPC消息。

最后,对于不完整的IPC消息,它的内容会被缓存在ChannelReader类的成员变量input_overflow_buf_描述的一个Buffer中。如果都是完整的IPC消息,即ChannelReader类的成员变量input_overflow_buf_描述的Buffer的内容为空,那么就要求之前从UNIX Socket中读取出来的文件描述符全部处理完毕,也就是每一个文件描述符都能够找到所属的IPC消息。这一点是通过调用由子类ChannelPosix重写的成员函数DidEmptyInputBuffers实现的。如果不能做到上述要求,那么ChannelReader类的成员函数DispatchInputData的返回值就为false,表示IPC消息在接收过程中出现了问题。

接下来我们就主要分析IPC消息的分发过程,即ChannelReader类的成员函数WillDispatchInputMessage和HandleInternalMessage以及ChannelProxy::Context类和SyncChannel::SyncContext类的成员函数OnMessageReceived的实现。

ChannelReader类的成员函数WillDispatchInputMessage由子类ChannelPosix重写,它的实现如下所示:

bool ChannelPosix::WillDispatchInputMessage(Message* msg) {
  uint16 header_fds = msg->header()->num_fds;
  if (!header_fds)
    return true;  // Nothing to do.

  // The message has file descriptors.
  const char* error = NULL;
  if (header_fds > input_fds_.size()) {
    // The message has been completely received, but we didn‘t get
    // enough file descriptors.
#if defined(IPC_USES_READWRITE)
    if (!ReadFileDescriptorsFromFDPipe())
      return false;
    ......
#endif  // IPC_USES_READWRITE
      ......
  }
  ......
  msg->file_descriptor_set()->SetDescriptors(&input_fds_.front(),
                                             header_fds);
  input_fds_.erase(input_fds_.begin(), input_fds_.begin() + header_fds);
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

ChannelReader类的成员函数WillDispatchInputMessage要做的事情是检查参数msg描述的IPC消息是否携带有文件描述符。如果不携带有,那么就不用对它进行处理了,否则的话,就需要找到这些携带的文件描述符,并且设置为参数msg指向的一个Message对象中去。

从前面分析的IPC消息的格式可以知道,如果一个IPC消息携带有文件描述符,那么它的Header有一个num_fds记录了携带的文件描述的数量。当这个数量等于0的时候,就说明一个IPC消息没有携带文件描述符。

对于携带有文件描述符的IPC消息,分为三类。第一类是是在Hello Message之前发送的。第二类是Hello Message。第三类是在Hello Message之后发送的。

根据前面的分析,第一类和第二类IPC消息携带的文件描述符已经读取出来了,并且保存在ChannelPosix类的成员变量input_fds_描述的一个std::vector中。那么如果找到这些IPC消息对应的文件描述符呢?我们假设这两类的IPC消息有3个,并且它们携带的文件描述符的个数分别是1、2和3。那么这6个文件描述符会依次记录在ChannelPosix类的成员变量input_fds_描述的一个std::vector中。由于IPC消息是顺序处理的,因此,我们就很容易知道,在ChannelPosix类的成员变量input_fds_描述的std::vector中,第一个文件描述符属于第一个IPC消息,第二和第三个文件描述符属于第二个IPC消息,第四、第五和第六个文件描述符属于第三个IPC消息的。

对于第三类IPC消息,它们携带的文件描述符是通过专门的UNIX Socket发送的,并且还没有读取出来。因此现在就要先将它们从专门的UNIX Socket流中读取出来,再设置到参数msg描述的IPC消息中去。

从专门的UNIX Socket流中读取文件描述符是通过调用ChannelPosix类的成员函数ReadFileDescriptorsFromFDPipe进行的,它的实现如下所示:

#if defined(IPC_USES_READWRITE)
bool ChannelPosix::ReadFileDescriptorsFromFDPipe() {
  char dummy;
  struct iovec fd_pipe_iov = { &dummy, 1 };

  struct msghdr msg = { 0 };
  msg.msg_iov = &fd_pipe_iov;
  msg.msg_iovlen = 1;
  msg.msg_control = input_cmsg_buf_;
  msg.msg_controllen = sizeof(input_cmsg_buf_);
  ssize_t bytes_received = HANDLE_EINTR(recvmsg(fd_pipe_, &msg, MSG_DONTWAIT));

  if (bytes_received != 1)
    return true;  // No message waiting.

  if (!ExtractFileDescriptorsFromMsghdr(&msg))
    return false;
  return true;
}
#endif

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

前面提到,专门用来传递文件描述符的UNIX Socket是由ChannelPosix类的成员变量fd_pipe_描述的,因此,这里以它为参数,调用系统接口recvmsg,就可以获得通过它发送的文件描述符。获得的文件描述符同样通过前面分析过的ChannelPosix类的成员函数ExtractFileDescriptorsFromMsghdr保存在成员变量input_fds_描述的一个std::vector中。

前面我们还提到,专门用来传递文件描述符的UNIX Socket是通过Hello Message从Client端进程传递给Server进程的。在我们这个情景中,就是从Render进程传递到Browser进程。Hello Message属于一个由内部处理的IPC消息,即由ChannelReader类的成员函数HandleInternalMessage处理的IPC消息,因此我们接下来继续分析ChannelReader类的成员函数HandleInternalMessage的实现。

由于ChannelReader类的成员函数HandleInternalMessage被子类ChannelPosix重写了,因此我们实际要分析的是ChannelPosix类的成员函数HandleInternalMessage的实现,如下所示:

void ChannelPosix::HandleInternalMessage(const Message& msg) {
  // The Hello message contains only the process id.
  PickleIterator iter(msg);

  switch (msg.type()) {
    ......

    case Channel::HELLO_MESSAGE_TYPE:
      int pid;
      if (!msg.ReadInt(&iter, &pid))
        NOTREACHED();

#if defined(IPC_USES_READWRITE)
      if (mode_ & MODE_SERVER_FLAG) {
        ......
        base::FileDescriptor descriptor;
        if (!msg.ReadFileDescriptor(&iter, &descriptor)) {
          NOTREACHED();
        }
        fd_pipe_ = descriptor.fd;
        ......
      }
#endif  // IPC_USES_READWRITE
      peer_pid_ = pid;
      ......
      break;
    ......
  }
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_posix.cc中。

我们假设此时参数msg描述的IPC消息就为一个Hello Message。前面在分析ChannelPosix类的成员函数QueueHelloMessage的时候提到,Hello Message携带有一个专门用来传递文件描述符的UNIX Socket的Client端描述符,并且它的Payload是一个描述对端进程的PID,因此,ChannelPosix类的成员函数HandleInternalMessage首先读出它的Payload中的PID,保存在成员变量peer_pid_中,接着再读出它携带的UNIX Socket的Client端描述符,保存在成员变量fd_pipe_中。

这里有一点需要注意的是,Client端进程和Server端进程会分别发送一个Hello Message给对方,但是只有Client端进程发送给Server端进程的Hello Message才携带有专门用来传递文件描述符的UNIX Socket的Client端描述符,因此ChannelPosix类的成员函数HandleInternalMessage只有判断出当前进程是Server端进程时,即成员变量mode_的值的MODE_SERVER_FLAG位被设置时,才会从参数msg描述的IPC消息中获取该UNIX Socket的Client端描述符,并且保存在成员变量fd_pipe_中。有了这个UNIX Socket的Client端描述符之后,Client端进程和Server端进程就可以通过它传递IPC消息携带的文件描述符了。

接下来,我们继续分析ChannelProxy::Context类和SyncChannel::SyncContext类的成员函数OnMessageReceived的实现,以便可以进一步了解IPC消息的分发过程。

SyncChannel::SyncContext类的成员函数OnMessageReceived的实现,我们在前面已经分析过了,它主要是用来实现同步IPC消息的发送的,并且它也会通过调用父类ChannelProxy::Context的成员函数OnMessageReceived来分发IPC消息,因此,接下来我们就重点分析ChannelProxy::Context的成员函数OnMessageReceived的实现,如下所示:

// Called on the IPC::Channel thread
bool ChannelProxy::Context::OnMessageReceived(const Message& message) {
  // First give a chance to the filters to process this message.
  if (!TryFilters(message))
    OnMessageReceivedNoFilter(message);
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelProxy::Context类的成员函数OnMessageReceived首先是将参数message描述的IPC消息分发给图1所示的Filter处理,这是通过调用另外一个成员函数TryFilters实现的。

如果图1所示的Filter不处理参数message描述的IPC消息,即ChannelProxy::Context类的成员函数TryFilters的返回值为false,那么再将它分发给图1所示的IPC::Listener处理,这是通过调用另外一个成员函数OnMessageReceivedNoFilter实现的。

ChannelProxy::Context类的成员函数TryFilters的实现如下所示:

bool ChannelProxy::Context::TryFilters(const Message& message) {
  ......

  if (message_filter_router_->TryFilters(message)) {
    ......
    return true;
  }
  return false;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面分析的Filter注册过程可以知道,图1所示的Filter都注册在ChannelProxy::Context类的成员变量message_filter_router_描述的一个MessageFilterRouter对象中,因此,ChannelProxy::Context类的成员函数TryFilters就调用该MessageFilterRouter对象对象的成员函数TryFilters将参数message描述的IPC消息分发给它内部的Filter处理。

如果有Filter处理了参数message描述的IPC消息,即调用MessageFilterRouter类的成员函数TryFilters的返回值为true,那么ChannelProxy::Context类的成员函数TryFilters也为true,否则的话,两者的返回值均为false,这样调用者就知道有没有Filter处理了该IPC消息。

MessageFilterRouter类的成员函数TryFilters的实现如下所示:

bool MessageFilterRouter::TryFilters(const Message& message) {
  if (TryFiltersImpl(global_filters_, message))
    return true;

  const int message_class = IPC_MESSAGE_CLASS(message);
  ......

  return TryFiltersImpl(message_class_filters_[message_class], message);
}

这个函数定义在文件external/chromium_org/ipc/message_filter_router.cc中。

前面我们在分析Filter的注册过程时提到,每一个Filter都可以指定自己所关注的IPC消息的类别,即IPC消息的Header的type的CLASS值。对于指定了关注的IPC消息的类别的Filter,称为非全局Filter,它们保存在MessageFilterRouter类的成员变量message_class_filters_描述的一个MessageFilters数组中。对于没有指关注的IPC消息的类别的Filter,它们称为全局Filter,保存在MessageFilterRouter类的成员变量global_filters_描述的一个MessageFilters对象中。

全局Filter比非全局Filter优先处理接收到的IPC消息,因此MessageFilterRouter类的成员函数TryFilters先将参数message描述的IPC消息分发给全局Filter处理。如果全局Filter不处理该IPC消息,那么MessageFilterRouter类的成员函数TryFilters首先通过宏IPC_MESSAGE_CLASS获得该IPC消息的Header的type的CLASS值,接着再根据该CLASS值找到对应的非全局Filter,并且将该IPC消息分发给它们处理。

将一个IPC消息分发给全局和非全局Filter处理,都是通过调用一个全局函数TryFiltersImpl实现的,它的实现如下所示:

bool TryFiltersImpl(MessageFilterRouter::MessageFilters& filters,
                    const IPC::Message& message) {
  for (size_t i = 0; i < filters.size(); ++i) {
    if (filters[i]->OnMessageReceived(message)) {
      return true;
    }
  }
  return false;
}

这个函数定义在文件external/chromium_org/ipc/message_filter_router.cc中。

函数TryFiltersImpl依次遍历参数filters描述的每一个的Filter,并且将参数message描述的IPC消息分发给它们处理,即以参数message描述的IPC消息为参数,调用它们对应的MessageFilter对象的成员函数OnMessageReceived。

如果中间的某一个Filter处理了参数message描述的IPC消息,即对应的MessageFilter对象的成员函数OnMessageReceived的返回值为true,那么该IPC消息就不会继续分发下去。从这里就可以看到,先注册的Filter比后注册的Filter优先处理接收到的IPC消息。同时,从前面的调用过程可以知道,参数message描述的IPC消息是在IO线程中分发给Filter处理的。

如果参数filters描述的Filter处理了参数message描述的IPC消息,那么函数TryFiltersImpl的返回值就为true,否则的话返回false,这样调用者就知道需不需要继续分发该IPC消息。

从前面Chromium的Render进程启动过程分析一文可以知道,Browser进程在启动一个Render进程之前,即在RenderProcessHostImpl类的成员函数Init中,会调用RenderProcessHostImpl类的成员函数CreateMessageFilters注册很多Filter,这些Filter以后就用来负责处理从Render进程发送的IPC消息,如下所示:

void RenderProcessHostImpl::CreateMessageFilters() {
  ......

  scoped_refptr<RenderMessageFilter> render_message_filter(
      new RenderMessageFilter(
          GetID(),
#if defined(ENABLE_PLUGINS)
          PluginServiceImpl::GetInstance(),
#else
          NULL,
#endif
          GetBrowserContext(),
          GetBrowserContext()->GetRequestContextForRenderProcess(GetID()),
          widget_helper_.get(),
          audio_manager,
          media_internals,
          storage_partition_impl_->GetDOMStorageContext()));
  AddFilter(render_message_filter.get());
  ......

  gpu_message_filter_ = new GpuMessageFilter(GetID(), widget_helper_.get());
  AddFilter(gpu_message_filter_);
  ......

};

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

例如,类型为RenderMessageFilter的Filter负责处理Render进程发送过来给Browser进程的请求创建一个Plugin进程的IPC消息。又如,类型为GpuMessageFilter的Filter负责处理Render进程发送过来给Browser进程的请求创建一个到GPU进程的IPC通道的IPC消息。这两个IPC消息的发送和处理过程在接下来两篇文章中分析GPU进程和Plugin进程的启动过程时就可以看到。

这一步执行完成之后,回到ChannelProxy::Context类的成员函数OnMessageReceived中,假设没有Filter处理接收到的IPC消息,那么该IPC消息就会通过ChannelProxy::Context类的成员函数OnMessageReceivedNoFilter分发给图1所示的IPC::Listener处理。

ChannelProxy::Context类的成员函数OnMessageReceivedNoFilter的实现如下所示:

// Called on the IPC::Channel thread
bool ChannelProxy::Context::OnMessageReceivedNoFilter(const Message& message) {
  listener_task_runner_->PostTask(
      FROM_HERE, base::Bind(&Context::OnDispatchMessage, this, message));
  return true;
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

从前面Chromium的Render进程启动过程分析一文可以知道,ChannelProxy::Context类的成员变量listener_task_runner_指向的是一个SingleThreadTaskRunner对象,该SingleThreadTaskRunner对象描述的是图1所示的IPC::Listener所运行在的线程,通过调用该SingleThreadTaskRunner对象的成员函数PostTask,那么向它描述的线程的消息队列发送一个任务,该任务绑定的函数为ChannelProxy::Context类的成员函数OnDispatchMessage。

因此,最终ChannelProxy::Context类的成员函数OnDispatchMessage会在图1所示的IPC::Listener所运行在的线程中被调用,它负责进一步分发处理参数message描述的IPC消息,它的实现如下所示:

// Called on the listener‘s thread
void ChannelProxy::Context::OnDispatchMessage(const Message& message) {
  ......

  listener_->OnMessageReceived(message);

  ......
}

这个函数定义在文件external/chromium_org/ipc/ipc_channel_proxy.cc中。

ChannelProxy::Context类的成员变量listener_描述的Listener对象即为图1所示的PC::Listener,ChannelProxy::Context类的成员函数OnDispatchMessage将参数message描述的IPC消息分给给它处理,这是通过调用它的成员函数OnMessageReceived实现的。

以Render进程向Browser进程发送IPC消息的情景为例,图1所示的IPC::Listener即为RenderProcessHostImpl,因此接下来RenderProcessHostImpl类的成员函数OnMessageReceived会被调用,它的实现如下所示:

bool RenderProcessHostImpl::OnMessageReceived(const IPC::Message& msg) {
  ......

  if (msg.routing_id() == MSG_ROUTING_CONTROL) {
    // Dispatch control messages.
    IPC_BEGIN_MESSAGE_MAP(RenderProcessHostImpl, msg)
      IPC_MESSAGE_HANDLER(ChildProcessHostMsg_ShutdownRequest,
                          OnShutdownRequest)
      ......
    IPC_END_MESSAGE_MAP()

    return true;
  }

  // Dispatch incoming messages to the appropriate IPC::Listener.
  IPC::Listener* listener = listeners_.Lookup(msg.routing_id());
  ......

  return listener->OnMessageReceived(msg);
}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

RenderProcessHostImpl类的成员函数OnMessageReceived自己只处理类型为MSG_ROUTING_CONTROL的消息,即Header的type值等于MSG_ROUTING_CONTROL的IPC消息,这类IPC消息称为控制类IPC消息。每一个具体类型的控制类IPC消息,都有一个对应的处理函数。例如,具体类型为ChildProcessHostMsg_ShutdownRequest的控制类IPC消息,被分发给了RenderProcessHostImpl类的成员函数OnShutdownRequest处理。

如果参数msg描述的IPC消息不是控制类IPC消息,那么接下来就会分发给注册在RenderProcessHostImpl里面的Route处理,即图1所示的Route。从前面分析的Route的注册过程可以知道,这些Route以其所关注的IPC消息的Header的routing_id为键值保存在RenderProcessHostImpl类的成员变量listeners_描述的一个Map中。因此,RenderProcessHostImpl类的成员函数OnMessageReceived首先根据参数msg描述的IPC消息的Header的routing_id值找到对应的Route,即一个IPC::Listener对象,接着再将参数msg描述的IPC消息分发给它处理,这是通过调用它的成员函数OnMessageReceived实现的。

从前面的调用过程可以知道,Route和Filter之间的区别除了后者比前者优化处理接收到的IPC消息之外,还有一点重要的区别,就是前者与图1所示的IPC::Listener都是在IPC::Listener所运行在的线程中处理IPC消息,这个线程一般就是当前进程的主线程,而后者在当前进程的IO线程中处理IPC消息。这又体现了Chromium的多线程设计哲学,每一个对象与一个特定的线程,然后所有针对该对象的操作,都在该对象绑定的线程中执行,这样就可以避免在多线程环境中由于加锁引发的开销问题,从而提高用户操作的响应性。

至此,IPC消息的发送、接收和分发过程就分析完成了。理解这个过程的重要性,就如同理解Android系统的Binder IPC过程一样重要。它使得我们在阅读Chromium的源码时,快速地理清涉及到的大量的IPC行为。例如,一个进程向另外一个进程发送一个IPC消息后,另外一个进程在什么地方以及如何处理该IPC消息。再结合前面Chromium的Render进程启动过程分析一文,我们就可以掌握Chromium的多进程架构的脉络。因为不管是Render进程,还是GPU进程和Plugin进程,它们的启动过程都是很类似的,以及它们在启动之后,相互之间都是基于本文分析的IPC消息发送、接收和分发过程进行IPC的。在接下来的一篇文章中,我们分析GPU进程的启动过程就可以体会到这一点,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 21:55:41

Chromium的IPC消息发送、接收和分发机制分析的相关文章

[原创]chromium源码阅读-进程间通信IPC.消息的接收与应答

chromium源码阅读-进程间通信IPC.消息的接收与应答 chromium源码阅读-进程间通信IPC.消息的接收与应答 介绍 chromium进程间通信在win32下是通过命名管道的方式实现的,最后的数据都是以二进制流的方式进行传播,pickle类就是负责消息的封包与解包功能,它将各种数据已二进制的形式写到内存缓冲区中,在实际通信的时候通过与其他一些辅助类与模板函数来实现具体数据结构的写与读.本文主要介绍的是chromium在将消息发送与接收过程中,以及chromium如何通过各种消息宏与C

activemq安装与简单消息发送接收实例

安装环境:Activemq5.11.1, jdk1.7(activemq5.11.1版本需要jdk升级到1.7),虚拟机: 192.168.147.131 [[email protected] software]# pwd /export/software [[email protected] software]# tar -zxvf apache-activemq-5.11.1-bin.tar.gz [[email protected] software]# mv apache-activem

IPC消息从发送者传递到接收者的过程

以Render进程向Browser进程发送IPC消息的情景为例,IPC::Sender即为RenderThreadImpl,而IPC::Listener即为RenderProcessHostImpl.IPC::Sender既可以通过ChannelProxy发送IPC消息,也可以通过SyncChannel发送IPC消息.两者的区别在于后者可以发送同步IPC消息.所谓同步IPC消息就是发送者将IPC消息发送给接收者之后,会等待接收者回复一个IPC消息.虽然原则上我们建议使用异步IPC消息,但是某些情

Android事件分发机制详解:史上最全面、最易懂

前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面.最清晰.最易懂的 本文秉着"结论先行.详细分析在后"的原则,即先让大家感性认识,再通过理性分析从而理解问题: 所以,请各位读者先记住结论,再往下继续看分析: 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读 目

Android面试收集录6 事件分发机制

1.基础认知 1.1.事件分发的对象是谁? 答:事件. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件). Touch事件相关细节(发生触摸的位置.时间.历史记录.手势动作等)被封装成MotionEvent对象 主要发生的Touch事件有如下四种: MotionEvent.ACTION_DOWN:按下View(所有事件的开始) MotionEvent.ACTION_MOVE:滑动View MotionEvent.ACTION_CANCEL:非人为原因结

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

DICOM医学图像处理:DIMSE消息发送与接收“大同小异”之DCMTK fo-dicom mDCM

背景: 从DICOM网络传输一文开始,相继介绍了C-ECHO.C-FIND.C-STORE.C-MOVE等DIMSE-C服务的简单实现,博文中的代码给出的实例都是基于fo-dicom库来实现的,原因只有一个:基于C#的fo-dicom库具有高封装性.对于初学者来说实现大多数的DIMSE-C.DIMSE-N服务几乎都是"傻瓜式"操作--构造C-XXX-RQ.N-XXX-RQ然后绑定相应的OnResponseReceived处理函数即可.本博文希望在前几篇预热的基础上,对比DCMTK.fo

TeamTalk Android代码分析(业务流程篇)---消息发送和接收的整体逻辑说明

第一次纪录东西,也没有特别的顺序,想到哪里就随手画了一下,后续会继续整理- 6.2消息页面动作流程 6.2.1 消息页面初始化的总体思路 1.页面数据的填充更新直接由页面主线程从本地数据库请求 2.数据库数据的填充由后台线程异步方式从网络请求 3.前台线程每次按照18条记录读取数据库数据,后台线程按照每次18*3从网络请求数据 4.后台线程数据的请求由主线程满足一定的条件后发送总线事件,在 oneventbackgroudthread 中处理,具体条件(或的关系)如下: 1>第一次请求 2>本

ActiveMQ 部署及发送接收消息

一.           下载 下载地址:http://activemq.apache.org/ 我这里使用的版本为当前最新5.8.0. 下载版本有Windows和Linux两个版本,且都分为32位和64位.根据自己需要选择下载. 二.           安装 我这里下载的为windows的32位版本(apache-activemq-5.8.0-bin.zip),下载后直接解压到需要安装的目录或在直接解压到当前目录也可,解压完安装也完成. 解压后目录如上图,里面包含了示例和文档,及所有的jar