Android Binder 設計思想

Android Binder 設計思想

來自此處《Android Bander設計與實現 - 設計篇》的筆記

1引言

1.1 傳統IPC有何弊端?爲什麼要新建Binder?

1.1.1傳輸性能

  • socket做爲通用接口,傳輸效率低,開銷大,主要用於在跨網絡的進程間通訊和本機低速網絡通訊,兩次拷貝過程
  • 消息隊列和管道採用存儲-轉發方式,發送方到內核緩存區,內核緩存區到接收方緩存區,至少兩次拷貝過程
  • 共享內存無需拷貝,但控制複雜,難以使用

1.1.2安全性考慮

  • 傳統IPC沒法獲取發送方可靠的UID/PID, binder能夠在內核中添加可靠的身份表示
  • 傳統IPC訪問接入點是開發的,沒法建議私有通道,例如socket的ip地址都是開放的,沒法阻止惡意程序猜想接收方地址,從而獲取鏈接

1.2 Binder是什麼?

  • 基於Client-Sever通訊模式的IPC機制
  • 傳輸過程只需1次拷貝
  • 爲發送方添加UID/PID身份,同時支持實名Binder和匿名Binder,安全性高

2 面向對象的 Binder IPC

2.1 Client-Server通訊模型

必要條件

  • server必需要有肯定的訪問接入點(或者說地址)來接受Client的請求,而且Client能夠經過某種途徑獲知Server地址
  • 指定Command-Reply協議來傳輸數據

網絡通訊

  • Server接入點就是Server主機IP+端口號
  • 傳輸協議爲TCP協議

binder

  • 對於Server而言,binder是Server提供的某個特定服務的訪問接入點,Clitent經過這個「地址」向Server發送請求來使用該服務
  • 對於Client而言,binder是通向Server的管道入口,想要和某個Server通訊,必須創建這個管道並獲取管道入口

2.2 Binder 面向對象思想

  • Binder實體是位於Server進程中的對象,該對象提供了一套方法用以實現對服務的請求,就像類的成員函數。
  • 遍及於client進程中的入口,能夠當作是Binder實體的「指針」,「引用」,「代理」或者說是「句柄。Client經過binder引用訪問Server。
  • 面向對象的思想將進程間通訊轉化爲「使用binder引用來訪問binder實體對象的方法」。binder實體是一個能夠跨進程引用的對象。binder實體位於某個進程中,而binder引用遍及於系統的各個進程中。
  • 形形色色的binder對象以及星羅棋佈的應用彷彿粘連各個應用程序的膠水,這也是Binder的英文原意。

2.3 Binder驅動

Binder驅動和內核其餘模塊同樣,使用C語言實現,爲面向對象的進程間通訊提供底層支持。
複製代碼

3 Binder通訊模型

3.0 Binder框架四個角色--類比互聯網:java

  • 運行於用戶空間:Server(服務器)、Client(客戶端)、ServerManager(DNS)
  • 運行於內核空間:Binder驅動(路由器)

3.1 Binder驅動

  • 通訊核心,工做於內核態,提供open(),mmap(),ioctl()等標準文件操做
  • 非硬件設備,只是實現方式與設備驅動程序同樣,以字符驅動設備中的misc設備註冊在dev下,經過/dev/binder訪問
  • 負責進程間Binder通訊的創建,binder在進程中的傳遞,binder引用計數管理,數據包在進程間的傳遞與交互等一系列底層支持。
  • 驅動與應用程序之間定義了一套接口協議,主要功能由ioctl()接口實現
  • 驅動代碼位於linux目錄的drivers/misc/binder.c中

3.2 ServiceManager與實名Binder(註冊)

  • SM的做用是將字符形式的Binder名稱轉化成Client中對該Binder的引用。
  • 註冊了名字的Binder叫實名Binder,就像網站有ip,也有地址。
  • Server建立Binder實體,並取一個字符名稱。將Binder連同名稱以數據包的形式經過Binder驅動發送給SM
  • Binder驅動爲此Binder建立位於內核中的實體節點和SM對實體的引用。SM收到數據包後,從中取出名字和引用填入一張查找表中,即註冊過程。
  • SM是Server端,有本身的Binder實體。特殊的是沒有名字,無需註冊。一個進程使用BINDER_SET_CONTEXT_MGR命令時,那此進程就會將自身註冊成SM,同時驅動會自動爲其建立Binder實體。此Binder的引用在全部Client中都固定爲0。相似網絡通訊,0號引用比如域名服務器地址,必須預先配置好。

