Binder系統程序間通訊(IPC)方式之一。Linux已經擁有管道,system V IPCsocketIPC手段,卻還要倚賴Binder來實現程序間通訊,說明Binder具有無可比擬的優勢。深入瞭解Binder並將之與傳統 IPC做對比有助於我們深入領會程序間通訊的實現和效能優化。本文將對Binder的設計細節做一個全面的闡述,首先通過介紹Binder通訊模型和 Binder通訊協議瞭解Binder的設計需求;然後分別闡述Binder在系統不同部分的表述方式和起的作用;最後還會解釋Binder在資料接收端的設計考慮,包括執行緒池管理,記憶體對映和等待佇列管理等。通過本文對Binder的詳細介紹以及與其它IPC通訊方式的對比,讀者將對Binder的優勢和使用Binder作為Android主要IPC方式的原因有深入瞭解。應用程式雖然是以獨立的程序來執行,但相互之間還是需要相互協作,彼此間又必須進行通訊和資料共享,這就需要程序通訊來完成。
Binder提供了以下功能:
1.用驅動程式來實現程序間通訊;
2.通過共享記憶體來提供效能;
3.為程序請求分配執行緒池;
4.為物件引入了引用計數和跨程序物件引用對映;
5.程序間同步呼叫;
Binder操作類似於執行緒遷移,兩個程序間通訊看起來就像是一個程序進入另一個程序去執行程式碼,然後帶著執行結果返回。Binder在使用者空間為每一個程序維護著一個可用的執行緒池,用於處理到來的IPC以及執行程序的本地訊息,Binder通訊時同步的而不是非同步的。

引言

基於Client-Server的通訊方式廣泛應用於從網際網路和資料庫訪問到嵌入式手持裝置內部通訊等各個領域。智慧手機平臺特別是Android 系統中,為了嚮應用開發者提供豐富多樣的功能,這種通訊方式更是無處不在,諸如媒體播放,視音訊頻捕獲,到各種讓手機更智慧的感測器(加速度,方位,溫度,光亮度等)都由不同的Server負責管理,應用程式只需做為Client與這些Server建立連線便可以使用這些服務,花很少的時間和精力就能開發出令人眩目的功能。Client-Server方式的廣泛採用對程序間通訊(IPC)機制是一個挑戰。目前linux支援的IPC包括傳統的管道,System V IPC,即訊息佇列/共享記憶體/訊號量,以及socket中只有socket支援Client-Server的通訊方式。當然也可以在這些底層機制上架設一套協議來實現Client-Server通訊,但這樣增加了系統的複雜性,在手機這種條件複雜,資源稀缺的環境下可靠性也難以保證。

另一方面是傳輸效能。socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程序間通訊和本機上程序間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。

表 各種IPC方式資料拷貝次數

共享記憶體 0
Binder 1
Socket/管道/訊息佇列 2

還有一點是出於安全性考慮。Android作為一個開放式,擁有眾多開發者的的平臺,應用程式的來源廣泛,確保智慧終端的安全是非常重要的。終端使用者不希望從網上下載的程式在不知情的情況下偷窺隱私資料,連線無線網路,長期操作底層裝置導致電池很快耗盡等等。傳統IPC沒有任何安全措施,完全依賴上層協議來確保。首先傳統IPC的接收方無法獲得對方程序可靠的UID/PID(使用者ID/程序ID),從而無法鑑別對方身份。Android為每個安裝好的應用程式分配了自己的UID,故程序的UID是鑑別程序身份的重要標誌。使用傳統IPC只能由使用者在資料包裡填入UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份標記只有由IPC機制本身在核心中新增。其次傳統IPC訪問接入點是開放的,無法建立私有通道。比如命名管道的名稱,system V的鍵值,socketip地址或檔名都是開放的,只要知道這些接入點的程式都可以和對端建立連線,不管怎樣都無法阻止惡意程式通過猜測接收方地址獲得連線。

