在<<WebKit模組化分析>>中說到WebKit2中的多進程模型。多進程模型已經是瀏覽器的基本架構要素,下面展開分析一下WebKit2中的多進程模型。
協作決定介面,確立責任分工後,對於模組或系統間最重要的事莫過於介面定義,而且是有著簡潔明確的定義。對於WebKit2中三個進程中的互動也是相當頻繁和多樣,如果使用傳統的查表法對應解析執行,就會面臨巨大的維護成本。WebKit2使用了Encoder和Decoder的概念的很好地將訊息解析的工作放到各個功能上。於是提供的公用介面主要關注於訊息的分發,而不再是似乎擁有一切的解析執行功能。
WebKit2在多進程介面上Process Launcher、CoreIPC以及WorkQueue構成了核心控制功能 (進程本身的Run Loop自然也是,但非此處的關注點),Encoder/Decoder/FunctionWrapper則是重要的工具類。下面就是對它們進行的學習和分析。
多執行緒模式的基本架構
先從基本結構看起。多進程模型是在WebKit中實現的,由WebKit中的三個Process分別處理應用主進程,網頁處理進程及外掛程式進程。它們在具體的實現上是基於Process Launcher(WebKit2/UIProcess/Launcher)進行進程管理,再通過Core IPC進行跨進程通訊(IPC)。
CoreIPC做為一個公用,且由各個進程共用的模組,日後會被放置到WebCore或WTF中。放在WTF中較為合適,因為它在模組化的角色就是公用功能模組。
Process Launcher具體建立進程的代碼需要平台適配,它使用了如下的方式: ProcessLauncher.h和ProcessLauncher.cpp有通用的定義和公用代碼的實現。再為不同平台建立不同的單元檔案來實現平台差異的部分。負責建立新進程的launchProcess就在其中。
Core IPC也是類似的實現方式。在不同的平台下使用不同的IPC技術。 Mach Port是Mac OS下類似pipes技術的實現,而XPC則是新推出一種更為安全的IPC技術,目前國內相關的中文資料並不多,還是要花時研究官方文檔(Creating
XPC Services)和範例程式碼(SandboxedFetch)。另外網友rainbird還推薦一個對XPC的封裝。
多進程模型的互動機制
WebKit2中使用來Proxy來與其它進程進行通訊。 WebProcessProxy與PluginProcessProxy均會繼承自ProcessLauncher::Client, 最後是通過CoreIPC::Connection來完成通訊。因為存在頁面上的對應關係,所以訊息傳遞時都會帶一個Page ID來標識,這樣在共用Web Process時就不會出現混淆的問題。是一個進程通訊的架構, CoreIPC::Connection會通過與其它幾個類一起完成進程間訊息傳遞: 流程也很容易理解,訊息寄件者通過Encoder對要發送的資訊進行編碼,再由Work Queue控制觸發發送或接收操作,最後由Decoder進行解碼,最後交由目標類解析執行。訊息寄件者Sender Class會繼承自CoreIPC::MessageSender,而接收者Target Class會繼承自CoreIPC::MessageReceiver。Encoder的實現與平台無關。Work Queue自己會有隊列管理機制觸發發送操作,再由註冊回呼函數觸發接收操作。無論發送和接收的函數都是由Connection通Function Wrapper提供的,Work Queue就是負責在適當的時機下觸發。它在不同平台使用了不同的技術: 關於GCD可以參考一下這篇文章: GCD介紹 Windows下的Thread Pool的說明可以看這裡: Thread Pool API(MSDN)下面Work Queue的類圖:
.dispatch用於發送時,由Connection調用並傳入負責發送的函數控制代碼。.executeFunction則是用於執行指定的函數指標。.invalidate則是用於清除當前排入的任務隊列。.platformXXX表示的是對應於各個平台的不同實現。其跨平台模式類似於ProcessLauncher。.registerXXX與unregisterXXX用於註冊和取消處理接收到新訊息的回呼函數,這個函數也是由Connection指定的( Mac OS下和Windows下的函數名不同)。
下面是Connection與WorkQueue互動示意的順序圖表:
多進程模型中訊息資料設計上面提到了訊息在傳遞過程中外發的訊息(outgoing message)和收到的訊息(incoming message)都由CoreIPC::Connection類來管理,而且包含一個編解碼的過程。1. 訊息每一個訊息都有一個特定的定義。如下所示,Arguement1和Argument2是兩個模板。 首先CoreIPC為不同數量參數的訊息定義了一套模板,就是ArgumentX, X從1到8,就是最大表示8個參數的訊息,模板資料類型指定的是各個參數的類型。其中一部分使用了修飾模式。不同參數的模板都採用對前一個模板的實現來達到複用的目的。 這種定義方式和一般的查表法是不同的,目的在於訊息本身就能表示自己,而不需要再做額外的對訊息的映射。下面是LoadURL訊息的定義:
struct LoadURL : CoreIPC::Arguments2<constWTF::String&, constWebKit::SandboxExtension::Handle&>
{
static const Kind messageID = LoadURLID;
static CoreIPC::StringReference receiverName() { return messageReceiverName();
}
staticCoreIPC::StringReference name() { returnCoreIPC::StringReference("LoadURL");
}
staticconstbool isSync = false;
typedefCoreIPC::Arguments2<constWTF::String&, constWebKit::SandboxExtension::Handle&>
DecodeType;
LoadURL(const WTF::String& url, const WebKit::SandboxExtension::Handle&
sandboxExtensionHandle)
: CoreIPC::Arguments2<const WTF::String&, const WebKit::SandboxExtension::Handle&>(url,
sandboxExtensionHandle)
{
}
};2. Encoder所謂Encoder就是資訊的各個參數組裝到一個buffer中的操作。Encode操作都是通過CoreIPC::MessageEncoder來完成的。它對Message的Message Name, Receiver Name, Destination ID(即Page ID)以及各個參數進行編碼。 *encode方法有不同類型的副本。*m_inlineBuffer是字元數組,會比m_buffer動態分配的方式效率要高。下面Encode的結果(以loadURL為例):實際發送時就是將這個buffer的內容發送出去。再由接收到使用對應的Decoder解析出來。 通過ReceiverName可以建立一個MessageReceiver對象,即此訊息的處理對象。就這樣,這個訊息就確定應當由誰來處理了。
bool MessageReceiverMap::dispatchMessage(Connection* connection, MessageID messageID, MessageDecoder&
decoder)
{
if (MessageReceiver* messageReceiver = m_globalMessageReceivers.get(decoder.messageReceiverName()))
{
ASSERT(!decoder.destinationID());
messageReceiver->didReceiveMessage(connection, messageID, decoder);
return true;
} ……}
和傳統的處理方式相比,是不是簡潔很多。雖然Recevier Names還是要有表存在,但相對常常的switch/case或查表法,它的變動性已經被極大的縮小了,也就降低了日後的維護成本。如果要接受訊息處理,只要調用MessageReceiverMap::addMessageReceiver()添加一項Receiver,而參數就是Receiver的名稱和類的執行個體。
再到具體的Message處理對象中處理,對於要接受訊息處理的類,會繼承自MessageReceiver,使得它們都有一個訊息處理的入口。如:
voidWebPage::didReceiveWebPageMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::MessageDecoder& decoder){ ……
if (decoder.messageName() == Messages::WebPage::LoadURL::name())
{
CoreIPC::handleMessage<Messages::WebPage::LoadURL>(decoder, this,
&WebPage::loadURL);
return;
} ……}最終通過callMemberFunction()來執行訊息對應的函數,即上面代碼指定的this和&WebPage::loadURL,decoder中則儲存著參數。3. Function WrapperFunction Wrapper的功能就是將函數或者某個對象的成員封裝成函數指標。在Work Queue與Connection的函數調用過程都是使用Function Wrapper來完成的。 Function Wrapper也是一個模板類,用於達到多態以支援不同的函數類型。比如下面支援類成員函數的定義(Functional.h):
template<typename R, typename C>
class FunctionWrapper<R (C::*)()> {
public:
typedef R ResultType;
static const bool shouldRefFirstParameter = HasRefAndDeref<C>::value;
static const bool shouldValidateFirstParameter = true;
explicit FunctionWrapper(R (C::*function)())
: m_function(function)
{
}
R operator()(C* c)
{
return (c->*m_function)();
}
private:
R (C::*m_function)();
};
Function Wrapper對Work Queue要封裝就是Connection::sendOutgoingMessage和Connection::dispatchOneMessage()兩個分別用於處理髮送和收到的訊息。
多進程模型的互動順序圖表樣本以下是一個互動的順序圖表,方便更好地理解它的設計。在UI上載入頁面時,UIProcess中的WebPageProxy向WebProcess中的WebPage發起loadURL請求。首先會通過調用Connection增加一個message到自己的outgoing message queue中。然後將自身的發送函數(sendOutgoingMessages)發送給WorkQueue對象執行,而不是直接把訊息發送到WorkQueue中執行。所以WorkQueue只是一個控制項類,而不是介面類。 WorkQueue會在適當的時機執行提交的任務,由executeFunction來執行相應的發送函數,將訊息通過系統的機制發送出去。而在WebProcess中,它會先在WorkQueue(不同的執行個體對象)中註冊一個事件處理函數,當事件進來後,它就會被執行到。進行觸發Connection對新的訊息進行解析並加入到自己的incoming message queue中。 Web Process會在自己的Run Loop中要求Connection解析收到的訊息,並在解析後調用對應的對象的處理函數。通過callMemberFunction()就最終調用到了WebPage::loadURL()函數。轉載請註明出處:http://blog.csdn.net/horkychen