[Chromium中文文檔]跨進程通信 (IPC)

跨進程通信 (IPC)

轉載請註明出處:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/Inter-process_Communication.html

全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh

概覽

Chromium有一個多進程架構,這意味着我們有許多需要互相交流的進程。我們的主要跨進程交流元素是命名管道。在Linux和OS X上,我們使用socketpair()。每個渲染器進程可以分配到一個命名管道來跟瀏覽器進程交流。這些管道是用異步方式使用的,確保沒有哪個端會等待另一個端。

想要得到如何編寫安全的IPC端點的知識,請查看IPC安全要點.

瀏覽器中IPC

在瀏覽器中,與渲染器的交流是通過一個獨立的I/O線程完成的。來自或者去往view的消息需要使用一個ChannelProxy代理到主線程。這種方案的優點是,資源請求(比如網頁等),這種最經常且極其關注性能的消息,可以整個的在I/O線程中處理,不會阻塞用戶界面。這些通過使用Channel::MessageFilter(由RenderProcessHost插入channel)來完成。這個過濾器運行在I/O線程裏,攔截資源請求信息,將它們直接轉發到資源分發主機。查看多進程資源加載獲取更多關於資源加載的信息。

渲染器中的IPC

每個渲染器也有一個線程管理交流(在這個例子裏,是主線程),而大多數渲染和大多數處理髮生在另一個線程裏(查看多進程架構的那個圖表)。大多數消息通過主渲染線程從瀏覽器發送給WebKit線程,反之亦然。這個額外的線程是用於支持同步的渲染器到瀏覽器的消息(參考下面的“同步消息”)。

消息

消息的類型

我們有兩種基本的消息類型:”路由“和”控制“。控制消息由創建管道的類處理,有時候這個類允許其他人通過一個MessageRouter對象接收消息,其他監聽器可以通過這個對象註冊和接收有着唯一管道id的消息。

例如,渲染時,控制消息沒有消息指定目標view,會被RenderProcess(渲染器)或者RenderProcessHost(瀏覽器)處理。來自資源的請求或者修改剪貼板的請求是沒有目標view的,所以是控制消息。一個路由消息的例子是,要求一個view繪製一個區域的消息。

路由消息曾經被用於從指定的RenderViewHost獲取消息。然而,技術上,任何類可以通過使用RenderProcessHost::GetNextRoutingID接收路由消息,並用RenderProcessHost::AddRoute註冊它自己這個消息。現在,RenderFrameHost和RenderViewHost有了他們自己的路由ID了。

消息是否是獨立類型在於,消息是從瀏覽器發送到渲染器,還是從渲染器到瀏覽器。從瀏覽器到渲染器的被稱爲View消息,因爲它們被髮送給RenderViewHost。從渲染器發送到瀏覽器的消息叫做ViewHost消息,因爲他們被髮送給RenderViewHost。你會注意到render_messages_internal.h裏定義的消息被分爲兩類。

插件也有獨立的進程。像渲染消息那樣,PluginProcess消息(從瀏覽器發送到插件進程)和PluginProcessHost消息(從插件進程發送到瀏覽器)。這些消息都定義在plugin_messages_internal.h裏。自動化消息(用於控制瀏覽器做UI測試)通過相同的方式完成。

聲明消息

特殊的宏用於聲明消息。渲染器和瀏覽器間發送的消息都聲明在render_messages_internal.h裏。有兩個部分,一個是發送到渲染器的View消息,一個是發送到瀏覽器的ViewHost消息。

如果要聲明一個從渲染器發送到瀏覽器(一個ViewHost消息)的消息,並且指定一個view(路由)包含一個url和一個整數作爲參數,這樣寫:

IPC_MESSAGE_ROUTED2(ViewHostMsg_MyMessage, GURL, int)

如果要聲明一個從瀏覽器發往渲染器的控制消息(一個View消息),並且不指定目標view(控制),不包含參數,這樣寫:

IPC_MESSAGE_CONTROL0(ViewMsg_MyMessage)

包裝數據

參數通過ParamTraits模板序列化或者反序列化到消息體中。這種模板的具體化在ipc_message_utils.h中提供給大多數常見的類型。如果你定義了你自己的類型,你也需要爲它定義你自己的ParamTraits具體形式。

有時候,一條消息有太多的值了,沒法合理得放到消息裏。這種情況下,我們定義一個獨立的結構來存放這些值。例如,對於FrameMsg_Navigate消息,在navigation_params.h中,定義了CommonNavigationParams結構。frame_messages.hIPC_STRUCT_TRAITS類型的宏定義了這個結構的具體ParamTraits。

發送消息

你通過“channel(通道)”發送消息(往下看)。在瀏覽器裏,RenderProcessHost包含了用於從瀏覽器UI線程發送消息到渲染器的channel。爲了方便,RenderWidgetHost(RenderViewHost的基類)提供了一個Send函數。

消息由指針發送,並將在它們分發後由IPC層刪除。因此,一旦你發現合適的Send函數,儘管帶着一條新消息去調用它:

Send(new ViewMsg_StopFinding(routing_id_));

