android Binder設計與實現四

5 Binder 的表述

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

· 應用程序進程:又分為Server進程和Client進程

· Binder驅動:Server和Client有不同表述形式

· 傳輸數據:由於Binder可以跨進程傳遞,需要在傳輸數據中予以表述

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

5.1 Binder 在應用程序中的表述

雖然Binder用到瞭面向對象的思想,但並不限制應用程序一定要使用面向對象的語言,無論是C語言還是C++語言都可以很容易的使用Binder 來通信。例如盡管Android主要使用java或C++,象SMgr這麼重要的進程就是用C語言實現的。不過面向對象的方式表述起來更方便,所以本文假設應用程序是用面向對象語言實現的。

Binder本質上隻是一種底層通信方式,和具體服務沒有關系。為瞭提供具體服務,Server必須提供一套接口函數以便Client通過遠程訪問 使用各種服務。這時通常采用Proxy設計模式:將接口函數定義在一個抽象類中,Server和Client都會以該抽象類為基類實現所有接口函數,所不 同的是Server端是真正的功能實現,而Client端是對這些函數遠程調用請求的包裝。如何將Binder和Proxy設計模式結合起來是應用程序實 現面向對象Binder通信的根本問題。

5.1.1 Binder 在Server端的表述 – 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()。

5.1.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回合完成函數的遠程調用並得到返回值。

5.2 Binder 在傳輸數據中的表述

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

表 6 Binder傳輸結構:flat_binder_object

  

成員 含義
unsigned long type 表明該Binder的類型,包括以下幾種:
BINDER_TYPE_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是強類型;
BINDER_TYPE_WEAK_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是弱類型;
BINDER_TYPE_HANDLE:表示傳遞的是Binder強類型的引用
BINDER_TYPE_WEAK_HANDLE:表示傳遞的是Binder弱類型的引用
BINDER_TYPE_FD:表示傳遞的是文件形式的Binder,詳見下節
unsigned long flags 該域隻對第一次傳遞Binder實體時有效,因為此刻驅動需要在內核中創建相應的實體節點,有些參數需要從該域取出:
第0-7位:代碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實體請求數據包的線程的最低優先級。當一個應用程序提供多個實體時,可以通過該參數調整分配給各個實體的處理能力。
第8位:代碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實體可以接收其它進程發過來的文件形式的Binder。由於接收文件形式的Binder會在本進程中自動打開文件,有些Server可以用該標志禁止該功能,以防打開過多文件。
union {
void *binder;
signed long handle;
};
當傳遞的是Binder實體時使用binder域,指向Binder實體在應用程序中的地址。
當傳遞的是Binder引用時使用handle域,存放Binder在進程中的引用號。
void *cookie; 該域隻對Binder實體有效,存放與該Binder有關的附加信息。

 

無論是Binder實體還是對實體的引用都從屬與某個進程,所以該結構不能透明地在進程之間傳輸,必須有驅動的參與。例如當Server把 Binder實體傳遞給Client時,在發送數據中,flat_binder_object中的type是 BINDER_TYPE_BINDER,binder指向Server進程用戶空間地址。如果透傳給接收端將毫無用處,驅動必須對數據流中的這個 Binder做修改:將type該成BINDER_TYPE_HANDLE;為這個Binder在接收進程中創建位於內核中的引用並將引用號填入 handle中。對於發生數據流中引用類型的Binder也要做同樣轉換。經過處理後接收進程從數據流中取得的Binder引用才是有效的,才可以將其填入數據包binder_transaction_data的target.handle域,向Binder實體發送請求。

這樣做也是出於安全性考慮:應用程序不能隨便猜測一個引用號填入target.handle中就可以向Server請求服務瞭,因為驅動並沒有為你在內核中創建該引用,必定會驅動被拒絕。唯有經過身份認證確認合法後,由‘權威機構’通過數據流授予你的Binder才能使用,因為這時驅動已經在內核中 為你建立瞭引用,交給你的引用號是合法的。

