chromium源碼閱讀--進程間通信(IPC)


     第一篇就有提到Chromium是目前默認是采用多進程架構,當然,chromium有singe-process的版本。

     多進程與多線程的區別,確實有很多可以講的,我的另一篇博客也講了一些 (Linux 進程,線程),這里是從瀏覽器的角度來說,如果是多線程,如果一個線程崩潰,影響了整個瀏覽器的使用,因為在現在的網頁標准更新了很多個版本,會有不同標准的頁面在網絡上,極大可能出現解析,渲染,插件等問題,那么對於用戶來說,體驗就會差很多了,瀏覽一個頁面出問題,就要重啟瀏覽器。而多進程則可以避免此問題,render進程崩潰只會影響當前的tab。

    嗯,上面說了那么多,就是為了說,多進程之間就需要進程通信來協作,而chromium的進程間通信是非常繁雜的,如何處理這個是我們需要了解的關鍵。

   那么本質的問題就是:

         1、發那些消息(Message Type)

         2、消息通道是怎么建立的 (Message Channel)

         3、發送者和接收者(Sender,Listener)

OK,咱一個個來。

 

一、 Message Type 

     主要分為2類:“routed” 和 “control”。

     1、routed消息

         主要是用來給某個RenderViewHost對象發送消息的。不過,任何類都可以通過GetNextRoutingID 和 AddRoute 注冊,就能接收routed消息。

     2、control消息

          control消息有創建pipe的類處理,當然這些類也可以接收routed消息。比如,請求資源或修改剪貼板不是特定於視圖的,所以是控制消息。

     3、消息的聲明

1 IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)

      這個宏用來聲明routed消息,這里聲明了一個從render進程發送到browser進程的消息,並有一個GURL參數,一個int參數

1 IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)

     這個宏用來聲明control消息,這里聲明了一個從browser進程發送到render進程的消息,沒有參數。

     這里還有幾個默認的約定:

          (1)這些宏后面的數字表明有幾個參數,最多5個參數,即: IPC_MESSAGE_ROUTED0~IPC_MESSAGE_ROUTED5 或者 IPC_MESSAGE_CONTROL0~IPC_MESSAGE_CONTROL5

          (2)消息名稱表明消息的接受者,FrameHostMsg,帶Host后綴的類,表示在browser進程接收處理的消息,FrameMsg,則表示在render進程處理的消息,如果是Plugin進程,也會帶有Plugin字樣。

 

二、Message Channel

    chromium的使用mojo IPC,並且在官網提供了性能對比 (Times in microseconds

 

Windows Z840

Linux Z620

MacBook Pro 15" 2016

IPC

36.9

69.5

52.5

Mojo cross-process

28.2

48

34.9

 

這里是官網關於mojo的一些介紹,https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md#System-Overview

從unittest看channel的創建:

 

1 void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) {
2   channel_ =
3       IPC::ChannelMojo::Create(TakeHandle(), IPC::Channel::MODE_SERVER,
4                                listener, base::ThreadTaskRunnerHandle::Get());
5 }

 在IPC::ChannelMojo::Create里看到需要 IPC::ChannelMojo的構造,

 1 ChannelMojo::ChannelMojo(
 2     mojo::ScopedMessagePipeHandle handle,
 3     Mode mode,
 4     Listener* listener,
 5     const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner)
 6     : task_runner_(ipc_task_runner),
 7       pipe_(handle.get()),
 8       listener_(listener),
 9       weak_factory_(this) {
10   weak_ptr_ = weak_factory_.GetWeakPtr();
11   bootstrap_ = MojoBootstrap::Create(std::move(handle), mode, ipc_task_runner);
12 }

 在MojoBootstrapImpl里完成sender和listener的綁定:

 1 class MojoBootstrapImpl : public MojoBootstrap {
 2  public:
 3   MojoBootstrapImpl(
 4       mojo::ScopedMessagePipeHandle handle,
 5       const scoped_refptr<ChannelAssociatedGroupController> controller)
 6       : controller_(controller),
 7         associated_group_(controller),
 8         handle_(std::move(handle)) {}
 9 
10   ~MojoBootstrapImpl() override {
11     controller_->ShutDown();
12   }
13 
14  private:
15   void Connect(mojom::ChannelAssociatedPtr* sender,
16                mojom::ChannelAssociatedRequest* receiver) override {
17     controller_->Bind(std::move(handle_));
18     controller_->CreateChannelEndpoints(sender, receiver);
19   }
20 
21  。。。
22 }

 上面的mojo  Channel的創建過程,linux提供的IPC比如:pipe,unix socket,share memory都不是線程安全的,mojo封裝了底層IPC細節並提供了線程安全保障,並且看上面的性能對比,mojo性能更好,這也是chromium逐漸轉用mojo的主要因素吧。

 OK,上面介紹了mojo,接下來我們會發現,在進程里都是使用IPC::ChannelProxy這個類來代理完成Channel的各種工作。

 這里我們只需看一個例子就能理解了,比如在browser進程的RenderProcessHost類里聲明了GetChannel接口:

