1. 程式人生 > >Binder通訊機制總結

Binder通訊機制總結

IBinder是android中IPC(inner-process communication)通訊的基礎,也是一個很難理解的概念,由於能力有限,本文只是在大牛的基礎上從巨集觀上大體理清一下思路。
Android底層是基於Linux,Linux中傳統IPC方式包括:傳統的管道(Pipe)、訊號(Signal)和跟蹤(Trace),這三項通訊手段只能用於父程序與子程序之間,或者兄弟程序之間;後來又增加了命令管道(Named Pipe),使得程序間通訊不再侷限於父子程序或者兄弟程序之間;為了更好地支援商業應用中的事務處理,在AT&T的Unix系統V中,又增加了三種稱為“System V IPC

”的程序間通訊機制,分別是報文佇列(Message)、共享記憶體(Share Memory)和訊號量(Semaphore);後來BSD Unix對“System V IPC”機制進行了重要的擴充,提供了一種稱為插口(Socket)的程序間通訊機制。
傳統IPC的缺點
1、傳輸效能。socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程序間通訊和本機上程序間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。
2、安全考慮。Android作為一個開放式,擁有眾多開發者的的平臺,應用程式的來源廣泛,確保智慧終端的安全是非常重要的。終端使用者不希望從網上下載的程式在不知情的情況下偷窺隱私資料,連線無線網路,長期操作底層裝置導致電池很快耗盡等等。傳統IPC沒有任何安全措施,完全依賴上層協議來確保。首先傳統IPC的接收方無法獲得對方程序可靠的UID/PID(使用者ID/程序ID),從而無法鑑別對方身份。Android為每個安裝好的應用程式分配了自己的UID,故程序的UID是鑑別程序身份的重要標誌。使用傳統IPC只能由使用者在資料包裡填入UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份標記只有由IPC機制本身在核心中新增。其次傳統IPC訪問接入點是開放的,無法建立私有通道。比如命名管道的名稱,system V的鍵值,socket的ip地址或檔名都是開放的,只要知道這些接入點的程式都可以和對端建立連線,不管怎樣都無法阻止惡意程式通過猜測接收方地址獲得連線。
基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通訊方式,傳輸效能和安全性的要求,這就是Binder。Binder基於Client-Server通訊模式
,傳輸過程只需一次拷貝,為傳送方新增UID/PID身份,既支援實名Binder也支援匿名Binder,安全性高。
Binder使用了面向物件的思想來描述作為訪問接入點的Binder及其在Client中的入口:Binder是一個實體位於Server中的物件,該物件提供了一套方法用以實現對服務的請求,就象類的成員函式。遍佈於client中的入口可以看成指向這個binder物件的‘指標’,一旦獲得了這個‘指標’就可以呼叫該物件的方法訪問server。在Client看來,通過Binder‘指標’呼叫其提供的方法和通過指標呼叫其它任何本地物件的方法並無區別,儘管前者的實體位於遠端Server中,而後者實體位於本地記憶體中。Binder模糊了程序邊界,淡化了程序間通訊過程,整個系統彷彿運行於同一個面向物件的程式之中。
Binder 通訊模型

