安卓進程通信之Binder學習指南(一)

毫不夸張地說,Binder是Android系統(tǒng)中最重要的特性之一;正如其名“粘合劑”所喻,它是系統(tǒng)間各個組件的橋梁,Android系統(tǒng)的開放式設計也很大程度上得益于這種及其方便的跨進程通信機制。

理解Binder對于理解整個Android系統(tǒng)有著非常重要的作用,Android系統(tǒng)的四大組件,AMS,PMS等系統(tǒng)服務無一不與Binder掛鉤;如果對Binder不甚了解,那么就很難了解這些系統(tǒng)機制,從而僅僅浮游與表面,不懂Binder你都不好意思說自己會Android開發(fā);要深入Android,Binder是必須邁出的一步。

現(xiàn)在網(wǎng)上有不少資料介紹Binder,個人覺得最好的兩篇如下:

Binder設計與實現(xiàn)

Android進程間通信(IPC)機制Binder簡要介紹和學習計劃系列

其中, 《Binder設計與實現(xiàn)》以一種宏觀的角度解釋了Android系統(tǒng)中的Binder機制,文章如行云流水;如果對于Binder有一定的了解再來看著篇文章,有一種打通任督二脈的感覺;每看一次理解就深一層。老羅的系列文章則從系統(tǒng)源碼角度深入分析了Binder的實現(xiàn)細節(jié);具有很大的參考意義;每當對于Binder細節(jié)有疑惑,看一看他的書就迎刃而解。

但是遺憾的是,Binder機制終究不是三言兩語就能解釋清楚的,一上來就扒出源碼很可能深陷細節(jié)無法自拔,老羅的文章那不是一般的長,如果看不懂強行看很容易睡著;勉強看完還是云里霧里;相反如果直接大談特談Binder的設計,那么完全就是不知所云;因此上述兩篇文章對于初學者并不友好,本文不會深入源碼細節(jié),也不會對于Binder的設計高談闊論;重點如下:

一些Linux的預備知識

Binder到底是什么?

Binder機制是如何跨進程的?

一次Binder通信的基本流程是什么樣?

深入理解Java層的Binder

讀完本文,你應該對于Java層的AIDL了如指掌,對于Binder也會有一個大體上的認識;再深入學習就得靠自己了,本人推薦的Binder學習路徑如下:

先學會熟練使用AIDL進行跨進程通信(簡單來說就是遠程Service)

看完本文

看Android文檔,Parcel, IBinder, Binder等涉及到跨進程通信的類

不依賴AIDL工具,手寫遠程Service完成跨進程通信

看《Binder設計與實現(xiàn)》

看老羅的博客或者書(書結構更清晰)

再看《Binder設計與實現(xiàn)》

學習Linux系統(tǒng)相關知識;自己看源碼。

背景知識

為了理解Binder我們先澄清一些概念。為什么需要跨進程通信(IPC),怎么做到跨進程通信?為什么是Binder?

由于Android系統(tǒng)基于Linux內(nèi)核,因此有必要了解相關知識。

進程隔離

進程隔離是為保護操作系統(tǒng)中進程互不干擾而設計的一組不同硬件和軟件的技術。這個技術是為了避免進程A寫入進程B的情況發(fā)生。 進程的隔離實現(xiàn),使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數(shù)據(jù)信息寫入進程B。

以上來自維基百科;操作系統(tǒng)的不同進程之間,數(shù)據(jù)不共享;對于每個進程來說,它都天真地以為自己獨享了整個系統(tǒng),完全不知道其他進程的存在;(有關虛擬地址,請自行查閱)因此一個進程需要與另外一個進程通信,需要某種系統(tǒng)機制才能完成。

用戶空間/內(nèi)核空間

詳細解釋可以參考Kernel Space Definition;簡單理解如下:

Linux Kernel是操作系統(tǒng)的核心,獨立于普通的應用程序,可以訪問受保護的內(nèi)存空間,也有訪問底層硬件設備的所有權限。