1 IPC::ChannelProxy* GetChannel() = 0;

根據chromium的套路,你大致就能想到,有一個RenderProcessHostImpl類會來實現這個接口,嗯,果不其然:

 1 class CONTENT_EXPORT RenderProcessHostImpl
 2     : public RenderProcessHost,
 3       public ChildProcessLauncher::Client,
 4       public ui::GpuSwitchingObserver,
 5       public mojom::RouteProvider,
 6       public mojom::AssociatedInterfaceProvider,
 7       public mojom::RendererHost {
 8       ...
 9       IPC::ChannelProxy* GetChannel() override;
10       ...
11 }

我們可以看到這里會提供一個IPC::ChannelProxy的指針,那么順着這個,ChannelProxy的創建和初始化就不遠了。

bool RenderProcessHostImpl::Init() {
   ...
   if (!channel_)
    InitializeChannelProxy();
  
   ...
   CreateMessageFilters();
  RegisterMojoInterfaces();
  ...

}

可以看到,上面初始化了Channel並給當前實例創建了MessageFilter和在mojo里注冊了消息發送的mojo interface。

mojo會負責將channel兩端連通,之后的消息發送就可使用IPC::ChannelProxy來完成了。

 

三、發送者和接收者

    1、發送者

    chromium里定義了IPC::Sender的接口:

 1 class Message;
 2 
 3 class IPC_EXPORT Sender {
 4  public:
 5   // Sends the given IPC message.  The implementor takes ownership of the
 6   // given Message regardless of whether or not this method succeeds.  This
 7   // is done to make this method easier to use.  Returns true on success and
 8   // false otherwise.
 9   virtual bool Send(Message* msg) = 0;
10 
11  protected:
12   virtual ~Sender() {}
13 };

    上面的使用例子,我們可以看到 IPC::ChannelProxy 是消息的發送者,看類的聲明:

1 class IPC_EXPORT ChannelProxy : public Sender {
2 
3 }

2、接收者

    同樣chromium也定義Listener。

class Message;

// Implemented by consumers of a Channel to receive messages.
class IPC_EXPORT Listener {
 public:
  // Called when a message is received.  Returns true iff the message was
  // handled.
  virtual bool OnMessageReceived(const Message& message) = 0;

  ...
};

我們在前面提到的router,是消息接收者,也是消息發送者:

1 class IPC_EXPORT MessageRouter : public Listener, public Sender { 
2     ...
3 }

還有子線程實例也是Listener:

1 class CONTENT_EXPORT ChildThreadImpl
2     : public IPC::Listener,
3       virtual public ChildThread,
4       private base::FieldTrialList::Observer,
5       public mojom::RouteProvider,
6       public mojom::AssociatedInterfaceProvider,
7       public mojom::ChildControl {
8     ...
9  }

好了,更多例子我也不舉了,chromium IPC還有更多的內容,在代碼待我們學習,這里暫時總結到這里,后續再補充。

 

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM