背景
Binder機制阵翎,是Android系統(tǒng)跨進程協(xié)作的核心怜俐。我們知道器紧,每個應用獨立地運行在自己的進程虛擬地址空間中伞租,就像是獨自占有CPU、內存等資源十性,相互之間不可見叛溢。Android系統(tǒng)中的一個個應用,亦如一個個信息孤島烁试。正是Binder這樣的跨進程訪問機制雇初,提供了孤島之間溝通的橋梁。
如上圖所示减响,鑒于安全和效率的考慮,Android系統(tǒng)服務提供是一種典型的C/S架構郭怪,如果希望獲取系統(tǒng)服務支示,就必須進程IPC請求,而Binder正是數(shù)據(jù)交互的核心鄙才。
平時做應用開發(fā)颂鸿,其實很少會操心這種底層機制,但是設計思想?yún)s是通用的攒庵,為了學習它的這種思想嘴纺,同時弄懂Binder機制,更好地進行應用開發(fā)浓冒,這幾天找了很多資料≡钥剩現(xiàn)在開始,才有那么點繞出來的感覺稳懒。這里闲擦,不談源碼實現(xiàn),通過對各種資料的匯集,更多的以圖的形式通俗展現(xiàn)Binder的內部機制墅冷。
Binder通信基礎
就整個通信過程纯路,將圍繞著數(shù)據(jù)傳遞的前提,數(shù)據(jù)如何傳遞寞忿,以及傳遞背后的協(xié)議來展開驰唬。
用戶空間和內核空間---誰來中轉數(shù)據(jù)
我們知道,進程運行在虛擬地址空間腔彰,鑒于安全考慮定嗓,這塊空間被分為用戶空間和內核空間。其中萍桌,用戶空間的執(zhí)行權限較低宵溅,凡是需要訪問物理設備、IO等上炎,都需要通過系統(tǒng)調用的方式恃逻,由內核代碼在內核空間運行。如下圖所示:
由上圖可知藕施,每個進程通過系統(tǒng)調用進入內核寇损,Linux內核空間由系統(tǒng)內的所有進程共享。從進程的角度看裳食,每個進程擁有4G的虛擬空間矛市。每個進程有各自的私有用戶空間(0-3G),這個空間對系統(tǒng)的其他進程是不可見的诲祸。最高的1G內核空間則為所有進程以及內核所共享浊吏。這段共享的內核空間,就構成了傳統(tǒng)Linux系統(tǒng)中數(shù)據(jù)不同進程之間數(shù)據(jù)交換的基礎救氯。更進一步找田,是依賴兩個函數(shù):
數(shù)據(jù)通過 copy_from_user 來到內核空間着憨,通過copy_to_user 返回給用戶空間墩衙。
由上可知,數(shù)據(jù)的傳輸過程至少需要對數(shù)據(jù)進行兩次拷貝甲抖,但是在 Android 中又有不同漆改,為了數(shù)據(jù)的高效傳輸,它對數(shù)據(jù)的拷貝僅需要一次准谚,原因在于:Server 進程在通過 open 打開 Binder 驅動(在此處傳入了進程信息挫剑,即 binder_proc),使用 mmap 進行內存映射的時候氛魁,映射出的物理內存同時存在于內核空間和 Server 所運行的 Binder 進程的用戶空間暮顺。而內核空間和用戶空間都是虛擬邏輯空間厅篓。這樣,當數(shù)據(jù) copy_from_user 之后存在內核空間捶码,這頁內存空間所在的實際物理空間實際上也對應于用戶空間羽氮。這樣,就無需在執(zhí)行 copy_to_user的操作惫恼。
序列化與反序列化---什么樣的數(shù)據(jù)完成進程穿透
那么档押,什么樣的數(shù)據(jù),能夠方便祈纯、安全地穿透進程間壁壘令宿,完成進程間通信呢?
序列化數(shù)據(jù)腕窥。在應用層粒没,就是一個Parcel對象。
關于序列化和反序列化簇爆,下面的這張圖癞松,描述的很清楚。
如上圖所示入蛆,我們都做過將一張紙裁剪成正方體的游戲∠烊兀現(xiàn)在將正方體拆開,還原成平面的紙哨毁,然后通過傳真機發(fā)送到遠端枫甲,在遠端接收后,安裝紙面上的軌跡扼褪,還原這個正方體想幻。
實際上,這給了我們啟示迎捺,在面向對象的世界里举畸,一個復雜的對象該如何在不同進程中傳遞的思路,這是說我們需要把一個復雜的對象的簡化了凳枝,變得平滑,轉換成基礎數(shù)據(jù)結構(int ,float,long等)跋核,發(fā)送到遠端后岖瑰,再從遠端復原成復雜對象。
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
//轉換成Parcel對象砂代,完成序列化
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
//從Parcel轉換成原對象蹋订,完成反序列化
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
來一發(fā)服務請求---協(xié)議保證
什么是協(xié)議?協(xié)議就是雙方的約定刻伊,就是規(guī)矩露戒,就是格式椒功。下面,我們來看看一個實際遠程調用過程的數(shù)據(jù)穿透協(xié)議智什。
上圖實際執(zhí)行了系統(tǒng)服務MediaPlayer的setVolume(float,float)函數(shù)調用過程动漾,而執(zhí)行的實體實際上是遠端的服務進程。
數(shù)據(jù)write_buffer實際上是通過copy_from_user傳遞到內核空間荠锭。傳遞的內容旱眯,在上圖中一目了然,主要包括類(android.media.IMediaPlayer)证九,方法(通過數(shù)字26映射)删豺,參數(shù)(1.0,1.0)。以上愧怜,就是函數(shù)調用從用戶空間被傳遞到內核空間呀页,通過Binder驅動發(fā)送到實際執(zhí)行的遠程服務進程完成執(zhí)行操作。
Binder通信機制
上面拥坛,我們該對整個通信過程有了一個大概的印象蓬蝶,應該已經(jīng)清楚,數(shù)據(jù)從哪里來渴逻,如何穿透進程壁壘以及穿透的基礎疾党。接下來,我們先來通過下圖澄清幾個概念惨奕。
- Binder驅動:一個內核層級的驅動雪位,促成了進程間通信。有人把它比作網(wǎng)絡通信中的路由器梨撞,而路由器的作用雹洗,就是從原地址存儲轉發(fā)數(shù)據(jù)目的地址,很貼切卧波。
- Binder對象:是IBinder接口的實現(xiàn)时肿。實際上就是遠程服務動作的實際執(zhí)行者。
- BinderToken:也有叫 handler 的港粱。實際上就是一個32位的整型值螃成,唯一的代表了一個遠端Binder對象。
- Binder Service:實際持有Binder對象查坪,處理業(yè)務邏輯寸宏。
- Binder Client:請求Binder服務者。
- Proxy:實現(xiàn)了AIDL接口偿曙,能夠序列化/反序列化數(shù)據(jù)結構氮凝,同時能夠通過Binder引用進程遠程調用。
- Stub:能夠序列化和反序列化數(shù)據(jù)望忆,并把轉換過程映射到實際的服務端的方法調用罩阵。
- Context Manager:一個注冊 handler(句柄)為0的特殊Binder對象竿秆。通過name到handler的映射關系,注冊和查找別的Binder對象稿壁。也有人把它對應為DNS服務器幽钢。DNS服務器的作用,就是通過IP地質到域名的映射常摧,提供查找和注冊搅吁。即我們向DNS服務器注冊自己的IP,并把它映射到一個域名落午;這樣谎懦,別人就可以通過域名來訪問我們的主機,因為DNS服務器將把這個域名轉換為IP溃斋。
把上圖再細致一點界拦,放大,如下圖所示:
Client:
- 首先梗劫,客戶端從ContextManager中通過服務名享甸,拿到遠程服務的handler(句柄),也就是binder token梳侨。
- 調用遠程服務的 foo 方法蛉威,然后序列化參數(shù);
- 通過 transact 把調用相關的資料提交給 libbinder處理走哺;
- 由 libbinder 通過ioctl進程系統(tǒng)調用蚯嫌,將foo調用請求提交給binder驅動;
- binder驅動通過handler找到真正的遠端服務進程丙躏,然后通過ioctl函數(shù)將foo調用傳遞給遠端服務進程的 libbinder择示;
- 遠端libbinder將調用交給Stub;
- 在Stub中晒旅,反序列化調用信息栅盲,還原;
- Stub找到實際服務提供者废恋,執(zhí)行客戶端的請求谈秫;
- 將結果序列化
......
再通過Binder驅動,返回給客戶端鱼鼓。
Server:
主要是在ContextManager中完成注冊(name -> handler)孝常;等待接收Binder驅動發(fā)來的請求。
Binder限制
在使用Binder進程跨進程調用的時候蚓哩,有兩個重要的限制需要注意:
- 一個服務進程中最多同時支持15個binder線程處理請求;
- 一個進程中用戶交互穿透數(shù)據(jù)的緩存大小最多為1M上渴,這就意味著岸梨,在傳遞的數(shù)據(jù)是有限的喜颁,如果資源耗盡,會拋出異常曹阔。
最后
作為一個應用開發(fā)者半开,對本質對底層的C實現(xiàn)機制,有些技癢赃份,但是久未使用過C寂拆,想從源碼的角度深入分析,需要花費更多精力抓韩,目前似乎又沒有這個必要纠永,那么,就先淺嘗輒止谒拴,不求甚解尝江。如有不對的地方,還請指出英上。
收獲炭序,就在于對序列化、反序列化的認識苍日;對數(shù)據(jù)結構化的認識惭聂;從Binder框架的認識;以后再讀源碼的時候相恃,不會被各種服務調用搞的暈頭轉向辜纲;再有就是對AIDL的使用上,不用再死記硬背如何通過AIDL進程跨進程調用了豆茫。
后面侨歉,會寫一下AIDL跨進程調用過程。
本文參考鏈接:
https://blog.checkpoint.com/wp-content/uploads/2015/02/Man-In-The-Binder-He-Who-Controls-IPC-Controls-The-Droid-wp.pdf
https://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf
https://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/android-binder-ipc.pdf
想要深入源碼和數(shù)據(jù)結構的同學揩魂,可以參考以下鏈接:
http://gityuan.com/2015/11/01/binder-driver/
http://www.cloudchou.com/android/post-507.html
http://www.reibang.com/p/1050ce12bc1e
http://blog.csdn.net/universus/article/details/6211589#comments
https://github.com/xdtianyu/SourceAnalysis/blob/master/Binder%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md
或者幽邓,自己去找binder.c 和 binder.h 這兩個文件來看吧。