下表總結瞭當flat_binder_object結構穿過驅動時驅動所做的操作:

表 7 驅動對flat_binder_object的操作

  

Binder 類型(type 域) 在發送方的操作 在接收方的操作
BINDER_TYPE_BINDER
BINDER_TYPE_WEAK_BINDER
隻有實體所在的進程能發送該類型的Binder。如果是第一次發送驅動將創建實體在內核中的節點,並保存binder,cookie,flag域。 如果是第一次接收該Binder則創建實體在內核中的引用;將handle域替換為新建的引用號;將type域替換為BINDER_TYPE_(WEAK_)HANDLE
BINDER_TYPE_HANDLE
BINDER_TYPE_WEAK_HANDLE
獲得Binder引用的進程都能發送該類型Binder。驅動根據handle域提供的引用號查找建立在內核的引用。如果找到說明引用號合法,否則拒絕該發送請求。 如果收到的Binder實體位於接收進程中:將ptr域替換為保存在節點中的binder值;cookie替換為保存在節點中的cookie 值;type替換為BINDER_TYPE_(WEAK_)BINDER。
如果收到的Binder實體不在接收進程中:如果是第一次接收則創建實體在內核中的引用;將handle域替換為新建的引用號
BINDER_TYPE_FD 驗證handle域中提供的打開文件號是否有效,無效則拒絕該發送請求。 在接收方創建新的打開文件號並將其與提供的打開文件描述結構綁定。

5.2.1 文件形式的 Binder

除瞭通常意義上用來通信的Binder,還有一種特殊的Binder:文件Binder。這種Binder的基本思想是:將文件看成Binder實 體,進程打開的文件號看成Binder的引用。一個進程可以將它打開文件的文件號傳遞給另一個進程,從而另一個進程也打開瞭同一個文件,就象Binder 的引用在進程之間傳遞一樣。

一個進程打開一個文件,就獲得與該文件綁定的打開文件號。從Binder的角度,linux在內核創建的打開文件描述結構struct file是Binder的實體,打開文件號是該進程對該實體的引用。既然是Binder那麼就可以在進程之間傳遞,故也可以用 flat_binder_object結構將文件Binder通過數據包發送至其它進程,隻是結構中type域的值為BINDER_TYPE_FD,表明 該Binder是文件Binder。而結構中的handle域則存放文件在發送方進程中的打開文件號。我們知道打開文件號是個局限於某個進程的值,一旦跨進程就沒有意義瞭。這一點和Binder實體用戶指針或Binder引用號是一樣的,若要跨進程同樣需要驅動做轉換。驅動在接收Binder的進程空間創 建一個新的打開文件號,將它與已有的打開文件描述結構struct file勾連上,從此該Binder實體又多瞭一個引用。新建的打開文件號覆蓋flat_binder_object中原來的文件號交給接收進程。接收進 程利用它可以執行read(),write()等文件操作。

傳個文件為啥要這麼麻煩,直接將文件名用Binder傳過去,接收方用open()打開不就行瞭嗎?其實這還是有區別的。首先對同一個打開文件共享的層次不同:使用文件Binder打開的文件共享linux VFS中的struct file,struct dentry,struct inode結構,這意味著一個進程使用read()/write()/seek()改變瞭文件指針另一個進程的文件指針也會改變;而如果兩個進程分別使用文件名打開同一文件則有各自的struct file結構,從而各自獨立維護文件指針,互不幹擾。其次是一些特殊設備文件要求在struct file一級共享才能使用,例如android的另一個驅動ashmem,它和Binder一樣也是misc設備,用以實現進程間的共享內存。一個進程打開的ashmem文件隻有通過文件Binder發送到另一個進程才能實現內存共享,這大大提高瞭內存共享的安全性,道理和Binder增強瞭IPC的安全 性是一樣的。

摘自 LuoXianXiong,您的夥伴

 

發佈留言