轉 http://blog.csdn.net/universus/article/details/6211589java
關鍵詞linux
Binder Android IPC Linux 內核 驅動數據庫
摘要設計模式
Binder是Android系統進程間通訊(IPC)方式之一。Linux已經擁有管道,system V IPC,socket等IPC手段,卻還要倚賴Binder來實現進程間通訊,說明Binder具備無可比擬的優點。深刻了解Binder並將之與傳統IPC作對比有助於咱們深刻領會進程間通訊的實現和性能優化。本文將對Binder的設計細節作一個全面的闡述,首先經過介紹Binder通訊模型和Binder通訊協議瞭解Binder的設計需求;而後分別闡述Binder在系統不一樣部分的表述方式和起的做用;最後還會解釋Binder在數據接收端的設計考慮,包括線程池管理,內存映射和等待隊列管理等。經過本文對Binder的詳細介紹以及與其它IPC通訊方式的對比,讀者將對Binder的優點和使用Binder做爲Android主要IPC方式的緣由有深刻了解。數組
基於Client-Server的通訊方式普遍應用於從互聯網和數據庫訪問到嵌入式手持設備內部通訊等各個領域。智能手機平臺特別是Android系統中,爲了嚮應用開發者提供豐富多樣的功能,這種通訊方式更是無處不在,諸如媒體播放,視音頻頻捕獲,到各類讓手機更智能的傳感器(加速度,方位,溫度,光亮度等)都由不一樣的Server負責管理,應用程序只需作爲Client與這些Server創建鏈接即可以使用這些服務,花不多的時間和精力就能開發出使人眩目的功能。Client-Server方式的普遍採用對進程間通訊(IPC)機制是一個挑戰。目前linux支持的IPC包括傳統的管道,System V IPC,即消息隊列/共享內存/信號量,以及socket中只有socket支持Client-Server的通訊方式。固然也能夠在這些底層機制上架設一套協議來實現Client-Server通訊,但這樣增長了系統的複雜性,在手機這種條件複雜,資源稀缺的環境下可靠性也難以保證。緩存
另外一方面是傳輸性能。socket做爲一款通用接口,其傳輸效率低,開銷大,主要用在跨網絡的進程間通訊和本機上進程間的低速通訊。消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的緩存區中,而後再從內核緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。共享內存雖然無需拷貝,但控制複雜,難以使用。安全
表 1 各類IPC方式數據拷貝次數性能優化
IPC服務器 |
數據拷貝次數網絡 |
共享內存 |
0 |
Binder |
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使用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框架定義了四個角色:Server,Client,ServiceManager(之後簡稱SMgr)以及Binder驅動。其中Server,Client,SMgr運行於用戶空間,驅動運行於內核空間。這四個角色的關係和互聯網相似:Server是服務器,Client是客戶終端,SMgr是域名服務器(DNS),驅動是路由器。
和路由器同樣,Binder驅動雖然默默無聞,倒是通訊的核心。儘管名叫‘驅動’,實際上和硬件設備沒有任何關係,只是實現方式和設備驅動程序是同樣的:它工做於內核態,提供open(),mmap(),poll(),ioctl()等標準文件操做,以字符驅動設備中的misc設備註冊在設備目錄/dev下,用戶經過/dev/binder訪問該它。驅動負責進程之間Binder通訊的創建,Binder在進程之間的傳遞,Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。驅動和應用程序之間定義了一套接口協議,主要功能由ioctl()接口實現,不提供read(),write()接口,由於ioctl()靈活方便,且可以一次調用實現先寫後讀以知足同步交互,而沒必要分別調用write()和read()。Binder驅動的代碼位於linux目錄的drivers/misc/binder.c中。
和DNS相似,SMgr的做用是將字符形式的Binder名字轉化成Client中對該Binder的引用,使得Client可以經過Binder名字得到對Server中Binder實體的引用。註冊了名字的Binder叫實名Binder,就象每一個網站除了有IP地址外還有本身的網址。Server建立了Binder實體,爲其取一個字符形式,可讀易記的名字,將這個Binder連同名字以數據包的形式經過Binder驅動發送給SMgr,通知SMgr註冊一個名叫張三的Binder,它位於某個Server中。驅動爲這個穿過進程邊界的Binder建立位於內核中的實體節點以及SMgr對實體的引用,將名字及新建的引用打包傳遞給SMgr。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。
Server向SMgr註冊了Binder實體及其名字後,Client就能夠經過名字得到該Binder的引用了。Client也利用保留的0號引用向SMgr請求訪問某個Binder:我申請得到名字叫張三的Binder的引用。SMgr收到這個鏈接請求,從請求數據包裏得到Binder的名字,在查找表裏找到該名字對應的條目,從條目中取出Binder的引用,將該引用做爲回覆發送給發起請求的Client。從面向對象的角度,這個Binder對象如今有了兩個引用:一個位於SMgr中,一個位於發起請求的Client中。若是接下來有更多的Client請求該Binder,系統中就會有更多的引用指向該Binder,就象java裏一個對象存在多個引用同樣。並且相似的這些指向Binder的引用是強類型,從而確保只要有引用Binder實體就不會被釋放掉。經過以上過程能夠看出,SMgr象個火車票代售點,收集了全部火車的車票,能夠經過它購買到乘坐各趟火車的票-獲得某個Binder的引用。
並非全部Binder都須要註冊給SMgr廣而告之的。Server端能夠經過已經創建的Binder鏈接將建立的Binder實體傳給Client,固然這條已經創建的Binder鏈接必須是經過實名Binder實現。因爲這個Binder沒有向SMgr註冊名字,因此是個匿名Binder。Client將會收到這個匿名Binder的引用,經過這個引用向位於Server中的實體發送請求。匿名Binder爲通訊雙方創建一條私密通道,只要Server沒有把匿名Binder發給別的進程,別的進程就沒法經過窮舉或猜想等任何方式得到該Binder的引用,向該Binder發送請求。
下圖展現了參與Binder通訊的全部角色,將在之後章節中一一提到。
圖 1 Binder通訊示例
考察一次Binder通訊的全過程會發現,Binder存在於系統如下幾個部分中:
· 應用程序進程:分別位於Server進程和Client進程中
· Binder驅動:分別管理爲Server端的Binder實體和Client端的引用
· 傳輸數據:因爲Binder能夠跨進程傳遞,須要在傳輸數據中予以表述
在系統不一樣部分,Binder實現的功能不一樣,表現形式也不同。接下來逐一探討Binder在各部分所扮演的角色和使用的數據結構。
雖然Binder用到了面向對象的思想,但並不限制應用程序必定要使用面向對象的語言,不管是C語言仍是C++語言均可以很容易的使用Binder來通訊。例如儘管Android主要使用java/C++,象SMgr這麼重要的進程就是用C語言實現的。不過面向對象的方式表述起來更方便,因此本文假設應用程序是用面嚮對象語言實現的。
Binder本質上只是一種底層通訊方式,和具體服務沒有關係。爲了提供具體服務,Server必須提供一套接口函數以便Client經過遠程訪問使用各類服務。這時一般採用Proxy設計模式:將接口函數定義在一個抽象類中,Server和Client都會以該抽象類爲基類實現全部接口函數,所不一樣的是Server端是真正的功能實現,而Client端是對這些函數遠程調用請求的包裝。如何將Binder和Proxy設計模式結合起來是應用程序實現面向對象Binder通訊的根本問題。
作爲Proxy設計模式的基礎,首先定義一個抽象接口類封裝Server全部功能,其中包含一系列純虛函數留待Server和Proxy各自實現。因爲這些函數須要跨進程調用,須爲其一一編號,從而Server能夠根據收到的編號決定調用哪一個函數。其次就要引入Binder了。Server端定義另外一個Binder抽象類處理來自Client的Binder請求數據包,其中最重要的成員是虛函數onTransact()。該函數分析收到的數據包,調用相應的接口函數處理請求。
接下來採用繼承方式以接口類和Binder抽象類爲基類構建Binder在Server中的實體,實現基類裏全部的虛函數,包括公共接口函數以及數據包處理函數:onTransact()。這個函數的輸入是來自Client的binder_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_data的target.ptr中返回給接收線程。接收線程從數據包中取出該指針,reinterpret_cast成Binder抽象類並調用onTransact()函數。因爲這是個虛函數,不一樣的Binder實體中有各自的實現,從而能夠調用到不一樣Binder實體提供的onTransact()。
作爲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回合完成函數的遠程調用並獲得返回值。