注意,你必須按順序指定路由ID,讓消息能夠路由到接收端正確的View/ViewHost。RenderWidgetHost(RenderViewHost的基類)和RenderWidget(RenderViewHost的基類)都有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函數,並實現之。

其他宏:

  • IPC_MESSAGE_FORWARD:這與IPC_MESSAGE_HANDLER相同,但你可以指定你自己的類來作爲消息發送的目的地,而非發送給當前類。
IPC_MESSAGE_FORWARD(ViewHostMsg_MyMessage, some_object_pointer, SomeObject::OnMyMessage)
  • IPC_MESSAGE_HANDLER_GENERIC:這允許你編寫自己的代碼,但你必須自己從消息中解包出參數。
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_MyMessage, printf("Hello, world, I got the message."))

安全考慮

IPC中的安全漏洞有着嚴重的後果(文件盜取,沙箱逃逸,遠程代碼執行),查看我們的IPC安全文檔以獲取如何避免常見陷阱的一些提示。

通道

IPC::Channel()(定義在ipc/ipc_channel.h裏)定義了通過管道交流的方法。IPC::SyncChannel提供了額外的功能用於同步等待一些消息的響應(正如下面的“同步消息”描述的,渲染器進程使用了這個特性,但瀏覽器進程不會這樣做)。

通道不是線程安全的,我們通常希望用通道在另一個線程裏發送消息。例如,當UI線程希望發送消息時,它必須通過I/O線程。爲此,我們使用IPC::ChannelProxy。它有着與正常通道對象類似的API,但它把消息代理到另一個線程去發送,而在收到這些消息時,把消息代理回原來的線程。這允許你的對象(通常是在UI線程中)在通道線程(通常是在I/O線程中)安裝一個IPC::ChannelProxy::Listener,以此從代理的消息中過濾掉一些消息。我們使用這個特性去做資源請求以及其他可以直接在I/O線程處理的請求。RenderProcessHost安裝一個RenderMessageFilter對象執行這種過濾。

同步消息

有些消息應該從渲染器的角度來同步。這大多數時候發生在,有一個支持返回值的WebKit調用,但我們必須在瀏覽器中執行這個調用。這種消息的例子是拼寫檢查以及在javaScript中獲取cookie。同步瀏覽器到渲染器的IPC是不允許的,以此避免在一個潛在的片段渲染器中阻塞用戶界面。

警告: 不要在UI線程處理任何同步消息!你必須在I/O線程中處理他們。否則,應用程序可能因爲插件等待UI線程的同步繪製而陷入死鎖,而渲染器等待瀏覽器同步消息時也會有一些阻塞。

聲明同步消息

同步消息用IPC_SYNC_MESSAGE_*這樣的宏來聲明。這些宏有輸入,也有返回值()(非同步消息沒有返回參數的概念)。對於一個有着兩個輸入參數和一個返回參數的控制函數,你應該在宏的名字中插入“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_*。你也可以沒有輸入或返回參數。沒有返回參數常用於渲染器必須等待瀏覽器完成某些操作但不需要結果時。我們在某些打印和剪貼板操作使用這種特性。

分發同步消息

當WebKit線程分發出一個同步IPC請求時,請求對象(繼承自IPC::SyncMessage)會在渲染器中通過IPC::SyncChannel對象分發給主線程。所有同步的消息也是通過它發送的。同步通道在接收到同步消息時,會阻塞調用線程,只有當收到回覆時,纔會解除阻塞。

在WebKit線程等待同步請求時,主線程仍然會從瀏覽器進程接收消息。這些消息會添加到WebKit線程裏,等到WebKit線程被喚醒時處理它們。當同步消息回覆被接收時,這個線程會解除阻塞。注意這意味着同步消息回覆可以不按順序處理。

同步消息和正常的消息用同樣的方式,帶着賦予構造器的輸出參數發送出去,例如:

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等宏來分發消息。消息處理函數與消息構造器有着相同的函數簽名,這個函數會簡單把輸出寫到輸出參數中。在上面的消息裏,你可以添加:

IPC_MESSAGE_HANDLER(MyMessage, OnMyMessage)

到OnMessageReceived函數,然後這樣寫:

void RenderProcessHost::OnMyMessage(GURL input_param, std::string* result) {
  *result = input_param.spec() + " is not available";
}

轉換消息類型爲消息名

如果運行崩潰了,並且此時你有消息的類型,你可以把它轉爲消息名。這種消息類型是一個32位值,高16位是類,低16位是ID。類基於ipc/ipc_message_start.h中的枚舉值,id基於定義消息的文件中的行號。這意味着你需要獲取準確的Chromium版本以獲取消息名。

一個554011中的例子是Chromium ad0950c1ac32ef02b0b0133ebac2a0fa4771cf20 版0x1c0098中。類0x1c,意味着行40,匹配ChildProcessMsgStart。ChildProcessMsgStart消息在content/common/child_process_messages.h中,而0x98行,即152行,對應的IPC是ChildProcessHostMsg_ChildHistogramData.

當你在處理content::RenderProcessHostImpl::OnBadMessageReceived導致的crash時,這項技術非常有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章