對于Kernel這么一個高安全級別的東西,顯然是不容許其它的應用程序隨便調(diào)用或訪問的,所以需要對Kernel提供一定的保護機制,這個保護機制用來告訴那些應用程序,你只可以訪問某些許可的資源,不許可的資源是拒絕被訪問的,于是就把Kernel和上層的應用程序抽像的隔離開,分別稱之為Kernel Space和User Space。

系統(tǒng)調(diào)用/內(nèi)核態(tài)/用戶態(tài)

雖然從邏輯上抽離出用戶空間和內(nèi)核空間;但是不可避免的的是,總有那么一些用戶空間需要訪問內(nèi)核的資源;比如應用程序訪問文件,網(wǎng)絡是很常見的事情,怎么辦呢?

Kernel space can be accessed by user processes only through the use of system calls.

用戶空間訪問內(nèi)核空間的唯一方式就是系統(tǒng)調(diào)用;通過這個統(tǒng)一入口接口,所有的資源訪問都是在內(nèi)核的控制下執(zhí)行,以免導致對用戶程序對系統(tǒng)資源的越權訪問,從而保障了系統(tǒng)的安全和穩(wěn)定。用戶軟件良莠不齊,要是它們亂搞把系統(tǒng)玩壞了怎么辦?因此對于某些特權操作必須交給安全可靠的內(nèi)核來執(zhí)行。

當一個任務(進程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時,我們就稱進程處于內(nèi)核運行態(tài)(或簡稱為內(nèi)核態(tài))此時處理器處于特權級最高的(0級)內(nèi)核代碼中執(zhí)行。當進程在執(zhí)行用戶自己的代碼時,則稱其處于用戶運行態(tài)(用戶態(tài))。即此時處理器在特權級最低的(3級)用戶代碼中運行。處理器在特權等級高的時候才能執(zhí)行那些特權CPU指令。

內(nèi)核模塊/驅動

通過系統(tǒng)調(diào)用,用戶空間可以訪問內(nèi)核空間,那么如果一個用戶空間想與另外一個用戶空間進行通信怎么辦呢?很自然想到的是讓操作系統(tǒng)內(nèi)核添加支持;傳統(tǒng)的Linux通信機制,比如Socket,管道等都是內(nèi)核支持的;但是Binder并不是Linux內(nèi)核的一部分,它是怎么做到訪問內(nèi)核空間的呢?Linux的動態(tài)可加載內(nèi)核模塊(Loadable Kernel Module,LKM)機制解決了這個問題;模塊是具有獨立功能的程序,它可以被單獨編譯,但不能獨立運行。它在運行時被鏈接到內(nèi)核作為內(nèi)核的一部分在內(nèi)核空間運行。這樣,Android系統(tǒng)可以通過添加一個內(nèi)核模塊運行在內(nèi)核空間,用戶進程之間的通過這個模塊作為橋梁,就可以完成通信了。

在Android系統(tǒng)中,這個運行在內(nèi)核空間的,負責各個用戶進程通過Binder通信的內(nèi)核模塊叫做Binder驅動;

驅動程序一般指的是設備驅動程序(Device Driver),是一種可以使計算機和設備通信的特殊程序。相當于硬件的接口,操作系統(tǒng)只有通過這個接口,才能控制硬件設備的工作;

驅動就是操作硬件的接口,為了支持Binder通信過程,Binder使用了一種“硬件”,因此這個模塊被稱之為驅動。


為什么使用Binder?

Android使用的Linux內(nèi)核擁有著非常多的跨進程通信機制,比如管道,System V,Socket等;為什么還需要單獨搞一個Binder出來呢?主要有兩點,性能和安全。在移動設備上,廣泛地使用跨進程通信肯定對通信機制本身提出了嚴格的要求;Binder相對出傳統(tǒng)的Socket方式,更加高效;另外,傳統(tǒng)的進程通信方式對于通信雙方的身份并沒有做出嚴格的驗證,只有在上層協(xié)議上進行架設;比如Socket通信ip地址是客戶端手動填入的,都可以進行偽造;而Binder機制從協(xié)議本身就支持對通信雙方做身份校檢,因而大大提升了安全性。這個也是Android權限模型的基礎。

Binder通信模型

對于跨進程通信的雙方,我們姑且叫做Server進程(簡稱Server),Client進程(簡稱Client);由于進程隔離的存在,它們之間沒辦法通過簡單的方式進行通信,那么Binder機制是如何進行的呢?

回想一下日常生活中我們通信的過程:假設A和B要進行通信,通信的媒介是打電話(A是Client,B是Server);A要給B打電話,必須知道B的號碼,這個號碼怎么獲取呢?通信錄.

這個通信錄就是一張表;內(nèi)容大致是:

1??B -> 12345676

2??C -> 12334354

先查閱通信錄,拿到B的號碼;才能進行通信;否則,怎么知道應該撥什么號碼?回想一下古老的電話機,如果A要給B打電話,必須先連接通話中心,說明給我接通B的電話;這時候通話中心幫他呼叫B;連接建立,就完成了通信。

另外,光有電話和通信錄是不可能完成通信的,沒有基站支持;信息根本無法傳達。

我們看到,一次電話通信的過程除了通信的雙方還有兩個隱藏角色:通信錄和基站。Binder通信機制也是一樣:兩個運行在用戶空間的進程要完成通信,必須借助內(nèi)核的幫助,這個運行在內(nèi)核里面的程序叫做Binder驅動,它的功能類似于基站;通信錄呢,就是一個叫做ServiceManager的東西(簡稱SM)

OK,Binder的通信模型就是這么簡單,如下圖:

整個通信步驟如下:

SM建立(建立通信錄);首先有一個進程向驅動提出申請為SM;驅動同意之后,SM進程負責管理Service(注意這里是Service而不是Server,因為如果通信過程反過來的話,那么原來的客戶端Client也會成為服務端Server)不過這時候通信錄還是空的,一個號碼都沒有。

各個Server向SM注冊(完善通信錄);每個Server端進程啟動之后,向SM報告,我是zhangsan, 要找我請返回0x1234(這個地址沒有實際意義,類比);其他Server進程依次如此;這樣SM就建立了一張表,對應著各個Server的名字和地址;就好比B與A見面了,說存?zhèn)€我的號碼吧,以后找我撥打10086;

Client想要與Server通信,首先詢問SM;請告訴我如何聯(lián)系zhangsan,SM收到后給他一個號碼0x1234;Client收到之后,開心滴用這個號碼撥通了Server的電話,于是就開始通信了。

那么Binder驅動干什么去了呢?這里Client與SM的通信,以及Client與Server的通信,都會經(jīng)過驅動,驅動在背后默默無聞,但是做著最重要的工作。驅動是整個通信過程的核心,因此完成跨進程通信的秘密全部隱藏在驅動里面;這個我們稍后討論。

OK,上面就是整個Binder通信的基本模型;做了一個簡單的類比,當然也有一些不恰當?shù)牡胤剑?比如通信錄現(xiàn)實中每個人都有一個,但是SM整個系統(tǒng)只有一個;基站也有很多個,但是驅動只有一個);但是整體上就是這樣的;我們看到其實整個通信模型非常簡單。

Binder機制跨進程原理

上文給出了Binder的通信模型,指出了通信過程的四個角色: Client, Server, SM, driver; 但是我們?nèi)匀徊磺宄?b>Client到底是如何與Server完成通信的。

兩個運行在用戶空間的進程A和進程B如何完成通信呢?內(nèi)核可以訪問A和B的所有數(shù)據(jù);所以,最簡單的方式是通過內(nèi)核做中轉;假設進程A要給進程B發(fā)送數(shù)據(jù),那么就先把A的數(shù)據(jù)copy到內(nèi)核空間,然后把內(nèi)核空間對應的數(shù)據(jù)copy到B就完成了;用戶空間要操作內(nèi)核空間,需要通過系統(tǒng)調(diào)用;剛好,這里就有兩個系統(tǒng)調(diào)用:copy_from_user,?copy_to_user。