Binder框架定義了四個角色:Server,Client,ServiceManager(以後簡稱SMgr)以及Binder驅動。其中Server,Client,SMgr運行於使用者空間,驅動運行於核心空間。這四個角色的關係和網際網路類似:Server是伺服器,Client是客戶終端,SMgr是域名伺服器(DNS),驅動是路由器通訊模型
server中有Binder物件,通過該物件方法可以實現client端各種服務請求,所以通訊的關鍵就是讓client如何拿到server端的Binder地引用。
(1)Binder驅動:
和路由器一樣,Binder驅動雖然默默無聞,卻是通訊的核心。儘管名叫‘驅動’,實際上和硬體裝置沒有任何關係,只是實現方式和裝置驅動程式是一樣的:它工作於核心態,提供open(),mmap(),poll(),ioctl()等標準檔案操作,以字元驅動裝置中的misc設備註冊在裝置目錄/dev下,使用者通過/dev/binder訪問該它。驅動負責程序之間Binder通訊的建立,Binder在程序之間的傳遞,Binder引用計數管理,資料包在程序之間的傳遞和互動等一系列底層支援。驅動和應用程式之間定義了一套介面協議,主要功能由ioctl()介面實現,不提供read(),write()介面,因為ioctl()靈活方便,且能夠一次呼叫實現先寫後讀以滿足同步互動,而不必分別呼叫write()和read()。Binder驅動的程式碼位於linux目錄的drivers/misc/binder.c中。
(2)ServiceManager中實名Binder的註冊
SMgr的作用是將字元形式的Binder名字轉化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。註冊了名字的Binder叫實名Binder,就象每個網站除了有IP地址外還有自己的網址。Server建立了Binder實體,為其取一個字元形式,可讀易記的名字,將這個Binder連同名字以資料包的形式通過Binder驅動傳送給SMgr,通知SMgr註冊一個名叫張三的Binder,它位於某個Server中。驅動為這個穿過程序邊界的Binder建立位於核心中的實體節點以及SMgr對實體的引用,將名字及新建的引用打包傳遞給SMgr。SMgr收資料包後,從中取出名字和引用填入一張查詢表中。Client想獲得某個Server中Binder的引用,需要從SMgr查詢表中查詢後獲得。
這當中Server在SMgr中的註冊也涉及到程序間的通訊,SMgr是一個程序,Server是另一個程序,Server向SMgr註冊Binder必然會涉及程序間通訊。當前實現的是程序間通訊卻又要用到程序間通訊,這就好像蛋可以孵出雞前提卻是要找只雞來孵蛋。Binder的實現比較巧妙:預先創造一隻雞來孵蛋:SMgr和其它程序同樣採用Binder通訊,SMgr是Server端,有自己的Binder物件(實體),其它程序都是Client,需要通過這個Binder的引用來實現Binder的註冊,查詢和獲取。SMgr提供的Binder比較特殊,它沒有名字也不需要註冊,當一個程序使用BINDER_SET_CONTEXT_MGR命令將自己註冊成SMgr時Binder驅動會自動為它建立Binder實體(這就是那隻預先造好的雞)。其次這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向SMgr註冊自己Binder就必需通過0這個引用號和SMgr的Binder通訊。類比網路通訊,0號引用就好比域名伺服器的地址,你必須預先手工或動態配置好。要注意這裡說的Client是相對SMgr而言的,一個應用程式可能是個提供服務的Server,但對SMgr來說它仍然是個Client。
(3)Client端Binder的引用
Server向SMgr註冊了Binder實體及其名字後,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向SMgr請求訪問某個Binder,SMgr收到這個連線請求,從請求資料包裡獲得Binder的名字,在查詢表裡找到該名字對應的條目,從條目中取出Binder的引用,將該引用作為回覆傳送給發起請求的Client。從面向物件的角度,這個Binder物件現在有了兩個引用:一個位於SMgr中,一個位於發起請求的Client中。如果接下來有更多的Client請求該Binder,系統中就會有更多的引用指向該Binder,就象java裡一個物件存在多個引用一樣。而且類似的這些指向Binder的引用是強型別,從而確保只要有引用Binder實體就不會被釋放掉。
(4) 匿名 Binder
並不是所有Binder都需要註冊給SMgr廣而告之的。Server端可以通過已經建立的Binder連線將建立的Binder實體傳給Client,當然這條已經建立的Binder連線必須是通過實名Binder實現。由於這個Binder沒有向SMgr註冊名字,所以是個匿名Binder。Client將會收到這個匿名Binder的引用,通過這個引用向位於Server中的實體傳送請求。匿名Binder為通訊雙方建立一條私密通道,只要Server沒有把匿名Binder發給別的程序,別的程序就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder傳送請求。
Binder 在應用程式中的表述
Binder本質上只是一種底層通訊方式,和具體服務沒有關係。為了提供具體服務,Server必須提供一套介面函式以便Client通過遠端訪問使用各種服務。這時通常採用Proxy設計模式:將介面函式定義在一個抽象類中,Server和Client都會以該抽象類為基類實現所有介面函式,所不同的是Server端是真正的功能實現,而Client端是對這些函式遠端呼叫請求的包裝。如何將Binder和Proxy設計模式結合起來是應用程式實現面向物件Binder通訊的根本問題。
(1) Binder 在Server端的表述 – Binder實體
做為Proxy設計模式的基礎,首先定義一個抽象介面類封裝Server所有功能,其中包含一系列純虛擬函式留待Server和Proxy各自實現。由於這些函式需要跨程序呼叫,須為其一一編號,從而Server可以根據收到的編號決定呼叫哪個函式。其次就要引入Binder了。Server端定義另一個Binder抽象類處理來自Client的Binder請求資料包,其中最重要的成員是虛擬函式transact()。該函式分析收到的資料包,呼叫相應的介面函式處理請求。
接下來採用繼承方式以介面類和Binder抽象類為基類構建Binder在Server中的實體,實現基類裡所有的虛擬函式,包括公共介面函式以及資料包處理函式:transact()。這個函式的輸入是來自Client的binder_transaction_data結構的資料包。前面提到,該結構裡有個成員code,包含這次請求的介面函式編號。onTransact()將case-by-case地解析code值,從資料包裡取出函式引數,呼叫介面類中相應的,已經實現的公共介面函式。函式執行完畢,如果需要返回資料就再構建一個binder_transaction_data包將返回資料包填入其中。
那麼各個Binder實體的transact()又是什麼時候呼叫呢?這就需要驅動參與了。前面說過,Binder實體須要以Binde傳輸結構flat_binder_object形式傳送給其它程序才能建立Binder通訊,而Binder實體指標就存放在該結構的handle域中。驅動根據Binder位置陣列從傳輸資料中獲取該Binder的傳輸結構,為它建立位於核心中的Binder節點,將Binder實體指標記錄在該節點中。如果接下來有其它程序向該Binder傳送資料,驅動會根據節點中記錄的資訊將Binder實體指標填入binder_transaction_data的target.ptr中返回給接收執行緒。接收執行緒從資料包中取出該指標,reinterpret_cast成Binder抽象類並呼叫onTransact()函式。由於這是個虛擬函式,不同的Binder實體中有各自的實現,從而可以呼叫到不同Binder實體提供的transact()。
(2) Binder 在Client端的表述 – Binder引用
做為Proxy設計模式的一部分,Client端的Binder同樣要繼承Server提供的公共介面類並實現公共函式。但這不是真正的實現,而是對遠端函式呼叫的包裝:將函式引數打包,通過Binder向Server傳送申請並等待返回值。為此Client端的Binder還要知道Binder實體的相關資訊,即對Binder實體的引用。該引用或是由SMgr轉發過來的,對實名Binder的引用或是由另一個程序直接傳送過來的,對匿名Binder的引用。
由於繼承了同樣的公共介面類,Client Binder提供了與Server Binder一樣的函式原型,使使用者感覺不出Server是執行在本地還是遠端。Client Binder中,公共介面函式的包裝方式是:建立一個binder_transaction_data資料包,將其對應的編碼填入code域,將呼叫該函式所需的引數填入data.buffer指向的快取中,並指明資料包的目的地,那就是已經獲得的對Binder實體的引用,填入資料包的target.handle中。注意這裡和Server的區別:實際上target域是個聯合體,包括ptr和handle兩個成員,前者用於接收資料包的Server,指向 Binder實體對應的記憶體空間;後者用於作為請求方的Client,存放Binder實體的引用,告知驅動資料包將路由給哪個實體。資料包準備好後,通過驅動介面傳送出去。經過BC_TRANSACTION/BC_REPLY回合完成函式的遠端呼叫並得到返回值。