轉(zhuǎn)載請注明出處:
https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/General_Architecture/Inter-process_Communication.html
全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續(xù)更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh
概覽
Chromium有一個多進(jìn)程架構(gòu),這意味著我們有許多需要互相交流的進(jìn)程。我們的主要跨進(jìn)程交流元素是命名管道。在Linux和OS X上,我們使用socketpair()。每個渲染器進(jìn)程可以分配到一個命名管道來跟瀏覽器進(jìn)程交流。這些管道是用異步方式使用的,確保沒有哪個端會等待另一個端。
想要得到如何編寫安全的IPC端點的知識,請查看IPC安全要點.
瀏覽器中IPC
在瀏覽器中,與渲染器的交流是通過一個獨立的I/O線程完成的。來自或者去往view的消息需要使用一個ChannelProxy代理到主線程。這種方案的優(yōu)點是,資源請求(比如網(wǎng)頁等),這種最經(jīng)常且極其關(guān)注性能的消息,可以整個的在I/O線程中處理,不會阻塞用戶界面。這些通過使用Channel::MessageFilter(由RenderProcessHost插入channel)來完成。這個過濾器運行在I/O線程里,攔截資源請求信息,將它們直接轉(zhuǎn)發(fā)到資源分發(fā)主機(jī)。查看多進(jìn)程資源加載獲取更多關(guān)于資源加載的信息。
渲染器中的IPC
每個渲染器也有一個線程管理交流(在這個例子里,是主線程),而大多數(shù)渲染和大多數(shù)處理發(fā)生在另一個線程里(查看多進(jìn)程架構(gòu)的那個圖表)。大多數(shù)消息通過主渲染線程從瀏覽器發(fā)送給WebKit線程,反之亦然。這個額外的線程是用于支持同步的渲染器到瀏覽器的消息(參考下面的“同步消息”)。
消息
消息的類型
我們有兩種基本的消息類型:”路由“和”控制“。控制消息由創(chuàng)建管道的類處理,有時候這個類允許其他人通過一個MessageRouter對象接收消息,其他監(jiān)聽器可以通過這個對象注冊和接收有著唯一管道id的消息。
例如,渲染時,控制消息沒有消息指定目標(biāo)view,會被RenderProcess(渲染器)或者RenderProcessHost(瀏覽器)處理。來自資源的請求或者修改剪貼板的請求是沒有目標(biāo)view的,所以是控制消息。一個路由消息的例子是,要求一個view繪制一個區(qū)域的消息。
路由消息曾經(jīng)被用于從指定的RenderViewHost獲取消息。然而,技術(shù)上,任何類可以通過使用RenderProcessHost::GetNextRoutingID接收路由消息,并用RenderProcessHost::AddRoute注冊它自己這個消息?,F(xiàn)在,RenderFrameHost和RenderViewHost有了他們自己的路由ID了。
消息是否是獨立類型在于,消息是從瀏覽器發(fā)送到渲染器,還是從渲染器到瀏覽器。從瀏覽器到渲染器的被稱為View消息,因為它們被發(fā)送給RenderViewHost。從渲染器發(fā)送到瀏覽器的消息叫做ViewHost消息,因為他們被發(fā)送給RenderViewHost。你會注意到render_messages_internal.h里定義的消息被分為兩類。
插件也有獨立的進(jìn)程。像渲染消息那樣,PluginProcess消息(從瀏覽器發(fā)送到插件進(jìn)程)和PluginProcessHost消息(從插件進(jìn)程發(fā)送到瀏覽器)。這些消息都定義在plugin_messages_internal.h里。自動化消息(用于控制瀏覽器做UI測試)通過相同的方式完成。
聲明消息
特殊的宏用于聲明消息。渲染器和瀏覽器間發(fā)送的消息都聲明在render_messages_internal.h里。有兩個部分,一個是發(fā)送到渲染器的View消息,一個是發(fā)送到瀏覽器的ViewHost消息。
如果要聲明一個從渲染器發(fā)送到瀏覽器(一個ViewHost消息)的消息,并且指定一個view(路由)包含一個url和一個整數(shù)作為參數(shù),這樣寫:
IPC_MESSAGE_ROUTED2(ViewHostMsg_MyMessage, GURL, int)
如果要聲明一個從瀏覽器發(fā)往渲染器的控制消息(一個View消息),并且不指定目標(biāo)view(控制),不包含參數(shù),這樣寫:
IPC_MESSAGE_CONTROL0(ViewMsg_MyMessage)
包裝數(shù)據(jù)
參數(shù)通過ParamTraits模板序列化或者反序列化到消息體中。這種模板的具體化在ipc_message_utils.h中提供給大多數(shù)常見的類型。如果你定義了你自己的類型,你也需要為它定義你自己的ParamTraits具體形式。
有時候,一條消息有太多的值了,沒法合理得放到消息里。這種情況下,我們定義一個獨立的結(jié)構(gòu)來存放這些值。例如,對于FrameMsg_Navigate消息,在navigation_params.h中,定義了CommonNavigationParams結(jié)構(gòu)。frame_messages.h用IPC_STRUCT_TRAITS類型的宏定義了這個結(jié)構(gòu)的具體ParamTraits。
發(fā)送消息
你通過“channel(通道)”發(fā)送消息(往下看)。在瀏覽器里,RenderProcessHost包含了用于從瀏覽器UI線程發(fā)送消息到渲染器的channel。為了方便,RenderWidgetHost(RenderViewHost的基類)提供了一個Send函數(shù)。
消息由指針發(fā)送,并將在它們分發(fā)后由IPC層刪除。因此,一旦你發(fā)現(xiàn)合適的Send函數(shù),盡管帶著一條新消息去調(diào)用它:
Send(new ViewMsg_StopFinding(routing_id_));
注意,你必須按順序指定路由ID,讓消息能夠路由到接收端正確的View/ViewHost。RenderWidgetHost(RenderViewHost的基類)和RenderWidget(RenderViewHost的基類)都有GetRoutingID()成員函數(shù)給你使用。
處理消息
消息由對IPC::Listener的實現(xiàn)來處理,其中最重要的函數(shù)是OnMessageReceived。我們有大量的宏來簡化這個函數(shù)中的消息處理,這個最好可以用例子來闡述:
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來實現(xiàn)自己的函數(shù)定義。在這個例子里,不要指定消息變量名,它會在給定的類上聲明一個OnMessageReceived函數(shù),并實現(xiàn)之。
其他宏:
- IPC_MESSAGE_FORWARD:這與IPC_MESSAGE_HANDLER相同,但你可以指定你自己的類來作為消息發(fā)送的目的地,而非發(fā)送給當(dāng)前類。
IPC_MESSAGE_FORWARD(ViewHostMsg_MyMessage, some_object_pointer, SomeObject::OnMyMessage)
- IPC_MESSAGE_HANDLER_GENERIC:這允許你編寫自己的代碼,但你必須自己從消息中解包出參數(shù)。
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_MyMessage, printf("Hello, world, I got the message."))
安全考慮
IPC中的安全漏洞有著嚴(yán)重的后果(文件盜取,沙箱逃逸,遠(yuǎn)程代碼執(zhí)行),查看我們的IPC安全文檔以獲取如何避免常見陷阱的一些提示。
通道
IPC::Channel()(定義在ipc/ipc_channel.h里)定義了通過管道交流的方法。IPC::SyncChannel提供了額外的功能用于同步等待一些消息的響應(yīng)(正如下面的“同步消息”描述的,渲染器進(jìn)程使用了這個特性,但瀏覽器進(jìn)程不會這樣做)。
通道不是線程安全的,我們通常希望用通道在另一個線程里發(fā)送消息。例如,當(dāng)UI線程希望發(fā)送消息時,它必須通過I/O線程。為此,我們使用IPC::ChannelProxy。它有著與正常通道對象類似的API,但它把消息代理到另一個線程去發(fā)送,而在收到這些消息時,把消息代理回原來的線程。這允許你的對象(通常是在UI線程中)在通道線程(通常是在I/O線程中)安裝一個IPC::ChannelProxy::Listener,以此從代理的消息中過濾掉一些消息。我們使用這個特性去做資源請求以及其他可以直接在I/O線程處理的請求。RenderProcessHost安裝一個RenderMessageFilter對象執(zhí)行這種過濾。
同步消息
有些消息應(yīng)該從渲染器的角度來同步。這大多數(shù)時候發(fā)生在,有一個支持返回值的WebKit調(diào)用,但我們必須在瀏覽器中執(zhí)行這個調(diào)用。這種消息的例子是拼寫檢查以及在javaScript中獲取cookie。同步瀏覽器到渲染器的IPC是不允許的,以此避免在一個潛在的片段渲染器中阻塞用戶界面。
警告: 不要在UI線程處理任何同步消息!你必須在I/O線程中處理他們。否則,應(yīng)用程序可能因為插件等待UI線程的同步繪制而陷入死鎖,而渲染器等待瀏覽器同步消息時也會有一些阻塞。
聲明同步消息
同步消息用IPC_SYNC_MESSAGE_*這樣的宏來聲明。這些宏有輸入,也有返回值()(非同步消息沒有返回參數(shù)的概念)。對于一個有著兩個輸入?yún)?shù)和一個返回參數(shù)的控制函數(shù),你應(yīng)該在宏的名字中插入“2_1”:
IPC_SYNC_MESSAGE_CONTROL2_1(SomeMessage, // Message name
GURL, //input_param1
int, //input_param2
std::string); //result
類似的,你也可以讓消息路由到view,這種情況下你需要把“CONTROL”換成“ROUTED”,得到IPC_SYNC_MESSAGE_*。你也可以沒有輸入或返回參數(shù)。沒有返回參數(shù)常用于渲染器必須等待瀏覽器完成某些操作但不需要結(jié)果時。我們在某些打印和剪貼板操作使用這種特性。
分發(fā)同步消息
當(dāng)WebKit線程分發(fā)出一個同步IPC請求時,請求對象(繼承自IPC::SyncMessage)會在渲染器中通過IPC::SyncChannel對象分發(fā)給主線程。所有同步的消息也是通過它發(fā)送的。同步通道在接收到同步消息時,會阻塞調(diào)用線程,只有當(dāng)收到回復(fù)時,才會解除阻塞。
在WebKit線程等待同步請求時,主線程仍然會從瀏覽器進(jìn)程接收消息。這些消息會添加到WebKit線程里,等到WebKit線程被喚醒時處理它們。當(dāng)同步消息回復(fù)被接收時,這個線程會解除阻塞。注意這意味著同步消息回復(fù)可以不按順序處理。
同步消息和正常的消息用同樣的方式,帶著賦予構(gòu)造器的輸出參數(shù)發(fā)送出去,例如:
const GURL input_param("http://www.google.com/");
std::string result;
content::RenderThread::Get()->Send(new MyMessage(input_param, &result));
printf("The result is %s\n", result.c_str());
處理同步消息
同步消息和異步消息使用相同的IPC_MESSAGE_HANDLER等宏來分發(fā)消息。消息處理函數(shù)與消息構(gòu)造器有著相同的函數(shù)簽名,這個函數(shù)會簡單把輸出寫到輸出參數(shù)中。在上面的消息里,你可以添加:
IPC_MESSAGE_HANDLER(MyMessage, OnMyMessage)
到OnMessageReceived函數(shù),然后這樣寫:
void RenderProcessHost::OnMyMessage(GURL input_param, std::string* result) {
*result = input_param.spec() + " is not available";
}
轉(zhuǎn)換消息類型為消息名
如果運行崩潰了,并且此時你有消息的類型,你可以把它轉(zhuǎn)為消息名。這種消息類型是一個32位值,高16位是類,低16位是ID。類基于ipc/ipc_message_start.h中的枚舉值,id基于定義消息的文件中的行號。這意味著你需要獲取準(zhǔn)確的Chromium版本以獲取消息名。
一個554011中的例子是Chromium ad0950c1ac32ef02b0b0133ebac2a0fa4771cf20 版0x1c0098中。類0x1c,意味著行40,匹配ChildProcessMsgStart。ChildProcessMsgStart消息在content/common/child_process_messages.h中,而0x98行,即152行,對應(yīng)的IPC是ChildProcessHostMsg_ChildHistogramData.
當(dāng)你在處理content::RenderProcessHostImpl::OnBadMessageReceived導(dǎo)致的crash時,這項技術(shù)非常有用。