Android Binder設計與實現(2) – 設計篇

來源:互聯網
上載者:User

 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設計模式:將介面函數定義在一個抽象類別中(如IMediaPlayerService),Server和Client都會以該抽象類別為基類實現所有介面函數,所不同的是Server端是真正的功能實現,而Client端是對這些函數遠程調用請求的封裝(如:BpMediaPlayerService)。如何將Binder和Proxy設計模式結合起來是應用程式實現物件導向Binder通訊的根本問題。

 

5.1.1 Binder 在Server端的表述– Binder實體
       做為Proxy設計模式的基礎,首先定義一個抽象介面類(如:IMediaPlayerService)封裝Server所有功能,其中包含一系列純虛函數留待Server和Proxy各自實現。 由於這些函數需要跨進程調用,須為其一一編號,從而Server可以根據收到的編號決定調用哪個函數。其次就要引入Binder了。Server端定義另一個Binder抽象類別處理來自Client的Binder請求資料包,其中最重要的成員是虛函數onTransact()(如:BnMediaPlayerService)。該函數分析收到的資料包,調用相應的介面函數處理請求。

       接下來採用繼承方式以介面類和Binder抽象類別為基類構建Binder在Server中的實體(如:MediaPlayerService),實現基類裡所有的虛函數,包括公用介面函數以及資料包處理函數: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提供的公用介面類並實現公用函數(如:BpMediaPlayerService)。但這不是真正的實現,而是對遠程函數調用的封裝:將函數參數打包,通過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的安全 性是一樣的。

     SurfaceComposerClient與SurfaceFlinger間共用的顯示buffer就是使用檔案Binder來實現的,先把fd傳過來,然後再mmap,這樣就獲得了用於畫圖的顯示buffer了。

 

5.3 Binder 在驅動中的表述
       驅動是Binder通訊的核心,系統中所有的Binder實體以及每個實體在各個進程中的引用都登記在驅動中;驅動需要記錄Binder引用 ->實體之間多對一的關係;為引用找到對應的實體;在某個進程中為實體建立或尋找到對應的引用;記錄Binder的歸屬地(位於哪個進程中);通過管理Binder的強/弱引用建立/銷毀Binder實體等等。

       驅動裡的Binder是什麼時候建立的呢?前面提到過,為了實現實名Binder的註冊,系統必須建立第一隻雞 -- 為SMgr建立的,註冊實名Binder專用的Binder實體,負責實名Binder註冊過程中的處理序間通訊。既然建立了實體也要有對應的引用:驅動將所有進程中的0號引用都預留給該Binder實體,即一開始所有進程的0號引用都指註冊實名Binder專用的Binder,無須特殊操作任何進程通過0 號引用都可以註冊實名Binder。接下來隨著應用程式通過不斷地註冊實名Binder,不斷向SMgr索要Binder的引用,不斷將Binder從一
個進程傳遞給另一個進程,越來越多的Binder以傳輸結構 – flat_binder_object的形式穿越驅動做跨進程的遷徙。由於binder_transaction_data中data.offset數組的存在,所有流經驅動的Binder都逃不過驅動的眼睛。Binder將對每個穿越進程邊界的Binder做如下操作:檢查傳輸結構的type域,如果是 BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER則建立Binder的實體;如果是 BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE則建立Binder的引用;如果是
BINDER_TYPE_HANDLE則為進程開啟檔案,無須建立任何資料結構。詳細過程可參考表7。隨著越來越多的Binder實體或引用穿過驅動在進 程間傳遞,驅動會在核心裡建立越來越多的節點或引用,當然這個過程對使用者來說是透明的。

 

5.3.1 Binder 實體在驅動中的表述
       驅動中的Binder實體也叫‘節點’,隸屬於提供實體的進程,由struct binder_node結構來表示:

表 8 Binder節點描述結構:binder_node

成員 含義
int debug_id; 用於調試
struct binder_work work; 當本節點引用計數發生改變,需要通知所屬進程時,通過該成員掛入所屬進程的to-do隊列裡,喚醒所屬進程執行Binder實體引用計數的修改。
union {

struct rb_node rb_node;

struct hlist_node dead_node;

};

每個進程都維護一棵紅/黑樹狀結構,以Binder實體在使用者空間的指標,即本結構的ptr成員為索引存放該進程所有的Binder實體。這樣驅動可以根據 Binder實體在使用者空間的指標很快找到其位於核心的節點。rb_node用於將本節點鏈入該紅/黑樹狀結構中。

銷毀節點時須將rb_node從紅/黑樹狀結構中摘除,但如果本節點還有引用沒有切斷,就用dead_node將節點隔離到另一個鏈表中,直到通知所有進程 切斷與該節點的引用後,該節點才可能被銷毀。

struct binder_proc *proc; 本成員指向節點所屬的進程,即提供該節點的進程。
struct hlist_head refs; 本成員是隊列頭,所有指向本節點的引用都連結在該隊列裡。這些引用可能隸屬於不同的進程。通過該隊列可以遍曆指向該節點的所有引用。
int internal_strong_refs; 用以實現強指標的計數器:產生一個指向本節點的強引用該計數就會加1。
int local_weak_refs; 驅動為傳輸中的Binder設定的弱引用計數。如果一個Binder打包在資料包中從一個進程發送到另一個進程,驅動會為該Binder增加引用計 數,直到接收進程通過BC_FREE_BUFFER通知驅動釋放該資料包的資料區為止。
int local_strong_refs; 驅動為傳輸中的Binder設定的強引用計數。同上。
void __user *ptr; 指向使用者空間Binder實體的指標,來自於flat_binder_object的binder成員
void __user *cookie; 指向使用者空間的附加指標,來自於flat_binder_object的cookie成員
unsigned has_strong_ref;