但是,Binder機制并不是這么干的。講這么一段,是說明進程間通信并不是什么神秘的東西。那么,Binder機制是如何實現(xiàn)跨進程通信的呢?

Binder驅動為我們做了一切。

假設Client進程想要調(diào)用Server進程的object對象的一個方法add;對于這個跨進程通信過程,我們來看看Binder機制是如何做的。 (通信是一個廣泛的概念,只要一個進程能調(diào)用另外一個進程里面某對象的方法,那么具體要完成什么通信內(nèi)容就很容易了。)

Alt text

首先,Server進程要向SM注冊;告訴自己是誰,自己有什么能力;在這個場景就是Server告訴SM,它叫zhangsan,它有一個object對象,可以執(zhí)行add?操作;于是SM建立了一張表:zhangsan這個名字對應進程Server;

然后Client向SM查詢:我需要聯(lián)系一個名字叫做zhangsan的進程里面的object對象;這時候關鍵來了:進程之間通信的數(shù)據(jù)都會經(jīng)過運行在內(nèi)核空間里面的驅動,驅動在數(shù)據(jù)流過的時候做了一點手腳,它并不會給Client進程返回一個真正的object對象,而是返回一個看起來跟object一模一樣的代理對象objectProxy,這個objectProxy也有一個add方法,但是這個add方法沒有Server進程里面object對象的add方法那個能力;objectProxy的add只是一個傀儡,它唯一做的事情就是把參數(shù)包裝然后交給驅動。(這里我們簡化了SM的流程,見下文)

但是Client進程并不知道驅動返回給它的對象動過手腳,畢竟偽裝的太像了,如假包換。Client開開心心地拿著objectProxy對象然后調(diào)用add方法;我們說過,這個add什么也不做,直接把參數(shù)做一些包裝然后直接轉發(fā)給Binder驅動。

驅動收到這個消息,發(fā)現(xiàn)是這個objectProxy;一查表就明白了:我之前用objectProxy替換了object發(fā)送給Client了,它真正應該要訪問的是object對象的add方法;于是Binder驅動通知Server進程,調(diào)用你的object對象的add方法,然后把結果發(fā)給我,Sever進程收到這個消息,照做之后將結果返回驅動,驅動然后把結果返回給Client進程;于是整個過程就完成了。

由于驅動返回的objectProxy與Server進程里面原始的object是如此相似,給人感覺好像是直接把Server進程里面的對象object傳遞到了Client進程;因此,我們可以說Binder對象是可以進行跨進程傳遞的對象

但事實上我們知道,Binder跨進程傳輸并不是真的把一個對象傳輸?shù)搅肆硗庖粋€進程;傳輸過程好像是Binder跨進程穿越的時候,它在一個進程留下了一個真身,在另外一個進程幻化出一個影子(這個影子可以很多個);Client進程的操作其實是對于影子的操作,影子利用Binder驅動最終讓真身完成操作。

理解這一點非常重要;務必仔細體會。另外,Android系統(tǒng)實現(xiàn)這種機制使用的是代理模式, 對于Binder的訪問,如果是在同一個進程(不需要跨進程),那么直接返回原始的Binder實體;如果在不同進程,那么就給他一個代理對象(影子);我們在系統(tǒng)源碼以及AIDL的生成代碼里面可以看到很多這種實現(xiàn)。

另外我們?yōu)榱撕喕麄€流程,隱藏了SM這一部分驅動進行的操作;實際上,由于SM與Server通常不在一個進程,Server進程向SM注冊的過程也是跨進程通信,驅動也會對這個過程進行暗箱操作:SM中存在的Server端的對象實際上也是代理對象,后面Client向SM查詢的時候,驅動會給Client返回另外一個代理對象。Sever進程的本地對象僅有一個,其他進程所擁有的全部都是它的代理。

一句話總結就是:Client進程只不過是持有了Server端的代理;代理對象協(xié)助驅動完成了跨進程通信。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容