3.3 Client獲取實名Binder引用(查找)

  • 經過前一階段的註冊,Client能夠直接經過名字獲取Binder引用。但咱們一般經過0號引用即SM,來獲取某個binder引用,此獲取過程一樣須要binder名稱。
  • SM中始終留有一個Binder引用,每一個Client經過查找都會獲得一個新的binder引用。如同java對象引用,同時也都是強引用。

3.4匿名Binder

並非全部進程中的binder都須要註冊給SM廣而告之。例如某個client進程能夠經過已經創建的Binder鏈接,將本身的binder實體引用發送給Server進程。Server進程使用該引用請求Client進程,此過程進程雙方的角色就發生了互換。例如應用啓動過程當中的ApplicationThread
複製代碼

4 Binder協議

4.0概述

基本格式:命令+數據。使用ioctl(fd,cmd,arg)交互。node

4.1 BINDER_WRITE_READ之寫操做

數據格式一樣爲:命令+數據。均存放在write_buffer域指向的內存空間,多條命令可連續存放。數據緊跟着命令後面,數據格式會有所不一樣.
複製代碼
Binder寫操做命令字 arg
BC_TRANSACTION 最經常使用命令之一,Client經過驅動向Server發送請求數據, struct binder_transaction_data 利用binder_transaction_data中的flag域區分同步異步。flag域中的TF_ONE_WAY爲1,則爲異步,Client無需等待BC_REPLY數據包。不然須要等到數據包接收,纔算完成一次交互。
BC_REPLY 最經常使用命令之二Server經過驅動向Client發送應答數據
BC_ACQUIRE_RESULT
BC_ATTEMPT_ACQUIRE
BC_FREE_BUFFER 釋放一塊映射的內存
BC_INCREFS BC_ACQUIRE BC_RELEASE BC_DECREFS 管理Binder引用計數 32位Binder引用號
BC_INCREFS_DONE BC_ACQUIRE_DONE Binder實體處理引用計數後,給Binder驅動的反饋
BC_REGISTER_LOOPER 通知binder驅動,Server端建立了一個線程 Server端線程池管理
BC_ENTER_LOOPER 通知binder驅動,Server端某個線程進入主循環,能夠接收數據
BC_EXIT_LOOPER 通知binder驅動,Server端某個線程退出主循環,再也不接收數據
BC_REQUEST_DEATH_NOTIFICATION client要求binder驅動在Binder實體銷燬時,client能獲得通知 參數1:uint32 *ptr; 須要獲得死亡通知的Binder引用 參數2:void * *cookie: 與死亡通知相關的信息,驅動會在發出死亡通知時返回給發出請求的進程。
BC_DEAD_BINDER_DONE client收到實體死亡通知,刪除引用,並告知驅動

4.2 BINDER_WRITE_READ 從Binder中讀出數據

格式與寫操做一致,一樣能連續存放。linux

Binder讀操做命令字
BR_ERROR 內部錯誤?
BR_OK
BR_NOOP
操做成功,與BR_TRANSACTION_COMPLETE區別在哪?
BR_SPAWN_LOOPER 驅動發現接收方線程不夠用,要求接收方建立線程
BR_TRANSACTION
BR_REPLY
BR_ACQUIRE_RESULT
BR_ATTEMPT_ACQUIRE
BRFINISHED
BR_DEAD_REPLAY 交互過程當中若是發現對方進程或線程已經死亡則返回該消息
BR_TRANSACTION_COMPLETE 驅動告知發送方「發送成功」,與接收方是否返回請求數據無關
BR_INCREFS
BR_ACQUIRE
BR_RELEASE
BR_DECREFS
引用與計數管理
BR_DEAD_BINDER
BR_CLEAR_DEATH_NOTIFICATION_DONE
告知Client進程,Binder實體死亡通知處理相關
BR_FAILED_REPLY 發送非法引用號,則返回此

4.3 struct binder_transaction_data 收發數據包結構

成員定義
union {
   size_t handle;
   void *ptr;
} target;
對於發送方 target指向目的地。target.handle(即句柄)存放binder引用
當數據包到達接收方時,target已被驅動賦予了binder實體對象內存的指針,存放在target.ptr
void *cookie; 發送方忽略該成員,binder實體建立時,接收方自定義的任意數值,與binder指針相關的額外信息,驅動也基本不關心該成員
unsigned int code; Server端定義的公共接口函數編號(業務碼?)
unsigned int flags; 交互相關標誌位,其中最重要的是TF_ONE_WAY位
pid_t sender_pid; 發送方進程ID和用戶ID,由驅動負責填入
uid_t sender_euid;
size_t data_size; Server定義的公共接口函數關心的數據相關,傳輸中的Binder,以flat_binder_object的形式包含在buffer中.
buffer指向部分會copy到映射區
size_t offsets_size;
union{
  struct { *buffer;*offsets} ptr; 
  uint8_t buf[8];
}data;

5 Binder的表述

Binder存在於系統的這些部分:設計模式

  • 應用程序進程:Server進程和Client進程
  • Binder驅動:分別管理爲Server端Binder實體和Client端引用
  • 傳輸數據:Binder能夠跨進程傳遞,須要在傳輸數據中予以表述

在不一樣的部分,Binder實現的功能不一樣,表現形式也不同。咱們須要關心其扮演的角色和使用的數據結構。 緩存

5.1 Binder在應用程序中的表述

Binder本質是底層通訊的方式,與具體服務無關。Server必須提供一套接口函數,以便Client遠程訪問。
這時一般採用Proxy設計模式,將接口函數定義在一個抽象類中安全

  • Server端,實現全部接口函數,是正在的功能實現
  • Client端一樣實現全部接口函數,但倒是對這些函數遠程調用請求的包裝

如何將Binder和Proxy設計模式結合是應用程序實現面向對象Binder通訊的根本問題 服務器

5.1.1 在server端表述--Binder實體

Binder實體類實現兩個類的全部虛函數:markdown

  • 公共接口函數類,具體服務實現
  • BBinder:IBinder抽象類,提供onTransact(),用於跨進程調用。輸入就是binder_tansaction_data結構上的數據包,case-by-case解析data中的code值和函數參數,分發給具體接口函數的實現。

5.1.2在client端表述--Binder引用

Client中會有代理類實現這兩個類:cookie

  • 公共接口函數類,函數調用封裝爲binder_transaction_data數據包
  • BpBinder:IBinder抽象類,提供transact(),用於跨進程調用. 

5.2 Binder 在傳輸數據中的表述

Binder傳輸結構爲flat_binder_object 網絡

5.2.1 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實體時有效,由於是用於驅動在內核中建立Binder實體節點,
內容暫不關心了就。
union{
  void * binder;
 signed long handle;
};
void * binder;
指向Binder實體在Server進程中的內存地址,傳遞Binder實體時使用。
signed long handle;
存放Binder在進程中的引用號,傳遞Binder引用時使用。
void * cookie; 只對Binder實體有效,存放Binder相關附加信息

5.2.2 驅動對flat_binder_object的操做

不管是Binder實體仍是引用都從屬某個進程,因此不能透明的在進程間傳遞,必須通過驅動「翻譯」。

binder類型 在發送方的操做 在接收方的操做
BINDER_TYPE_BINDER BINDER_TYPE_WEAK_BINDER 只有實體所在的進程才能發送該類型的Binder。第一次發送時,驅動爲其在內核中建立節點,並保存binder,cookie,flag域 若是是第一次接收該Binder則建立實體在內核中的引用;將handle域替換爲新建的引用號;將type域替換爲INDER_TYPE_(WEAK_)HANDLE
BINDER_TYPE_HANDLE BINDER_TYPE_WEAK_HANDLE 驅動根據handle域提供的引用號查找binder在內核中的引用。若是找不到,則不合法。 1.若是收到的binder實體位於接收進程:將ptr域替換爲保存在節點中的binder值,cookie值替換,type替換爲BINDER_TYPE_(WEAK_)BINDER   
2.若是收到的Binder實體不在接收進程中:若是第一次接收則建立實體在內核中的引用;將handle域替換爲新建的引用號
BINDER_TYPE_FD

5.3 Binder 在驅動中的表述

驅動是Binder通訊的核心:

  • 全部Binder實體及其在各個進程中的引用都登記在驅動中
  • 驅動記錄Binder引用與實體間多對一的關係
  • 爲引用找到對應實體,爲實體建立或者查找對應引用
  • 記錄Binder的歸屬進程
  • 管理Binder引用,建立or銷燬Binder實體

5.3.1 Binder實體在驅動中的表述

Binder實體在驅動中,稱爲「節點」,由binder_node 結構表示。根據傳輸數據中的flat_binder_object,構建binder_node節點。