unsigned pending_strong_ref;

unsigned has_weak_ref;

unsigned pending_weak_ref

這一組標誌用於控制驅動與Binder實體所在進程互動式修改引用計數
unsigned has_async_transaction; 該成員表明該節點在to-do隊列中有非同步互動尚未完成。驅動將所有發送往接收端的資料包暫存在接收進程或線程開闢的to-do隊列裡。對於非同步交 互,驅動做了適當流控:如果to-do隊列裡有非同步互動尚待處理則該成員置1,這將導致新到的非同步互動存放在本結構成員 – asynch_todo隊列中,而不直接送到to-do隊列裡。目的是為同步互動讓路,避免長時間阻塞發送端。
unsigned accept_fds 表明節點是否同意接受檔案方式的Binder,來自flat_binder_object中flags成員的 FLAT_BINDER_FLAG_ACCEPTS_FDS位。由於接收檔案Binder會為進程自動開啟一個檔案,佔用有限的檔案描述符,節點可以設定 該位拒絕這種行為。
int min_priority 設定處理Binder請求的線程的最低優先順序。發送線程將資料提交給接收線程處理時,驅動會將發送線程的優先順序也賦予接收線程,使得資料即使跨了進 程也能以同樣優先順序得到處理。不過如果發送線程優先順序過低,接收線程將以預設的最小值運行。

該域的值來自於flat_binder_object中flags成員。

struct list_head async_todo 非同步互動等待隊列;用於分流發往本節點的非同步互動包

       每個進程都有一棵紅/黑樹狀結構用於存放建立好的節點,以Binder在使用者空間的指標作為索引。每當在傳輸資料中偵測到一個代表Binder實體的 flat_binder_object,先以該結構的binder指標為索引搜尋紅/黑樹狀結構;如果沒找到就建立一個新節點添加到樹中。由於對於同一個進程來說 記憶體位址是唯一的,所以不會重複建設造成混亂。

 5.3.2 Binder 引用在驅動中的表述
       與實體一樣,Binder的引用也是驅動根據傳輸資料中的flat_binder_object建立的,隸屬於獲得該引用的進程,用struct binder_ref結構體表示:

表 9 Binder引用描述結構:binder_ref

成員 含義
int debug_id; 調試用
struct rb_node rb_node_desc; 每個進程有一棵紅/黑樹狀結構,進程所有引用以引用號(即本結構的desc域)為索引添入該樹中。本成員用做連結到該樹的一個節點。
struct rb_node rb_node_node; 每個進程又有一棵紅/黑樹狀結構,進程所有引用以節點實體在驅動中的記憶體位址(即本結構的node域)為所引添入該樹中。本成員用做連結到該樹的一個節點。
struct hlist_node node_entry; 該域將本引用做為節點鏈入所指向的Binder實體結構binder_node中的refs隊列
struct binder_proc *proc; 本引用所屬的進程
struct binder_node *node; 本引用所指向的節點(Binder實體)
uint32_t desc; 本結構的引用號
int strong; 強引用計數
int weak; 弱引用計數
struct binder_ref_death *death; 應用程式向驅動發送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令從 而當Binder實體銷毀時能夠收到來自驅動的提醒。該域不為空白表明使用者訂閱了對應實體銷毀的‘噩耗’。

 

       就象一個對象有很多指標一樣,同一個Binder實體可能有很多引用,不同的是這些引用可能分布在不同的進程中。和實體一樣,每個進程使用紅/黑樹狀結構存放所有該進程正在使用的引用。但Binder的引用可以通過兩個索引值索引:

      · 對應實體在核心中的地址。注意這裡指的是驅動建立於核心中的binder_node結構的地址,而不是Binder實體在使用者進程中的地址。實體在核心中的地址是唯一的,用做索引不會產生二義性;但實體可能來自不同使用者進程,而實體在不同使用者進程中的地址可能重合,不能用來做索引。驅動利用該紅/黑樹狀結構在一個 進程中快速尋找某個Binder實體所對應的引用(一個實體在一個進程中只建立一個引用)。

     · 引用號。引用號是驅動為引用分配的一個32位標識,在一個進程內是唯一的,而在不同進程中可能會有同樣的值,這和進程的開啟檔案號很類似。引用號將返回給應用程式,可以看作Binder引用在使用者進程中的控制代碼。除了0號引用在所有進程裡都保留給SMgr,其它值由驅動在建立引用時動態分配。向Binder 發送資料包時,應用程式通過將引用號填入binder_transaction_data結構的target.handle域中表明該資料包的目的
Binder。

       驅動根據該引用號在紅/黑樹狀結構中找到引用的binder_ref結構,進而通過其node域知道目標Binder實體所在的進程及其它相關資訊,實現資料包的路由。

 

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.