Chrome原始碼剖析【二】:【二】Chrome的程序間通訊
【二】Chrome的程序間通訊
1. Chrome程序通訊的基本模式
程序間通訊,叫做IPC(Inter-Process Communication),在Chrome不多的文件中,有一篇就是介紹這個的,在這裡。Chrome最主要有三類程序,一類是Browser主程序,我們一直尊稱它老人家為老大;還有一類是各個Render程序,前面也提過了;另外還有一類一直沒說過,是Plugin程序,每一個外掛,在Chrome中都是以程序的形式呈現,等到後面說外掛的時候再提罷了。Render程序和Plugin程序都與老大保持程序間的通訊,Render程序與Plugin程序之間也有彼此聯絡的通路,唯獨是多個Render程序或多個Plugin程序直接,沒有互相聯絡的途徑,全靠老大協調
管道名字的協商 |
|
圖5 Chrome的IPC處理流程圖 |
溫柔的訊息迴圈 |
2. 程序間的跨執行緒通訊和同步通訊
在Chrome中,任何底層的資料都是執行緒非安全的,Channel不是太上老君(抑或中國足球?...),它也沒有例外。在每一個程序中,只能有一個執行緒來負責操作Channel,這個執行緒叫做IO執行緒(名不符實真是一件悲涼的事情...)。其它執行緒要是企圖越俎代庖,是會出大亂子的。。。
但是有時候(其實是大部分時候...),我們需要從非IO執行緒與別的程序相通訊,這該如何是好?如果,你有看過我前面寫的執行緒模型,你一定可以想到,做法很簡單,先將對Channel的操作放到Task中,將此Task放到IO執行緒佇列裡,讓IO執行緒來處理即可。當然,由於這種事情發生的太頻繁,每次都人肉做一次頗為繁瑣,於是有一個代理類,叫做ChannelProxy,來幫助你完成這一切。。。
從介面上看,ChannelProxy的介面和Channel沒有大的區別(否則就不叫Proxy了...),你可以像用Channel一樣,用ChannelProxy來Send你的訊息,ChannelProxy會辛勤的幫你完成剩餘的封裝Task等工作。不僅如此,ChannelProxy還青出於藍勝於藍,在這個層面上做了更多的事情,比如:傳送同步訊息。。。
不過能傳送同步訊息的類不是ChannelProxy,而是它的子類,SyncChannel。在Channel那裡,所有的訊息都是非同步的(在Windows中,也叫Overlapped...),其本身也不支援同步邏輯。為了實現同步,SyncChannel並沒有另造輪子,而只是在Channel的層面上加了一個等待操作。當ChannelProxy的Send操作返回後,SyncChannel會把自己阻塞在一組訊號量上,等待回包,直到永遠或超時。從外表上看同步和非同步沒有什麼區別,但在使用上還是要小心,在UI執行緒中使用同步訊息,是容易被髮指的。。。
3. Chrome中的IPC訊息格式
說了半天,還有一個大頭沒有提過,那就是訊息包。如果說,多執行緒模式下,對資料的訪問開銷來自於鎖,那麼在多程序模式下,大部分的額外開銷都來自於程序間的訊息拆裝和傳遞。不論怎麼樣的模式,只要程序不同,訊息的打包,序列化,反序列化,組包,都是不可避免的工作。。。
在Chrome中,IPC之間的通訊訊息,都是派生自IPC::Message類的。對於訊息而言,序列化和反序列化是必須要支援的,Message的基類Pickle,就是幹這個活的。Pickle提供了一組的介面,可以接受int,char,等等各種資料的輸入,但是在Pickle內部,所有的一切都沒有區別,都轉化成了一坨二進位制流。這個二進位制流是32位齊位的,比如你只傳了一個bool,也是最少佔32位的,同時,Pickle的流是有自增邏輯的(就是說它會先開一個Buffer,如果滿了的話,會加倍這個Buffer...),使其可以無限擴充套件。Pickle本身不維護任何二進位制流邏輯上的資訊,這個任務交到了上級處理(後面會有說到...),但Pickle會為二進位制流新增一個頭資訊,這個裡面會存放流的長度,Message在繼承Pickle的時候,擴充套件了這個頭的定義,完整的訊息格式如下:
圖6 Chrome的IPC訊息格式 |
訊息的序列化 |
4. 定義IPC訊息
如果你寫過MFC程式,對MFC那裡面一大堆巨集有所忌憚的話,那麼很不幸,在Chrome中的IPC訊息定義中,你需要再吃一點苦頭了,甚至,更苦大仇深一些;如果你曾經領教過用模板的特化偏特化做Traits、用模板做函式過載、用編譯期的Tuple做變引數支援,之類機制的種種麻煩的話,那麼,同樣很遺憾,在Chrome中,你需要再感受一次。。。
不過,先讓我們忘記巨集和模板,看人肉一個訊息,到底需要哪些操作。一個標準的IPC訊息定義應該是類似於這樣的:
class SomeMessage大概意思是這樣的,你需要從Message(或者其他子類)派生出一個子類,該子類有一個獨一無二的ID值,該子類接受一個引數,你需要對這個引數進行序列化。兩個麻煩的地方看的很清楚,如果生成獨一無二的ID值?如何更方便的對任何引數可以自動的序列化?。。。 在Chrome中,解決這兩個問題的答案,就是巨集 + 模板。Chrome為每個訊息安排了一種ID規格,用一個16bits的值來表示,高4位標識一個Channel,低12位標識一個訊息的子id,也就是說,最多可以有16種Channel存在不同的程序之間,每一種Channel上可以定義4k的訊息。目前,Chrome已經用掉了8種Channel(如果A、B程序需要雙向通訊,在Chrome中,這是兩種不同的Channel,需要定義不同的訊息,也就是說,一種雙向的程序通訊關係,需要耗費兩個Channel種類...),他們已經覺得,16bits的ID格式不夠用了,在將來的某一天,估計就被擴充套件成了32bits的。書歸正傳,Chrome是這麼來定義訊息ID的,用一個列舉類,讓它從高到低往下走,就像這樣:
: public IPC::Message { public: enum { ID = ...; } SomeMessage(SomeType & data) : IPC::Message(MSG_ROUTING_CONTROL, ID, ToString(data)) {...} ... };
enum SomeChannel_MsgType這是一個型別為5的Channel的訊息ID宣告,由於指明瞭最開始的兩個值,所以後續列舉的值會依次遞減,如此,只要維護Channel型別的唯一性,就可以維護所有訊息ID的唯一性了(當然,前提是不能超過訊息上限...)。但是,定義一個ID還不夠,你還需要定義一個使用該訊息ID的Message子類。這個步驟不但繁瑣,最重要的,是違反了DIY原則,為了新增一個訊息,你需要在兩個地方開工幹活,是可忍孰不可忍,於是Google祭出了巨集這顆原子彈,需要定義訊息,格式如下:
{ SomeChannelStart = 5 << 12, SomeChannelPreStart = (5 << 12) - 1, Msg1, Msg2, Msg3, ... MsgN, SomeChannelEnd };
IPC_BEGIN_MESSAGES(PluginProcess, 3) IPC_MESSAGE_CONTROL2(PluginProcessMsg_CreateChannel, int /* process_id */, HANDLE /* renderer handle */) IPC_MESSAGE_CONTROL1(PluginProcessMsg_ShutdownResponse, bool /* ok to shutdown */) IPC_MESSAGE_CONTROL1(PluginProcessMsg_PluginMessage, std::vector<uint8> /* opaque data */) IPC_MESSAGE_CONTROL0(PluginProcessMsg_BrowserShutdown) IPC_END_MESSAGES(PluginProcess)這是Chrome中,定義PluginProcess訊息的巨集,我挖過來放在這了,如果你想新增一條訊息,只需要新增一條類似與IPC_MESSAGE_CONTROL0東東即可,這說明它是一個控制訊息,引數為0個。你基本上可以這樣理解,IPC_BEGIN_MESSAGES就相當於完成了一個列舉開始的宣告,然後中間的每一條,都會在列舉裡面增加一個ID,並宣告一個子類。這個一巨集兩吃,直逼北京烤鴨兩吃的高超做法,可以參看ipc_message_macros.h,或者看下面一巨集兩吃的一個舉例。。。
多次展開巨集的技巧 |
IPC_BEGIN_MESSAGE_MAP_EX(RenderProcessHost, msg, msg_is_ok)這個東西很簡單,展開後基本可以視為一個Switch迴圈,判斷訊息ID,然後將訊息,傳遞給對應的函式。與MFC的Message Map比起來,做的事情少多了。。。 通過巨集的手段,可以解決訊息類宣告和訊息的分發問題,但是自動的序列化還不能支援(所謂自動的序列化,就是不論你是什麼型別的引數,幾個引數,都可以直接序列化,不需要另寫程式碼...)。在C++這種語言中,所謂自動的序列化,自動的型別識別,自動的XXX,往往都是通過模板來實現的。這些所謂的自動化,其實就是通過事前的大量人肉勞作,和模板自動遞推來實現的,如果說.Net或Java中的自動序列化是過山軌道,這就是那挑夫的驕子,雖然最後都是兩腿不動到了山頂,這底下費得力氣真是天壤之別啊。具體實現技巧,有興趣的看看《STL原始碼剖析》,或者是《C++新思維》,或者Chrome中的ipc_message_utils.h,這要說清楚實在不是一兩句的事情。。。 總之通過巨集和模板,你可以很簡單的宣告一個訊息,這個訊息可以傳入各式各樣的引數(這裡用到了誇張的修辭手法,其實,只要是模板實現的自動化,永遠都是有限制的,在Chrome的模板實現中,引數數量不要超過5個,型別需要是基本型別、STL容器等,在不BT的場合,應該夠用了...),你可以呼叫Channel、ChannelProxy、SyncChannel之類的Send方法,將訊息傳送給其他程序,並且,實現一個Listener類,用Message Map來分發訊息給對應的處理函式。如此,整個IPC體系搭建完成。。。
IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdatedCacheStats, OnUpdatedCacheStats) IPC_MESSAGE_UNHANDLED_ERROR() IPC_END_MESSAGE_MAP_EX()
補充幾點: 第一點,當某個類傳送訊息時,先要 通過巨集實際建立一個訊息類,然後建立這個訊息類的例項,在例項的建構函式中通過模板引數偏特化完成序列化,再把這個訊息例項通過channelproxy發往一個sendtask到自己的io執行緒,這個task的run是channelproxy::context->send(msg...),這個send實質最後呼叫到channel中的send方法,send方法完成兩件事:1、這個訊息入管道訊息佇列 2、實時的觸發傳送一次訊息管道推送ProcessOutgoingMessages()(這個推送只是推送中的一次而已)。(ps:管道與管道訊息佇列的聯合工作是通過完成埠完成:OnIOCompleted()完成,而oniocomplete()與CMessagePumpForIO::WaitForIOCompletion()關聯,又一次將訊息佇列與訊息迴圈聯合起來,這裡充分體現瞭解決方案的一致性)
苦力的巨集和模板 |
相關推薦
Chrome原始碼剖析【二】:【二】Chrome的程序間通訊
【二】Chrome的程序間通訊 1. Chrome程序通訊的基本模式 程序間通訊,叫做IPC(Inter-Process Communication),在Chrome不多的文件中,有一篇就是介紹這個的,在這裡。Chrome最主要有三類程序,一類是Browser主程序,我們一
Google Chrome原始碼剖析【三】:程序模型
【三】 Chrome的程序模型 1. 基本的程序結構 Chrome是一個多程序的架構,不過所有的程序都會由老大,Browser程序來管理,走的是集中化管理的路子。在Browser程序中,有xxxProcessHost,每一個host,都對應著一個Process,比如Re
Chrome原始碼剖析【二】
【二】Chrome的程序間通訊 1. Chrome程序通訊的基本模式 程序間通訊,叫做IPC(Inter-Process Communication),在Chrome不多的文件中,有一篇就是介紹這個的,在這裡。Chrome最主要有三類程序,一類是Browser主程序,我們
Google Chrome原始碼剖析【序】
【序】 開源是口好東西,它讓這個充斥著大量工業垃圾程式碼和教材玩具程式碼的行業,多了一些藝術氣息和美的潛質。它使得每個人,無論你來自米國紐約還是中國鐵嶺,都有機會站在巨人的肩膀上,如果不能,至少也可以抱一把大腿。。。 現在我就是來抱大腿的,這條粗腿隸屬於 Chrome(
Chrome原始碼剖析【一】
開源是口好東西,它讓這個充斥著大量工業垃圾程式碼和教材玩具程式碼的行業,多了一些藝術氣息和美的潛質。它使得每個人,無論你來自米國紐約還是中國鐵嶺,都有機會站在巨人的肩膀上,如果不能,至少也可以抱一把大腿。。。現在我就是來抱大腿的,這條粗腿隸屬於Chrome(開源專案名稱其實是Chromium,本來Chrome
Chrome原始碼剖析 【一】 Chrome的多執行緒模型
【一】 Chrome的多執行緒模型 0. Chrome的併發模型 如果你仔細看了前面的圖,對Chrome的執行緒和程序框架應該有了個基本的瞭解。Chrome有一個主程序,稱為Browser程序,它是老大,管理Chrome大部分的日常事務;其次,會有很多Rendere
Chrome原始碼剖析【三】Chrome的程序模型
1. 基本的程序結構 Chrome是一個多程序的架構,不過所有的程序都會由老大,Browser程序來管理,走的是集中化管理的路子。在Browser程序中,有xxxProcessHost,每一個host,都對應著一個Process,比如RenderProcessHost對
【leetcode筆記】:重建二叉樹
【重建二叉樹】 0.題目 http://t.cn/RA636CF 1.瞭解什麼是二叉樹的前序、中序、後序三種遍歷 關於二叉樹的前序、中序、後序三種遍歷:https://blog.csdn.net/qq_33243189/article/details/80222629
Python學習筆記【Supervisor】:使用Supervisor監控Tornado程序
Linux常見應用服務配置模式nginx和supervisor:採用主配置檔案+專案配置檔案 安裝(如果使用pip安裝注意看是否需要指定使用python2版本) 第一步:在Linux中使用apt-get 指令安裝 sudo apt-get install supervisor
【FileOutputStream類:write用法】
package test; import java.io.FileOutputStream; import java.io.IOException; /** * @author shusheng * @description * @Email [email protected] *
【FileInputStream類:讀取陣列】
package test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * @author shusheng * @description *
【應用除錯:logger-master】之一:logger-master開篇
【應用除錯:logger-master】章節列表 本篇文章是【應用除錯:logger-master】系列的開篇文章,主要介紹logger-master相關功能並作效果演示。 一 功能介紹 logger-master專案為應用開發提供便捷
【深度學習:目標檢測】RCNN學習筆記(5):faster rcnn
轉載:http://blog.csdn.net/u011534057/article/details/51247371 reference link:http://blog.csdn.net/shenxiaolu1984/article/details/51152614
【深度學習:目標檢測】RCNN學習筆記(1):Rich feature hierarchies for accurate object detection and semantic segmentat
轉載:http://blog.csdn.net/u011534057/article/details/51218218 rcnn主要作用就是用於物體檢測,就是首先通過selective search 選擇2000個候選區域,這些區域中有我們需要的所對應的物體的bound
【深度學習:目標檢測】RCNN學習筆記(10):SSD:Single Shot MultiBox Detector
之前一直想總結下SSD,奈何時間緣故一直沒有整理,在我的認知當中,SSD是對Faster RCNN RPN這一獨特步驟的延伸與整合。總而言之,在思考於RPN進行2-class分類的時候,能否借鑑YOLO並簡化faster rcnn在21分類同時整合faster rcnn中anchor boxes實現m
【深度學習:目標檢測】 RCNN學習筆記(11):R-FCN: Object Detection via Region-based Fully Convolutional Networks
轉自:http://blog.csdn.NET/shadow_guo/article/details/51767036 作者代季峰 1,14年畢業的清華博士到微軟亞洲研究院的視覺計算組,CVPR 16 兩篇一作的會議主持人同時公佈了原始碼~ 2 1. 簡介
【圖解AI:動圖】各種型別的卷積,你認全了嗎?
卷積(convolution)是深度學習中非常有用的計算操作,主要用於提取影象的特徵。在近幾年來深度學習快速發展的過程中,卷積
Python基礎總結之第六天開始【認識List:列表】【認識Tuple:元組】【還有他們基本的操作】(新手可相互督促)
早,在北京的週六,熱到不行~~~ 今天更新筆記列表(List)、元組(Tuple)以及它們的操作方法 在列表中會經常用到List列表,前面我們認識到的有字串,字串資料是不能修改當前字串裡面的任意某個字元 a='abhj'
【部分轉載】:【lower_bound、upperbound講解、二分查詢、最長上升子序列(LIS)、最長下降子序列模版】
二分 lower_bound lower_bound()在一個區間內進行二分查詢,返回第一個大於等於目標值的位置(地址) upper_bound upper_bound()與lower_bound()的主要區別在於前者返回第一個大於目標值的位置 int lowerBound(int x){ i
程序/執行緒同步的方式和機制,程序間通訊【轉】
(轉自:https://www.cnblogs.com/virusolf/p/5331946.html) 一、程序/執行緒間同步機制。 臨界區、互斥區、事件、訊號量四種方式臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)、事件(Event)的區別