binder_node結構
屬性 含義
int debug_id; 用於調試
struct binder_work work;
union{
struct rb_node rb_node;
struct hlist_node dead_node;
};
內核爲每一個進程維護一顆紅黑樹,存放此進程的全部binder實體的用戶空間指針(ptr).rb_node即爲binder_node在紅黑樹中的形式。
待銷燬節點,待切斷全部引用後,完全銷燬。
struct binder_proc *proc; 該節點所屬進程
struct hlist_head refs; 該節點全部引用隊列
int internal_strong_refs; 強指針計數器
int local_strong_refs; 驅動爲傳輸中的Binder設置的強引用計數
int local_weak_refs; 驅動爲傳輸中的Binder設置的弱引用計數
void __user * ptr; 指向用戶空間內存地址,來自flat_binder_object的binder成員
void __user *cookie;
unsigned pending_strong_ref;
unsigned has_strong_ref;
unsigned has_weak_ref;
unsigned pending_weak_ref;
引用計數相關
unsigned has_async_transaction; 表面該節點在to-do隊列中有異步交互還沒有完成。to-do隊列是驅動爲接收進程或線程開闢的,用於暫存發往接收端的數據包。
若是to-do中有還沒有完成的異步交互,則將新到的異步交互,存放在async隊列中。爲同步交互讓路,避免長時間阻塞發送端
struct list_head aysnc_todo; 異步交互等待隊列;分流發往本節點的異步交互包
unsigned accept_fds; 文件binder相關,略
int min_priority; 設置處理請求的線程的最低優先級,值來自於flat_binder_object中的flags成員
區別在哪?

5.3.2 Binder引用在驅動中的表述

表述爲binder_ref,根據傳輸數據中的flat_binder_object構建。

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實體銷燬時可以收到來自驅動的提醒。該域不爲空代表用戶訂閱了對應實體銷燬的‘噩耗’。

5.3.3 小結:

Binder實體會有不少引用,這些引用分佈在不一樣的進程中。

  • 每一個進程使用紅黑樹存放實體
  • 每一個進程使用紅黑樹存放引用

Binder引用能夠經過兩個鍵值索引:

  • 實體在內核中的地址:

驅動建立於內核中的binder_node結構地址,非實體在用戶進程中的內存地址。內核地址是惟一的,而用戶進程地址可能會重合

  • 引用號:

驅動爲引用分配的32位標識,Binder引用在用戶進程中的句柄,單個進程內是惟一的。經過引用號在紅黑樹中找到binder_ref,經過ref的node域找到Binder實體相關信息。

6. Binder內存映射和接收緩存區管理

  • 傳統IPC數據拷貝:用戶空間-->內核空間-->用戶空間
  • Binder數據拷貝:Client用戶空間--> 內核空間:Server用戶空間
  • Binder內存映射mmap()方法:
    • 接收方開闢緩存區,mmap(),返回內存映射在用戶空間地址
    • 映射類型爲只讀,用戶不能直接訪問緩存區,緩存區由驅動管理
    • 惟有binder_transaction_data.data.buffer會進入緩存區。其他部分(理解爲消息頭)依然須要接收方提供。
  • Binder驅動職責:
    • 驅動爲接收方分擔了最爲繁瑣的任務:分配/釋放大小不等,難以預測的有效負荷緩存區。接收方只需存放大小固定,空間可預測的消息頭便可。經過映射,省略了在內核中暫存。

7 Binder接收線程管理(略)

Binder會預先建立一堆線程,這些線程會阻塞在等待隊列上。一旦有數據請求,驅動會從隊列中喚醒一個線程來處理。
Binder如何管理線程:

  • 應用程序經過BINDER_SET_MAX_THREADS,告訴驅動最大線程數
  • 之後每一個線程建立,進入/退出循環都會告知驅動,以便驅動記錄當前線程池狀態(BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP)
  • 其餘線程利用率優化

8 數據包接收隊列與等待隊列管理

1.隊列用以緩解請求與處理的「供需矛盾」:

  • 接收隊列,也叫to-do隊列,存放數據包,分爲:進程全局隊列& 線程私有接收隊列
  • 等待隊列,等待to-do處理後的數據,分爲:進程全局等待隊列 & 線程私有等待隊列

2.數據包進入進程or線程to-do隊列規則:

  • 規則1:Client發送給Server的請求數據包進入全局to-do隊列。(除非發送線程T1與Server的線程T2有交互,且T1正等待讀取返回包)
  • 規則2:同步請求的返回數據包,送入發送請求的線程的私有to-do隊列

3.驅動遞交同步交互和異步交互規則:

爲了減小異步交互對同步發送端的阻塞。異步要爲同步讓步。即一旦to-do隊列中有異步在處理,新提交的異步只能特製的async_todo隊列。而新提交的同步,依然能夠進入to-do隊列

9 總結

性能:單次拷貝<br />	安全性:PID/UID可控<br />	易用性:面向對象設計
複製代碼
相關文章
相關標籤/搜索