基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通訊方式,傳輸效能和安全性的要求,這就是BinderBinder基於 Client-Server通訊模式,傳輸過程只需一次拷貝,為傳送發新增UID/PID身份,既支援實名Binder也支援匿名Binder,安全性高。

面向物件的 Binder IPC

Binder使用Client-Server通訊方式:一個程序作為Server提供諸如視訊/音訊解碼,視訊捕獲,地址本查詢,網路連線等服務;多個程序作為Client向Server發起服務請求,獲得所需要的服務。要想實現Client-Server通信據必須實現以下兩點:一是server 必須有確定的訪問接入點或者說地址來接受Client的請求,並且Client可以通過某種途徑獲知Server的地址;二是制定Command- Reply協議來傳輸資料。例如在網路通訊中Server的訪問接入點就是Server主機的IP地址+埠號,傳輸協議為TCP協議。對Binder而言,Binder可以看成Server提供的實現某個特定服務的訪問接入點, Client通過這個‘地址’向Server傳送請求來使用該服務;對Client而言,Binder可以看成是通向Server的管道入口,要想和某個 Server通訊首先必須建立這個管道並獲得管道入口。

與其它IPC不同,Binder使用了面向物件的思想來描述作為訪問接入點的Binder及其在Client中的入口:Binder是一個實體位於 Server中的物件,該物件提供了一套方法用以實現對服務的請求,就象類的成員函式。遍佈於client中的入口可以看成指向這個binder物件的 ‘指標’,一旦獲得了這個‘指標’就可以呼叫該物件的方法訪問server。在Client看來,通過Binder‘指標’呼叫其提供的方法和通過指標呼叫其它任何本地物件的方法並無區別,儘管前者的實體位於遠端Server中,而後者實體位於本地記憶體中。‘指標’是C++的術語,而更通常的說法是引用,即Client通過Binder的引用訪問Server。而軟體領域另一個術語‘控制代碼’也可以用來表述Binder在Client中的存在方式。從通訊的角度看,Client中的Binder也可以看作是Server Binder的‘代理’,在本地代表遠端Server為Client提供服務。本文中會使用‘引用’或‘控制代碼’這個兩廣泛使用的術語。

面向物件思想的引入將程序間通訊轉化為通過對某個Binder物件的引用呼叫該物件的方法,而其獨特之處在於Binder物件是一個可以跨程序引用的物件,它的實體位於一個程序中,而它的引用卻遍佈於系統的各個程序之中。最誘人的是,這個引用和Java裡引用一樣既可以是強型別,也可以是弱型別,而且可以從一個程序傳給其它程序,讓大家都能訪問同一Server,就象將一個物件或引用賦值給另一個引用一樣。Binder模糊了程序邊界,淡化了程序間通訊過程,整個系統彷彿運行於同一個面向物件的程式之中。形形色色的Binder物件以及星羅棋佈的引用彷彿粘接各個應用程式的膠水,這也是Binder 在英文裡的原意。當然面向物件只是針對應用程式而言,對於Binder驅動和核心其它模組一樣使用C語言實現,沒有類和物件的概念。Binder驅動為面向物件的程序間通訊提供底層支援。

Binder 通訊模型

Binder框架定義了四個角色:ServerClientServiceManager(以後簡稱SMgr)以及驅動。其中 ServerClientSMgr運行於使用者空間,驅動運行於核心空間。這四個角色的關係和網際網路類似:Server是伺服器,Client是客戶終端,SMgr是域名伺服器(DNS),驅動是路由器。

Binder 驅動

和路由器一樣,Binder驅動雖然默默無聞,卻是通訊的核心。儘管名叫‘驅動’,實際上和硬體裝置沒有任何關係,只是實現方式和裝置驅動程式是一樣的:它工作於核心態,提供open()mmap()poll()ioctl()等標準檔案操作,以字元驅動裝置中的misc設備註冊在裝置目錄 /dev下,使用者通過/dev/binder訪問該它。驅動負責程序之間Binder通訊的建立,Binder在程序之間的傳遞,Binder引用計數管理,資料包在程序之間的傳遞和互動等一系列底層支援。驅動和應用程式之間定義了一套介面協議,主要功能由ioctl()介面實現,不提供 read()write()介面,因為ioctl()靈活方便,且能夠一次呼叫實現先寫後讀以滿足同步互動,而不必分別呼叫write()和 read()

ServiceManager與實名Binder

和DNS類似,ServiceManager的作用是將字元形式的Binder名字轉化成Client中對該Binder的引用,使得Client能夠通過Binder 名字獲得對Server中Binder實體的引用。註冊了名字的Binder叫實名Binder,就象每個網站除了有IP地址外都有自己的網址。Server建立了Binder實體,為其取一個字元形式,可讀易記的名字,將這個Binder連同名字以資料包的形式通過Binder驅動傳送給ServiceManager,通知ServiceManager註冊一個名叫張三的Binder,它位於某個Server中。驅動為這個穿過程序邊界的Binder建立位於核心中的實體節點以及ServiceManager對實體的引用,將名字及新建的引用傳遞給ServiceManager。ServiceManager收資料包後,從中取出名字和引用填入一張查詢表中。

ServiceManager是一個程序,Server是另一個程序,Server向ServiceManager註冊Binder必然會涉及程序間通訊。當前實現的是程序間通訊卻又要用到程序間通訊,這就好象蛋可以孵出雞前提卻是要找只雞來孵蛋。Binder的實現比較巧妙:預先創造一隻雞來孵蛋。 ServiceManager和其它程序同樣採用Binder通訊,ServiceManager是Server端,有自己的Binder實體,其它程序都是Client,需要通過這個 Binder的引用來實現Binder的註冊,查詢和獲取。ServiceManager提供的Binder比較特殊,它沒有名字也不需要註冊,當一個程序使用 BINDER_SET_CONTEXT_MGR命令將自己註冊成ServiceManager時Binder驅動會自動為它建立Binder實體。其次這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向ServiceManager註冊自己Binder就必需通過0這個引用和ServiceManager的Binder通訊。類比網路通訊,0號引用就好比域名伺服器的地址,你必須手工或動態配置好。要注意這裡說的Client是相對ServiceManager而言的,一個應用程式是個提供服務的Server,但對ServiceManager來說它仍然是個Client。

Client 獲得實名Binder的引用

Server向ServiceManager註冊了Binder實體及其名字後,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向ServiceManager請求訪問某個Binder:我申請獲得名字叫張三的Binder的引用。ServiceManager收到這個連線請求,從請求資料包裡獲得Binder的名字,在查詢表裡找到該名字對應的條目,從條目中取出Binder的引用,將該引用作為回覆傳送給發起請求的Client。從面向物件的角度,這個 Binder物件現在有了兩個引用:一個位於ServiceManager中,一個位於發起請求的Client中。如果接下來有更多的Client請求該Binder,系統中就會有更多的引用指向該Binder,就象java裡一個物件存在多個引用一樣。而且類似的這些指向Binder的引用是強型別,從而確保只要有引用 Binder實體就不會被釋放掉。通過以上過程可以看出,ServiceManager象個火車票代售點,收集了所有火車的車票,可以通過它購買到乘坐各趟火車的票,即得到某個Binder的引用。

匿名 Binder

並不是所有Binder都需要註冊給ServiceManageServer端可以通過已經建立的Binder連線將建立的Binder實體傳給 Client,當然這條已經建立的Binder連線必須是通過實名Binder實現。由於這個Binder沒有向ServiceManager註冊名字,所以是個匿名 BinderClient將會收到這個匿名Binder的引用,通過這個引用向位於Server中的實體傳送請求。匿名Binder為通訊雙方建立一條私密通道,只要Server沒有把匿名Binder發給別的程序,別的程序就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder傳送請求。

Binder 協議

Binder協議基本格式是(命令+資料),使用ioctl(fd, cmd, arg)函式實現互動。命令由引數cmd承載,資料由引數arg承載,隨cmd不同而不同。下表列舉了所有命令及其所對應的資料:

表 2 Binder通訊命令字


這其中最常用的命令是BINDER_WRITE_READ。該命令的引數包括兩部分資料:一部分是向Binder寫入的資料,一部分是要從 Binder讀出的資料,驅動程式先處理寫部分再處理讀部分。這樣安排的好處是應用程式可以很靈活地處理命令的同步或非同步。例如若要傳送非同步命令可以只填 入寫部分而將read_size置成0;若要只從Binder獲得資料可以將寫部分置空即write_size置成0;若要傳送請求並同步等待返回資料可 以將兩部分都置上。

BINDER_WRITE_READ 之寫操作

Binder寫操作的資料時格式同樣也是(命令+資料)。這時候命令和資料都存放在binder_write_read 結構write_buffer域指向的記憶體空間裡,多條命令可以連續存放。資料緊接著存放在命令後面,格式根據命令不同而不同。下表列舉了Binder寫 操作支援的命令:

表 3 Binder寫操作命令字

在這些命令中,最常用的是BC_TRANSACTION/BC_REPLY命令對,Binder資料通過這對命令傳送給接收方。這對命令所承載的資料包由結構體struct binder_transaction_data定義。Binder互動有同步和非同步之分,利用binder_transaction_data中 flag域區分。如果flag域的TF_ONE_WAY位為1則為非同步互動,即Client端傳送完請求互動即結束, Server端不再返回BC_REPLY資料包;否則Server會返回BC_REPLY資料包,Client端必須等待接收完該資料包方才完成一次交 互。

BINDER_WRITE_READ 之讀操作

Binder裡讀出的資料格式和向Binder中寫入的資料格式一樣,採用(訊息ID+資料)形式,並且多條訊息可以連續存放。下表列舉了從 Binder讀出的命令字及其相應的引數:

表 4 Binder讀操作訊息ID


和寫資料一樣,其中最重要的訊息是BR_TRANSACTION 或BR_REPLY,表明收到了一個格式為binder_transaction_data的請求資料包(BR_TRANSACTION)或返回資料包 (BR_REPLY)。

Binder收發資料包結構binder_transaction_data

  1. struct binder_transaction_data {  
  2.     /* The first two are only used for bcTRANSACTION and brTRANSACTION, 
  3.      * identifying the target and contents of the transaction. 
  4.      */
  5.     union {  
  6.         size_t  handle; /* target descriptor of command transaction */
  7.         void    *ptr;   /* target descriptor of return transaction */
  8.     } target;  
  9.     void        *cookie;    /* target object cookie */
  10.     unsigned int    code;       /* transaction command */
  11.     /* General information about the transaction. */
  12.     unsigned int    flags;  
  13.     pid_t       sender_pid;  
  14.     uid_t       sender_euid;  
  15.     size_t      data_size;  /* number of bytes of data */
  16.     size_t      offsets_size;   /* number of bytes of offsets */
  17.     /* If this transaction is inline, the data immediately 
  18.      * follows here; otherwise, it ends with a pointer to 
  19.      * the data buffer. 
  20.      */
  21.     union {  
  22.         struct {  
  23.             /* transaction data */
  24.             constvoid  *buffer;  
  25.             /* offsets from buffer to flat_binder_object structs */
  26.             constvoid  *offsets;  
  27.         } ptr;  
  28.         uint8_t buf[8];  
  29.     } data;  
  30. };  



Binder 的表述

考察一次Binder通訊的全過程會發現,Binder存在於系統以下幾個部分中:

應用程式程序:又分為Server程序和Client程序

Binder驅動:ServerClient有不同表述形

傳輸資料:由於Binder可以跨程序傳遞,需要在傳輸資料中予以表述

在系統不同部分,Binder實現的功能不同,表現形式也不一樣的。接下來逐一探討Binder在各部分所扮演的角色和使用的資料結構

Binder 在應用程式中的表述

雖然Binder用到了面向物件的思想,但並不限制應用程式一定要使用面向物件的語言,無論是C語言還是C++語言都可以很容易的使用Binder 來通訊。例如儘管Android主要使用javaC++,像SMgr這麼重要的程序就是用C語言實現的。不過面向物件的方式表述起來更方便,所以本文假設應用程式是用面嚮物件語言實現的。Binder本質上只是一種底層通訊方式,和具體服務沒有關係。為了提供具體服務,Server必須提供一套介面函式以便Client通過遠端訪問使用各種服務。這時通常採用Proxy設計模式:將介面函式定義在一個抽象類中,ServerClient都會以該抽象類為基類實現所有介面函式,所不同的是Server端是真正的功能實現,而Client端是對這些函式遠端呼叫請求的包裝。如何將BinderProxy設計模式結合起來是應用程式實現面向物件Binder通訊的根本問題。

Binder Server端的表述 – Binder實體

做為Proxy設計模式的基礎,首先定義一個抽象介面類封裝Server所有功能,其中包含一系列純虛擬函式留待ServerProxy各自實現。 由於這些函式需要跨程序呼叫,須為其一一編號,從而Server可以根據收到的編號決定呼叫哪個函式。其次就要引入Binder了。Server端定義另一個Binder抽象類處理來自ClientBinder請求資料包,其中最重要的成員是虛擬函式onTransact()。該函式分析收到的資料包,呼叫相應的介面函式處理請求。接下來採用繼承方式以介面類和Binder抽象類為基類構建BinderServer中的實體,實現基類裡所有的虛擬函式,包括公共介面函式以及資料包處理函式:onTransact()。這個函式的輸入是來自Clientbinder_transaction_data結構的資料包。前面提到, 該結構裡有個成員code,包含這次請求的介面函式編號。onTransact()case-by-case地解析code值,從資料包裡取出函式引數,呼叫介面類中相應的已經實現的公共介面函式。函式執行完畢,如果需要返回資料就再構建一個binder_transaction_data包將返回資料包填入其中。那麼各個Binder實體的onTransact()又是什麼時候呼叫呢?這就需要驅動參與了。前面說過,Binder實體須要以Binde傳輸結構flat_binder_object形式傳送給其它程序才能建立Binder通訊,而Binder實體指標就存放在該結構的handle域中。驅動根據Binder位置陣列從傳輸資料中獲取該Binder的傳輸結構,為它建立位於核心中的Binder節點,將Binder實體指標記錄在該節點中。如果接下來有其它程序向該Binder傳送資料,驅動會根據節點中記錄的資訊將Binder實體指標填入binder_transaction_datatarget.ptr中返回給接收執行緒。接收執行緒從資料包中取出該指標,reinterpret_castBinder抽象類並呼叫onTransact()函式。由於這是個虛擬函式,不同的Binder實體中有各自的實現,從而可以呼叫到不同Binder實體提供的onTransact()

Binder Client端的表述 – Binder引用

做為Proxy設計模式的一部分,Client端的Binder同樣要繼承Server提供的公共介面類並實現公共函式。但這不是真正的實現,而是對遠端函式呼叫的包裝:將函式引數打包,通過BinderServer傳送申請並等待返回值。為此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域是個聯合體,包括ptrhandle兩個成員,前者用於作為響應方的Server,指向Binder實體對應的記憶體空間;後者用於作為請求方的Client,存放Binder實體的引用,告知驅動資料包將路由給哪個實體。資料包準備好後,通過驅動介面傳送出去。經過BC_TRANSACTION/BC_REPLY回合完成函式的遠端呼叫並得到返回值。

Binder 在傳輸資料中的表述

Binder可以塞在資料包的有效資料中越程序邊界從一個程序傳遞給另一個程序,這些傳輸中的Binder用結構 flat_binder_object表示,如下表所示: