第一篇就有提到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還有更多的內容,在代碼待我們學習,這里暫時總結到這里,后續再補充。