https://www.chromium.org/developers/design-documents/inter-process-communication
全体像
Chromiumはマルチプロセスアーキテクチャで多くのプロセスもち、それぞれが通信する。基本的に名前付きパイプを使う。LinuxやOS Xではsocketpair()を使う。通信は非同期でおこわなれる。
ブラウザ内でのIPC
ブラウザとレンダラの通信は分離されたIOスレッドで行う。メッセージはChannelProxyを通じて送受信される。利点としては重たくなるリソース要求(ウェブページなど)がIOスレッドで行われUIがブロックしなくなること。RenderProcessHostによりChannelProxy::MessageFilterがインストールされ、このフィルターはIOで動き、リソース要求を横取りし、それをリソースディスパッチャーへ転送する。詳しくはマルチプロセスリソースローディングを参照。
レンダラでのIPC
それぞれのレンダラもメッセージを処理するスレッドを持つ(ここの場合はメインスレッド)。レンダリングや他の処理は別スレッドで行う。ほとんどのメッセージはブラウザから送られ、メインスレッドを通じてWebKitに到達する、逆も真。さらに同期通信を行うスレッドもある。
メッセージ
メッセージの種類
大きくroutedとcontrolの2つ。controlメッセージはパイプを作成したクラスで処理される。このクラスは他のクラスがメッセージを受け取ることも可能にしていて、他のクラスはMessageRouterオブジェクトを持ち、それを登録する。
例えばレンダリング中、controlメッセージは特定のビュー対象ではなく、RenderProcess(レンダラ側)やRenderProcessHost(ブラウザ側)で処理される。リソース要求やクリップボード操作はビュー特定の操作でないのでcontrolメッセージで行う。routedメッセージの例はビューに対する描画要求である。
これとは別の切り方はどちらからどちらへ送られるかで分ける場合。ドキュメントフレーム関連のメッセージがブラウザから送られれば、Frameメッセージと呼ばれる。なぜならそれはRenderFrameへ送られるから。逆の場合はFrameHostメッセージと呼ばれる。frame_messages.hを見れば2つのセクションがあり、FrameとFrameHostで分けられている。
ビューやプラグインでも同様にView,ViewHostそしてPluginProcess,PluginProcessHostとなる。
メッセージの宣言
マクロが使われる。レンダラからブラウザへのroutedメッセージで引数にURLとintがある場合、以下のようになる。
IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)
ブラウザからレンダラへのコントロールメッセージは以下のように宣言される。
IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)
値を塩漬け
パラメータはParamTraitsテンプレートでメッセージ本文にシリアサイズされる。この場合、テンプレートの特殊化でほとんどの型はすでに定義されている(ipc_message_utils.h)。自分で型を定義した場合は、その型のParamTraitsを定義しなければならない。
メッセージが非常に多くの値を持つことがある。この場合、値を保持する構造体をつくる。例えばFrameMsg_Navigateメッセージだと、CommonNavigationParams構造体が定義される(navigation_params.h)。frame_message.hはParamTraitsの特殊化を定義する、このときIPC_STRUCT_TRAITSマクロが使われる。
メッセージの送信
メッセージはチャネル(下記参照)を通じて送られる。ブラウザでは、RenderProcessHostがチャネルを保持し、ブラウザのUIスレッドからレンダラへメッセージが送られる。RenderWidgetHost(RenderViewHostの基底クラス)は使うのに便利なSend関数を提供する。
メッセージはポインタを使って送られ、配達されたあとIPCレイヤによってdeleteされる。よって適切なSend関数を見つけたら、newを使って送信する。
Send(new ViewMsg_StopFinding(routing_id));
メッセージが適切なView/ViewHostへルートされるようにrouting IDを指定する。RenderWidgetHost(RenderViewHostの基底クラス)とRenderWidget(RenderViewの基底クラス)はGetRoutingID()メンバをもつ。
メッセージハンドリング
インタフェース"IPC::Listener"を実装することでメッセージをハンドリングする。最も重要な関数はOnMessageReceived。これを実装するための多様なマクロが用意されている。以下を例を参照。
MyClass::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(MyClass, message) // Will call OnMyMessage with the message. The parameters of the message will be unpacked for you. IPC_MESSAGE_HANDLER(ViewHostMsg_MyMessage, OnMyMessage) ... IPC_MESSAGE_UNHANDLED_ERROR() // This will throw an exception for unhandled messages. IPC_END_MESSAGE_MAP() } // This function will be called with the parameters extracted from the ViewHostMsg_MyMessage message. MyClass::OnMyMessage(const GURL& url, int something) { ... }
IPC_DEFINE_MESSAGE_MAPを使って関数を実装することもできる。この場合、メッセージ変数名を指定しない。与えられたクラスのOnMessageReceived関数を宣言し実装する。
他のマクロ:
- OPC_MESSAGE_FORWARD:これはIPC_MESSAGE_HANDLERと同じだが自分のクラスを指定しメッセージを受け取れる。通常は現在のクラスに送られる。
IPC_MESSAGE_FORWARD(ViewHostMsg_MyMessage, some_object_pointer, SomeObject::OnMyMessage)
- OPC_MESSAGE_HANDLER_GENERIC:これは自分でコードを書ける。しかし自分でメッセージパラメータをアンパックしなければならない:
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_MyMessage, printf("Hello, world, I got the message."))
セキュリティの考慮
IPCのセキュリティバグはひどい結果をもたらす(ファイル窃盗、サンドボックス脱出、リモートコード実行)。IPCのセキュリティを調べて、落とし穴に落ちないように。
チャネル
IPC::Channle(ipc/ipc_channel.hで定義)はパイプで通信するメソッドを定義する。IPC::SyncChannelはメッセージ返信を待つための追加機能を提供する(レンダラプロセスはこれを使用するが(下記参照)、ブラウザプロセスは決して使わない)。
チェネルはスレッドセーフではない。ときには他のスレッドにあるチャネルでメッセージを送信したくなる(例えば、IOスレッドを経由しなければならないメッセージをUIスレッドが送信したい場合)。このために、IPC::ChannelProxyを使う。これは通常のチャネルオブジェクトと同様のAPIをもつが、他のスレッドを使ったメッセージ送信のためのプロキシとなり、戻り値のためのプロキシになる。これによりオブジェクト(通常UIスレッドの)がIPC::ChannelProxy::Listenerをインストールし(通常IOスレッドに)、メッセージをフィルタできる。今はこれをリソース要求や他のIOスレッドでハンドルされる要求に使っている。RenderProcessHostはRenderMessageFilterオブジェクトをインストールしフィルタリングを行う。
同期メッセージ
レンダラにとってはいくつかのメッセージは同期しなければならない。WebKitがブラウザからの返信を受け取りたい場合はだいたい当てはまる。スペルチェックやJavasScriptによるクッキーの取得が当てはまる。ブラウザからレンダラへの非同期は許されない。ユーザインタフェースがブロックされ、レンダラがカクカク(flaky)になる。
危険:UIスレッドで同期メッセージを処理しないこと!IOスレッドのみで行うこと。そうしないとアプリはデッドロックするかもしれない。プラグインはUIスレッドから同期描画を要求するので、レンダラが同期メッセージを待っているときブロックされることがあるから。
同期メッセージの宣言
同期メッセージはIPC_SYNC_MESSAGE_*マクロで宣言される。これらのマクロは入力と戻り値をもつ(非同期メッセージは戻り値の概念がない)。2つの入力をもち、1つの戻り値をもつコントロール関数は2_1をマクロ名につけて宣言する:
IPC_SYNC_MESSAGE_CONTROL2_1(SomeMessage, // Message name GURL, //input_param1 int, //input_param2 std::string); //result
同様に、ビューに回されるメッセージはcontrolをroutedに変えてIPC_SYNC_MESSAGE_ROUTED2_1のように宣言する。入力と戻り値のないものも宣言できる。戻り値がないものはレンダラがブラウザの処理を待つときに使う。ペインティングやクリップボード操作で使う。
Issuing synchronous messages