Android跨進程通信IPC之6——Binder框架

Android跨進程通信IPC整體內(nèi)容如下

本文的主要內(nèi)容如下:

  • 1 Android整體架構(gòu)
  • 2 IPC原理
  • 3 Binder簡介
  • 4 Binder通信機制
  • 5 Binder協(xié)議

一、Android整體架構(gòu)

(一) Android的整體架構(gòu)

為了讓大家更好的理解Binder機制,我們先來看下Android的整體架構(gòu)。因為這樣大家就知道在Android架構(gòu)中Binder出于什么地位。
用一下官網(wǎng)上的圖片


Android架構(gòu).png

從下往上依次為:

  • 內(nèi)核層:Linux內(nèi)核和各類硬件設(shè)備的驅(qū)動,這里需要注意的是,Binder IPC驅(qū)動也是這一層的實現(xiàn),比較特殊。
  • 硬件抽象層:封裝"內(nèi)核層"硬件驅(qū)動,提供可供"系統(tǒng)服務(wù)層"調(diào)用的統(tǒng)一硬件接口
  • 系統(tǒng)服務(wù)層:提供核心服務(wù),并且提供可供"應(yīng)用程序框架層"調(diào)用的接口
  • Binder IPC 層:作為"系統(tǒng)服務(wù)層"與"應(yīng)用程序框架層"的IPC橋梁,相互傳遞接口調(diào)用的數(shù)據(jù),實現(xiàn)跨進程的通信。
  • 應(yīng)用程序框架層:這一層可以理解為Android SDK,提供四大組件,View繪制等平時開發(fā)中用到的基礎(chǔ)部件

(二) Android的架構(gòu)解析

在一個大的項目里面,分層是非常重要的,處于最底層的接口最具有"通用性",接口顆粒度最細,越往上層通用性降低。理論上來說上面每一層都可以"開放"給開發(fā)者調(diào)用,例如開發(fā)者可以直接調(diào)用硬件抽象層的接口去操作硬件,或者直接調(diào)用系統(tǒng)服務(wù)層中的接口去直接操作系統(tǒng)服務(wù),甚至像Windows開發(fā)一樣,開發(fā)者可以在內(nèi)核層寫程序,運行在內(nèi)核中。不過開放帶來的問題就是開發(fā)者權(quán)利太大,對于系統(tǒng)的穩(wěn)定性是沒有任何好處的,一個病毒制作者搞一個內(nèi)核層的病毒出來,系統(tǒng)可能就永遠起不來。所以谷歌的做法是將開發(fā)者的權(quán)利收攏到"應(yīng)用程序框架層",開發(fā)者只能調(diào)用這一層的接口。

在上面的層次中,內(nèi)核層與硬件抽象層均用C/C++實現(xiàn),系統(tǒng)服務(wù)層是以Java實現(xiàn),硬件抽象層編譯為so文件,以JNI的形式供系統(tǒng)服務(wù)層使用。系統(tǒng)服務(wù)層中的服務(wù)隨著系統(tǒng)啟動而啟動,只要不關(guān)機,就會一直運行。這些服務(wù)干什么事情?其實很簡單,就是完成一個手機有的核心功能如短信的收發(fā)管、電話的接聽、掛斷以及應(yīng)用程序的包管理、Activity的管理等等。每一個服務(wù)均運行在一個獨立的進程中,因此是以Java實現(xiàn),所以本質(zhì)上來說是運行在一個獨立進程的Dalvik虛擬機中。那么問題來了,開發(fā)者的App也運行在一個獨立的進程空間中,如果調(diào)用到系統(tǒng)的服務(wù)層中的接口?答案是IPC(Inter-Process Communication),進程間通訊是和RPC(Remote Procedure Call)不一樣的,實現(xiàn)原理也不一樣。每一個系統(tǒng)服務(wù)在應(yīng)用框架層都有一個Manager與之對應(yīng),方便開發(fā)者調(diào)用其相關(guān)功能,具體關(guān)系如下:

調(diào)用關(guān)系.png

(三)、總結(jié)一下:

  • Android 從下而上分了內(nèi)核層、硬件抽象層、系統(tǒng)服務(wù)層、Binder IPC 層、應(yīng)用程序框架層
  • Android 中"應(yīng)用程序框架層"以 SDK 的形式開放給開發(fā)者使用,"系統(tǒng)服務(wù)層" 中的核心服務(wù)隨系統(tǒng)啟動而運行,通過應(yīng)用層序框架層提供的 Manager 實時為應(yīng)用程序提供服務(wù)調(diào)用。系統(tǒng)服務(wù)層中每一個服務(wù)運行在自己獨立的進程空間中,應(yīng)用程序框架層中的 Manager 通過 Binder IPC 的方式調(diào)用系統(tǒng)服務(wù)層中的服務(wù)。

二、IPC原理

從進程的角度來看IPC機制

image.png

每個Android進程,只能運行在自己的進程所擁有的虛擬地址空間,如果是32位的系統(tǒng),對應(yīng)一個4GB的虛擬地址空間,其中3GB是用戶空,1GB是內(nèi)核空間,而內(nèi)核空間的大小是可以通過參數(shù)配置的。對于用戶空間,不同進程之間彼此是不能共享的,而內(nèi)核空間確實可以共享的。Client進程與Server進程通信,恰恰是利用進程間可共享的內(nèi)核內(nèi)空間來完成底層通信工作的,Client端與Server端進程往往采用ioctl等方法跟內(nèi)核空間的驅(qū)動進行。

三、Binder綜述

(一) Binder簡介

1、Binder的由來

簡單的說,Binder是Android平臺上的一種跨進程通信技術(shù)。該技術(shù)最早并不是谷歌公司提出的,它前身是Be Inc公司開發(fā)的OpenBinder,而且在Palm中也有應(yīng)用。后來OpenBinder的作者Dianne Hackborn加入了谷歌公司,并負責(zé)Android平臺開發(fā)的工作,所以把這項技術(shù)也帶進了Android。

我們知道,在Android的應(yīng)用層次上,基本上已經(jīng)沒有了進程的概念了,然后在具體的實現(xiàn)層次上,它畢竟還是要構(gòu)建一個個進程之上的。實際上,在Android內(nèi)部,哪些支持應(yīng)用的組件往往會身處不同的繼承,那么應(yīng)用的底層必然會涉及大批的跨進程通信,為了保證了通信的高效性,Android提供了Binder機制。

2、什么是Binder

讓我們從四個維度來看Binder,這樣會讓大家對理解Binder機制更有幫助

  • 1 從來類的角度來說,Binder就是Android的一個類,它繼承了IBinder接口
  • 2 從IPC的角度來說,Binder是Android中的一個中的一種跨進程通信方式,Binder還可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動是/dev/binder,該通信方式在Linux中沒有(由于耦合性太強,而Linux沒有接納)
  • 3 從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager等)和相應(yīng)的ManagerService的橋梁
  • 4 從Android應(yīng)用層的角度來說,Binder是客戶端和服務(wù)端進行通信的媒介,當你bindService的時候,服務(wù)端會返回一個包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)。

3、Binder機制的意義

Binder機制具有兩層含義:

  • 1 是一種跨進程通信的方式(IPC)
  • 2 是一種遠程過程調(diào)用方式(PRC)

而從實現(xiàn)的角度來說,Binder核心被實現(xiàn)成一個Linux驅(qū)動程序,并運行于內(nèi)核態(tài)。這樣它才能具有強大的跨進程訪問能力。

4、和傳統(tǒng)IPC機制相比,谷歌為什么采用Binder

我們先看下Linux中的IPC通信機制:

  • 1、傳統(tǒng)IPC:匿名管道(PIPE)、信號(signal)、有名管道(FIFO)
  • 2、AT&T Unix:共享內(nèi)存,信號量,消息隊列
  • 3、BSD Unix:Socket

關(guān)于這塊如果大家不了解,請看前面的文章。

雖然Android繼承Linux內(nèi)核,但是Linux與Android通信機制是不同的。Android中有大量的C/S(Client/Server)應(yīng)用方式,這就要求Android內(nèi)部提供IPC方法,而如果采用Linux所支持的進程通信方式有兩個問題:性能和安全性。那

  • 性能:目前Linux支持的IPC包括傳統(tǒng)的管道,System V IPC(包括消息隊列/共享內(nèi)存/信號量)以及socket,但是只有socket支持Client/Server的通信方式,由于socket是一套通用當初網(wǎng)絡(luò)通信方式,其效率低下,且消耗比較大(socket建立連接過程和中斷連接過程都有一定的開銷),明顯在手機上不適合大面積使用socket。而消息隊列和管道采用"存儲-轉(zhuǎn)發(fā)" 方式,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的緩存區(qū)中,然后再從內(nèi)核緩存中拷貝到接收方緩存中,至少有兩次拷貝過程。共享內(nèi)存雖然無需拷貝,但控制復(fù)雜,難以使用。
  • 安全性:在安全性方面,Android作為一個開放式,擁有眾多開發(fā)者的平臺,應(yīng)用程序的來源廣泛,確保智能終端的安全是非常重要的。終端用戶不希望從網(wǎng)上下載的程序在不知情的情況下偷窺隱私數(shù)據(jù),連接無線網(wǎng)絡(luò),長期操作底層設(shè)備導(dǎo)致電池很快耗盡的情況。傳統(tǒng)IPC沒有任何安全措施,完全依賴上層協(xié)議來去報。首先傳統(tǒng)IPC的接受方無法獲取對方進程可靠的UID/PID(用戶ID/進程ID),從而無法鑒別對方身份。Android為每個安裝好的應(yīng)用程序分配了自己的UID,故進程的UID是鑒別進程的身份的重要標志。使用傳統(tǒng)IPC只能由用戶在數(shù)據(jù)包里填入UID/PID,但這樣不可靠,容易被惡意程序利用。可靠的身份標記只由IPC機制本身在內(nèi)核中添加。其次傳統(tǒng)IPC訪問接入點是開放的,無法建立私有通道。比如命名管道、system V的鍵值,socket的ip地址或者文件名都是開放的,只要知道這些接入點的程序都可以對端建立連接,不管怎樣都無法阻止惡意程序通過接收方地址獲得連接。

基于以上原因,Android需要建立一套新的IPC機制來滿足系統(tǒng)對通信方式,傳輸性能和安全性的要求,所以就有了Binder。Binder基于Client/Server通信模式,傳輸過程只需要一次拷貝,為發(fā)送發(fā)添加UID/PID身份,雞翅實名Binder也支持匿名Binder,安全性高。下圖為Binder通信過程示例:

Binder通信過程.png
  • 相比于傳統(tǒng)的跨進程通信手段,通信雙方必須要處理線程同步,內(nèi)存管理等問題,工作量大,而且問題多,就像我們前面介紹的傳統(tǒng)IPC 命名管道(FIFO) 信號量(semaphore) 消息隊列已經(jīng)從Android中去掉了,同其他IPC相比,Socket是一種比較成熟的通信手段了,同步控制也很容易實現(xiàn)。Socket用于網(wǎng)絡(luò)通信非常合適,但是用于進程間通信就效率很低。
  • Android在架構(gòu)上一直希望模糊進程的概念,取而代之以組件的概念。應(yīng)用也不需要關(guān)心組件存放的位置、組件運行在那個進程中、組件的生命周期等問題。隨時隨地的,只要擁有Binder對象,就能使用組件的功能。Binder就像一張網(wǎng),將整個系統(tǒng)的組件,跨進程和線程的組織在一起。

Binder是整個系統(tǒng)的運行的中樞。Android在進程間傳遞數(shù)據(jù)使用共享內(nèi)存的方式,這樣數(shù)據(jù)只需要復(fù)制一次就能從一個進程到達另一個進城了(前面文章說了,一般IPC都需要兩步,第一步用戶進程復(fù)制到內(nèi)核,第二步再從內(nèi)核復(fù)制到服務(wù)進程。)

PS: 整個Androdi系統(tǒng)架構(gòu)中,雖然大量采用了Binder機制作為IPC(進程間通信)方式,但是也存在部分其他的IPC方式,比如Zygote通信就是采用socket。

5、Binder在Service服務(wù)中的作用

在Android中,有很多Service都是通過Binder來通信的,比如MediaService名下的眾多Service:

  • AudioFlinger音頻核心服務(wù)
  • AudioPolicyService:音頻策略相關(guān)的重要服務(wù)
  • MediaPlayerService:多媒體系統(tǒng)中的重要服務(wù)
  • CarmeraService:有關(guān)攝像/照相的重要服務(wù)

那具體是怎么應(yīng)用或者通信機制是什么那?那就讓我們來詳細了解下

(二)、總結(jié)

Android Binder 是在OpenBinder上定制實現(xiàn)的。原先的OpenBinder 框架現(xiàn)在已經(jīng)不再繼續(xù)開發(fā),所以也可以說Android讓原先的OpenBinder得到了重生。Binder是Android上大量使用的IPC(Inter-process communication,進程間通訊)機制。無論是應(yīng)用程序?qū)ο到y(tǒng)服務(wù)的請求,還是應(yīng)用程序自身提供對外服務(wù),都需要使用Binder。

整體架構(gòu)

整體架構(gòu).png

從圖中可以看出,Binder的實現(xiàn)分為這幾層,按照大的框架理解是

  • framewor層
    • java 層
    • jni 層
    • native/ C++層
  • linux驅(qū)動層 c語言

讓我們來仔細研究下。

  • 其中Linux驅(qū)動層位于Linux內(nèi)核中,它提供了最底層的數(shù)據(jù)傳遞,對象標示,線程管理,通過調(diào)用過程控制等功能。驅(qū)動層其實是Binder機制的核心。
  • Framework層以Linux驅(qū)動層為基礎(chǔ),提供了應(yīng)用開發(fā)的基礎(chǔ)設(shè)施。Framework層既包含了C++部分的實現(xiàn),也包含了Java基礎(chǔ)部分的實現(xiàn)。為了能將C++ 的實現(xiàn)復(fù)用到Java端,中間通過JNI進行銜接。

開發(fā)者可以在Framework之上利用Binder提供的機制來進行具體的業(yè)務(wù)邏輯開發(fā)。其實不僅僅是第三方開發(fā)者,Android系統(tǒng)中本身也包含很多系統(tǒng)服務(wù)都是基于Binder框架開發(fā)的。其中Binder框架是典型的C/S架構(gòu)。所以在后面中, 我們把服務(wù)的請求方稱為Client,服務(wù)的實現(xiàn)方稱之Server。Clinet對于Server的請求會經(jīng)由Binder驅(qū)動框架由上至下傳遞到內(nèi)核的Binder驅(qū)動中,請求中包含了Client將要調(diào)用的命令和參數(shù)。請求到了Binder驅(qū)動以后,在確定了服務(wù)的提供方之后,再講從下至上將請求傳遞給具體的服務(wù)。如下圖所示:

Binder調(diào)用.png

如果大家對網(wǎng)絡(luò)協(xié)議有所了解的話,其實會發(fā)現(xiàn)整個數(shù)據(jù)的傳遞過程和網(wǎng)絡(luò)協(xié)議如此的相似。

四、Binder通信機制

Android內(nèi)部采用C/S架構(gòu)。而Binder通信也是采用C/S架構(gòu)。那我們來看下Binder在C/S的中的流程。

(一) Binder在C/S中的流程

如下圖


Binder流程.png

具體流程如下:

  • 1、相應(yīng)的Service需要注冊服務(wù)。Service作為很多Service的擁有者,當它想向Client提供服務(wù)時,得先去Service Manager(以后縮寫成SM)那兒注冊自己的服務(wù)。Server可以向SM注冊一個或者多個服務(wù)。
  • 2、Client申請服務(wù)。Client作為Service的使用者,當他想使用服務(wù)時,得向SM申請自己所需要的服務(wù)。Client可以申請一個或者多個服務(wù)。
  • 3、當Client申請服務(wù)成功后,Client就可以使用服務(wù)了。

SM一方面管理Server所提供的服務(wù),同時又響應(yīng)Client的請求并為之分配響應(yīng)的服務(wù)。扮演角色相當于月老,兩邊前線。這種通信方式的好處是:一方面,service和Client請求便于管理,另一方面在應(yīng)用程序開發(fā)時,只需要為Client建立到Server的連接即可,這樣只需要花很少的時間成本去實現(xiàn)Server的相應(yīng)功能。那么Binder與這個通信有什么關(guān)系?其實三者的通信方式就是Binder機制(比如Server向SM注冊服務(wù),使用Binder通信;Client申請請求也是Binder通信。)

PS:注意這里的ServiceManager是指Nativie層的ServiceManager(C++),并非是framework層的ServiceManager(Java)。ServiceManager是整個Binder通信機制的大管家,是Android進程間通信機制的守護進程。

(二)Binder通信整體框架

這里先提前和大家說下,后面我們會不斷的提及兩個概念,一個是Server,還有一個是Service,我這里先強調(diào)下,Server是Server,Service是Service,大家不要混淆,一個Server下面可能有很多Service,但是一個Servcie也只能隸屬于一個Server。下面我們將從三個角度來看Binder框架,這樣

1、從內(nèi)核和用戶空間的角度來看

Binder通信模型如下:


Binder通信整體框架.png

我們可以發(fā)現(xiàn):

  • 1、Client和Server是存在于用戶空間
  • 2、Client和Server通信實現(xiàn)是由Binder驅(qū)動在內(nèi)核的實現(xiàn)
  • 3、SM作為守護進程,處理客戶端請求,管理所有服務(wù)

如果大家不好理解上面的意思,我們可以把SM理解成為DNS服務(wù)器,那么Binder Driver就相當于路由的功能。這里就涉及到Client和Server是如何通信的問題。

2、從Android的層級的角度

如下圖:(注意圖片的右邊)

Binder原理.png

圖中Client/Server/ServiceManager之間的相互通信都是基于Binder機制。圖中Clinet/Server/ServiceManager之間交互都是虛線表示,是由于他們彼此之間不直接交互,都是通過Binder驅(qū)動進行交互,從而實現(xiàn)IPC通信方式。其中Binder驅(qū)動位于內(nèi)核空間,Client/Server/ServiceManager可以看做是Android平臺的基礎(chǔ)架構(gòu)。而Client和Server是Android的應(yīng)用層,開發(fā)人員只需要自定義實現(xiàn)client、Server端,借助Android的基本平臺架構(gòu)就可以直接進行IPC通信。

3、從Binder的架構(gòu)角度來看

如下圖:


Binder架構(gòu).png

同樣

Binder IPC 屬于 C/S 結(jié)構(gòu),Client 部分是用戶代碼,用戶代碼最終會調(diào)用 Binder Driver 的 transact 接口,Binder Driver 會調(diào)用 Server,這里的 Server 與 service 不同,可以理解為 Service 中 onBind 返回的 Binder 對象,請注意區(qū)分下。

Client端:用戶需要實現(xiàn)的代碼,如 AIDL 自動生成的接口類
Binder Driver:在內(nèi)核層實現(xiàn)的 Driver
Server端:這個 Server 就是 Service 中 onBind 返回的 IBinder 對象
需要注意的是,上面綠色的色塊部分都是屬于用戶需要實現(xiàn)的部分,而藍色部分是系統(tǒng)去實現(xiàn)了。也就是說 Binder Driver 這塊并不需要知道,Server 中會開啟一個線程池去處理客戶端調(diào)用。為什么要用線程池而不是一個單線程隊列呢?試想一下,如果用單線程隊列,則會有任務(wù)積壓,多個客戶端同時調(diào)用一個服務(wù)的時候就會有來不及響應(yīng)的情況發(fā)生,這是絕對不允許的。

對于調(diào)用 Binder Driver 中的 transact 接口,客戶端可以手動調(diào)用,也可以通過 AIDL 的方式生成的代理類來調(diào)用,服務(wù)端可以繼承 Binder 對象,也可以繼承 AIDL 生成的接口類的 Stub 對象。這些細節(jié)下面繼續(xù)接著說,這里暫時不展開。

切記,這里 Server 的實現(xiàn)是線程池的方式,而不是單線程隊列的方式,區(qū)別在于,單線程隊列的話,Server 的代碼是線程安全的,線程池的話,Server 的代碼則不是線程安全的,需要開發(fā)者自己做好多線程同步。

(三)、Binder流程

1、Server向SM注冊服務(wù)
注冊.png
  • 1、首先 XXServer(XXX代表某個)在自己的進程中向Binder驅(qū)動申請創(chuàng)建一個XXXService的Binder實體。
  • 2、Binder驅(qū)動為這個XXXService創(chuàng)建位于內(nèi)核中的Binder實體節(jié)點以及Binder的引用,注意,是將名字和新建的引用打包傳遞給SM(實體沒有傳給SM),通知SM注冊一個名叫XXX的Service。
  • 3、SM收到數(shù)據(jù)包后,從中取出XXXService名字和引用,填入一張查找表中
  • 4、此時,如果有Client向SM發(fā)送申請服務(wù)XXXService的請求,那么SM就可以查找表中該Service的Binder引用,并把BInder引用(XXXBpBinder返回給Client)

在進一步了解Binder通信機制之前,我們先弄清楚幾個概念。

  • 引用和實體。這里,對于一個用于通信的實體(可以理解為真實空間的Object),可以額有多個該實體的引用(沒有真實空間,可以理解成實體的一個鏈接,操作引用就可以操作對應(yīng)鏈接上的實體)。如果一個進程持有某個實體,其他進程也想操作該實體,最高效的做法是去獲取該實體的引用,再去操作這個引用。
  • 有些資源也把實體成本本地對象,應(yīng)用稱為遠程對象。所以也可以這么理解:應(yīng)用是從本地進程發(fā)送給其他進程操作實體之用,所以有本地和遠程對象之名。

為了大家在后面更好的理解,這里補充幾個概念

  • Binder實體對 :Binder實體對象就是Binder實體對象就是Binder服務(wù)的提供者。一個提供Binder服務(wù)的類必須繼承BBinder類,因此,有時為了強調(diào)對象類型,也用"BBinder對象"來代替"Binder實體對象"。
  • Binder引用對象 :Binder引用對象是Binder實體對象在客戶進程的代表,每個引用對象的類型都是BpBiner類,同樣可以用名稱"BpBinder對象"來代替"Binder引用對象"。
  • Binder代理對象 :代理對象也成為接口對象,它主要是為了客戶端的上層應(yīng)用提供接口服務(wù),從IInterface類派生。它實現(xiàn)了Binder服務(wù)的函數(shù)接口,當然只是一個轉(zhuǎn)調(diào)的空殼。通過代理對象,應(yīng)用能像使用本地對象一樣使用遠端實體對象提供服務(wù)。
  • IBiner對象 :BBinder和BpBinder類是從IBinder類中繼承來。在很多場合,不需要刻意地去區(qū)分實體對象和引用對象,這時候也可以統(tǒng)一使用"IBinder對象"來統(tǒng)一稱呼他們。
  • Binder代理對象 主要是和應(yīng)用程序打交道,將Binder代理對象和Binder引用對應(yīng)(BpBinder對象)分開的好處是代理對象可以有很多實例,但是它們包含的是同一個引用對象,這樣方便了應(yīng)用層的使用。如下圖所示:
Binder對象關(guān)系圖.png

這樣應(yīng)用層可以直接拋開接口對象直接使用Binder的引用對象,但是這樣開發(fā)的程序兼容性不好。也正是客戶端將引用對象和代理對象分離,Android才能用一套架構(gòu)來同時為Java和native層提供Binder服務(wù)。隔離后,Binder底層不需要關(guān)系上層的實現(xiàn)細節(jié),只需要和Binder實體對象和引用對象進行交互。

PS:BpBinder(Binder引用對象,在客戶端)和BBinder(Binder實體,在服務(wù)端)都是Android中Binder通信相關(guān)的代表,它們都是從IBiner類中派生而來(BpBinder和BBinder在Native層,不在framework層),關(guān)系圖如下:

image.png

client端:BpBinder通過調(diào)用transact()來發(fā)送事物請求
server端:BBinder通過onTransact()會接受到相應(yīng)的事物

這時候再來看下這個圖,然后大家思考一下,就會明白很多事情。


Binder原理.png
2、如何獲得一個SM的遠程接口
獲取.png

如果你足夠細心,你會發(fā)現(xiàn)這里有一個問題:

SM和Server都是進程,Server向SM注冊Binder需要進程間通信,當前實現(xiàn)的是進程間通信,卻又用到進程間通信。有點暈是不,就好像先有雞還是先有蛋這個問題。

其實Binder是這么解決這個問題的:

  • 針對Binder的通信機制,Server端擁有的是Binder的實體(BBinder);Client擁有的是Binder的引用(BpBinder)。
  • 如果把SM看做Server端,讓它在Binder驅(qū)動一起運行起來時就有自己的實體(BBinder)(代碼中設(shè)置ServiceManager的Binder其handle的值恒為0)。這個Binder實體沒有名字也不需要注冊,所有的Client都認為handle值為0的binder引用(BpBinder)是用來與SM通信的。那么這個問題就解決了。
  • 但是問題又來了,Client和Server中handle的值為0(值為0的引用是專門與SM通信用的),還不行,還需要讓SM的handle值為0的實體(BBinder)為0才算大功告成。怎么實現(xiàn)的? 當一個進程調(diào)用Binder驅(qū)動時,使用** "BINDER_SET_CONTEXXT_MGR" ** 命名(在binder_ioctl中)將自己注冊成SM時,Binder驅(qū)動會自動為她創(chuàng)建Binder實體。這個Binder的引用對所有Client都為0。
3、Client從SM中獲得Service的遠程接口

Server向SM注冊了Binder實體及其名字后,Client就可以Service的名字在SM在查找表中獲得了該Binder的引用(BpBinder)了。Client也利用了保留的handle值為0的引用向SM請求訪問某個Service:當申請訪問XXXService的引用。SM就會從請求數(shù)據(jù)包中獲得XXXService的名字,在查找表中找到名字對應(yīng)的條目,取出Binder的引用打包回復(fù)給Client。然后,Client就可以利用XXXService的引用使用XXXService的服務(wù)了。如果有更多的Client請求該Service,系統(tǒng)中就會有更多的Client獲得這個引用。

如下圖


獲取遠程接口.png
4、建立C/S連接后

首先要明白一個事情:

Client要擁有自己的自己的Binder實體,以及Server的Binder的應(yīng)用;Server有用自己的Binder的實體,以及Client的Binder引用。

我們也可以按照網(wǎng)絡(luò)請求的方式來分析:

  • 從Client向Server發(fā)送數(shù)據(jù):Client為發(fā)送方,擁有Binder實體;Server為接收方,擁有Binder引用。
  • 從Server向Client發(fā)送數(shù)據(jù):Server為發(fā)送方,擁有Binder實體:Client為接收方,擁有Binder引用。

其實,在我們建立C/S連接后,無需考慮誰是Client,誰是Server。只要理清誰是發(fā)送方,誰是接收方,就能知道Binder的實體和應(yīng)用在那邊。

那我們看下建立C/S連接后的,具體流程,如下圖:

建立C/S連接后的流程.png

那我們說下具體的流程:

  • 第一步,發(fā)送方通過Binder實體請求發(fā)送操作
  • 第二步,Binder驅(qū)動會處理這個操作請求,把發(fā)送方的數(shù)據(jù)放入寫緩存(binder_write_read.write_buffer)(對于接受方來說為讀緩存區(qū)),并把read_size(接收方讀數(shù)據(jù))置為數(shù)據(jù)大小。
  • 第三步,接收方之前一直在阻塞狀態(tài)中,當寫緩存又數(shù)據(jù),則會讀取數(shù)據(jù),執(zhí)行命令操作
  • 第四步,接收方執(zhí)行完后,會把返回結(jié)果同樣采用binder_transaction_data結(jié)構(gòu)體封裝,寫入緩沖區(qū)(對于發(fā)送方,為讀緩沖區(qū))

(四) 匿名Binder

在Android中Binder還可以建立點對點的私有通道,匿名Binder就是這種方式。在Binder通信中,并不是所有通信的Binder實體都需要注冊給SM的,Server可以通過已建立的實體Binder連接將創(chuàng)建的Binder實體傳給Client。而這個Binder沒有向SM注冊名字。這樣Server和Client通信就有很高的隱私性和安全性。

如下圖:

匿名Binder.png

五、 Binder的層次

從代碼上看,Binder設(shè)計的類可以分成4個層級,如下圖所示

Binder的層次圖.png
  • 最上層的是位于Framewok中的各種Binder服務(wù)類和它們的接口類。這一層的類非常多,比如常見的ActivityManagerService(縮寫叫AMS)、WindowManagerService(縮寫叫WMS)、PackageManagerService(縮寫是PMS)等,它們?yōu)閼?yīng)用程序提供了各種各樣的服務(wù)。
  • 中間則分為為兩層,上面是用于服務(wù)類和接口開發(fā)的基礎(chǔ),比如IBinder、BBinder、BpBinder等。下層是和驅(qū)動交互的IPCThreadState和ProcessState類。
  • 這里刻意把中間的libbinder中的類劃分為兩個層次的原因,是在這4層中,第一層的和第二層聯(lián)系很緊密,第二層中的 各種Binder類用來支撐服務(wù)類和代理類的開發(fā)。但是第三層的IPCThread和第四層之間耦合得很厲害,單獨理解IPCThread或者是驅(qū)動都是一件很難的事,必須把它們結(jié)合起來理解,這一點正是Binder架構(gòu)被人詬病的地方,驅(qū)動和應(yīng)用層之間過于耦合,違反了Linux驅(qū)動設(shè)計的原則,因此,主流的Linux并不愿意接納Binder。

下面我們就來詳細的看來Binder

六、Binder協(xié)議

Biner協(xié)議格式基本是"命令+數(shù)據(jù)",使用ioctl(fd,cmd,arg)函數(shù)實現(xiàn)交互。命令由參數(shù)cmd承載,數(shù)據(jù)由參數(shù)arg,隨著cmd不同而不動。下表列了所有命令及其對應(yīng)的數(shù)據(jù):

命令 含義 參數(shù)(arg)
BINDER_WRITE_READ 該命令向Binder寫入或讀取數(shù)據(jù)。參數(shù)分為兩段:寫部分和讀部分。如果write_size不為0,就將write_buffer里的數(shù)據(jù)寫入Binder;如果read_ size不為0再從Binder中讀取數(shù)據(jù)存入read_buffer中。write_consumered和read_consumered表示操作完成時Binder驅(qū)動實際寫入或者讀出數(shù)據(jù)的個數(shù) struct binder_write_read{ singed long write_size;singed long write_consumed; unsigned long write_buffer; signed long read_size; signed long read_consumed; unsigned long read_buffer; } ;
BINDER_SET_MAX_THREADS 該命令告知Binder驅(qū)動接收方(通常是Server端)線程池中最大的線程數(shù)。由于Client是并發(fā)向Server端發(fā)送請求的,Server端必須開辟線程池為這些并發(fā)請求提供服務(wù) 。告知驅(qū)動線程池的最大值是為了讓驅(qū)動發(fā)現(xiàn)線程數(shù)達到該線程池的最大值是為了讓驅(qū)動發(fā)現(xiàn)線程數(shù)達到該值時,不要再命令接收端啟動先的線程。 int max_threads;
BINDER_SET_CONTEXT_MGR 當前進程注冊為SM。系統(tǒng)中只能存在一個SM,只要當前的SM沒有調(diào)用close()關(guān)閉,Binder驅(qū)動就不能有別的進程變成SM
BINDER_TREAD_EXIT 通知Binder驅(qū)動當前線程退出了。Binder會為所有參與的通信線程(包括Server線程池中的線程和Client發(fā)出的請求的線程) 建立相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。這些線程在退出時必須通知驅(qū)動釋放相應(yīng)的數(shù)據(jù)結(jié)構(gòu)
BINDER_VERSION 獲取Binder驅(qū)動的版本號

這其中最常用的命令是 BINDER_WRITE_READ。該命令的參數(shù)包括兩個部分:

  • 1、是向Binder寫入數(shù)據(jù)
  • 2、是向Binder讀出數(shù)據(jù)

驅(qū)動程序先處理寫部分再處理讀部分。這樣安排的好處是應(yīng)用程序可以很靈活的地處理命令的同步或者異步。例如若要發(fā)送異步命令可以只填入寫部分而將read_size設(shè)置為0,若要只從Binder獲得的數(shù)據(jù)可以將寫部分置空,即write_size置0。如果想要發(fā)送請求并同步等待返回數(shù)據(jù)可以將兩部分都置上。

(一)、BINDER_WRITE_READ 之寫操作

Binder寫操作的數(shù)據(jù)時格式同樣也是(命令+數(shù)據(jù))。這時候命令和數(shù)據(jù)都存放在binder_write_read結(jié)構(gòu)中的write_buffer域指向的內(nèi)存空間里,多條命令可以連續(xù)存放。數(shù)據(jù)緊接著存放在命令后面,格式根據(jù)命令不同而不同。下表列舉了Binder寫操作支持的命令:
我提供兩套,一套是圖片,方便手機用戶,一部分是文字,方便PC用戶


寫操作.png

上面圖片,下面是文字

命令 含義 參數(shù)(arg)
BC_TRANSACTION BC_REPLY BC_TRANSACTION用于Client向Server發(fā)送請求數(shù)據(jù);BC_REPLY用于Server向Client發(fā)送回復(fù)(應(yīng)答)數(shù)據(jù)。其后面緊接著一個binder_transaction_data結(jié)構(gòu)體表明要寫入的數(shù)據(jù)。 struct binder_transaction_data
BC_ACQUIRE_RESULT 暫未實現(xiàn)
BC_FREE_BUFFER 釋放一塊映射內(nèi)存。Binder接受方通過mmap()映射一塊較大的內(nèi)存空間,Binder驅(qū)動基于這片內(nèi)存采用最佳匹配算法實現(xiàn)接受數(shù)據(jù)緩存的動態(tài)分配和釋放,滿足并發(fā)請求對接受緩存區(qū)的需求。應(yīng)用程序處理完這篇數(shù)據(jù)后必須盡快使用費改命令釋放緩存區(qū),否則會因為緩存區(qū)耗盡而無法接受新數(shù)據(jù) 指向需要釋放的緩存區(qū)的指針;該指針位于收到的Binder數(shù)據(jù)包中
BC_INCREFS BC_ACQUIRE BC_RELEASE BC_DECREFS 這組命令增加或減少Binder的引用計數(shù),用以實現(xiàn)強指針或弱指針的功能 32位Binder引用號
BC_REGISTER_LOOPER BC_ENTER_LOOPER BC_EXIT_LOOPER 這組命令同BINDER_SET_MAX_THREADS 一并實現(xiàn)Binder驅(qū)動對接收方線程池的管理。BC_REGISTER_LOOPER通知驅(qū)動線程池中的一個線程已經(jīng)創(chuàng)建了;BC_ENTER_LOOPER通知該驅(qū)動線程已經(jīng)進入主循環(huán),可以接受數(shù)據(jù);BC_EXIT_LOOPER通知驅(qū)動該線程退出主循環(huán),不在接受數(shù)據(jù)。 -----
BC_REQUEST_DEATH_NOTIFICATION 獲得Binder引用的進程通過該命令要求驅(qū)動在Binder實體銷毀得到通知。雖說強指針可以確保只要有引用就不會銷毀實體,但這畢竟是個跨進程的引用,誰也無法保證實體由于所在的Server關(guān)閉Binder驅(qū)動或異常退出而消失,引用者能做的就是要求Server在此刻給出通知 uint32 *ptr;需要得到死亡的通知Binder引用 void **cookie:與死亡通知相關(guān)的信息,驅(qū)動會在發(fā)出死亡通知時返回給發(fā)出請求的進程。
BC_DEAD_BINDER 收到實體死亡通知書的進程在刪除引用后用本命令告知驅(qū)動 void * cookie

在這些命令中,最常用的h是BC_TRANSACTION/BC_REPLY命令對,Binder請求和應(yīng)答數(shù)據(jù)就是通過這對命令發(fā)送給接受方。這對命令所承載的數(shù)據(jù)包由結(jié)構(gòu)體struct binder_transaction_data定義。Binder交互有同步和異步之分。利用binder_transcation_data中的flag區(qū)域劃分。如果flag區(qū)域的TF_ONE_WAY位為1,則為異步交互,即client發(fā)送完請求交互即結(jié)束,Server端不再返回BC_REPLY數(shù)據(jù)包;否則Server會返回BC_REPLY數(shù)據(jù)包,Client端必須等待接受完數(shù)據(jù)包后才能完成一次交互。

(二)、BINDER_WRITE_READ:從Binder讀出數(shù)據(jù)

在Binder里讀出數(shù)據(jù)格式和向Binder中寫入數(shù)據(jù)格式一樣,采用(消息ID+數(shù)據(jù))形式,并且多條消息可以連續(xù)存放。下面列舉從Binder讀出命令及相應(yīng)的參數(shù)。
為了照顧手機端的朋友,先發(fā)圖片

Binder讀出數(shù)據(jù).png

Binder讀操作消息ID

消息 含義 參數(shù)(arg)
BR_ERROR 發(fā)生內(nèi)部錯誤(如內(nèi)存分配失敗) ----
BR_OK BR_NOOP 操作完成 ----
BR_SPAWN_LOOPER 該消息用于接受方線程池管理。當驅(qū)動發(fā)現(xiàn)接收方所有線程都處于忙碌狀態(tài)且線程池中的線程總數(shù)沒有超過BINDER_SET_MAX_THREADS設(shè)置的最大線程時,向接收方發(fā)送該命令要求創(chuàng)建更多的線程以備接受數(shù)據(jù)。 -----
BR_TRANSCATION BR_REPLY 這兩條消息分別對應(yīng)發(fā)送方的 BC_TRANSACTION 和BC_REPLY,表示當前接受的數(shù)據(jù)是請求還是回復(fù) binder_transaction_data
BR_ACQUIRE_RESULT BR_ATTEMPT_ACQUIRE BR_FINISHED 尚未實現(xiàn) -----
BR_DEAD_REPLY 交互過程中如果發(fā)現(xiàn)對方進程或線程已經(jīng)死亡則返回該消息 -----
BR_TRANSACTION_COMPLETE 發(fā)送方通過BC_TRRANSACTION或BC_REPLY發(fā)送完一個數(shù)據(jù)包后,都能收到該消息作為成功發(fā)送的反饋。這和BR_REPLY不一樣,是驅(qū)動告知發(fā)送方已經(jīng)發(fā)送成功,而不是Server端返回數(shù)據(jù)。所以不管同步還是異步交互接收方都能獲得本消息。 -----
BR_INCREFS BR_ACQUIRE BR_RFLEASE BR_DECREFS 這組消息用于管理強/弱指針的引用計數(shù)。只有提供Binder實體的進程才能收到這組消息 void *ptr : Binder實體在用戶空間中的指針 void **cookie:與該實體相關(guān)的附加數(shù)據(jù)
BR_DEAD_BINDER BR_CLEAR_DEATH_NOTIFICATION_DONE 向獲得Binder引用的進程發(fā)送Binder實體死亡通知書:收到死亡通知書的進程接下來會返回 BC_DEAD_BINDER_DONE 確認 void *cookie 在使用BC_REQUEST_DEATH_NOTIFICATION注冊死亡通知時的附加參數(shù)
BR_FAILED_REPLY 如果發(fā)送非法引用號則返回該消息 -----

和寫數(shù)據(jù)一樣,其中最重要的消息是BR_TRANSACTION或BR_REPLY,表明收到一個格式為binder_transaction_data的請求數(shù)據(jù)包(BR_TRANSACTION或返回數(shù)據(jù)包(BR_REPLY))

(三)、struct binder_transaction_data :收發(fā)數(shù)據(jù)包結(jié)構(gòu)

該結(jié)構(gòu)是Binder接收/發(fā)送數(shù)據(jù)包的標準格式,每個成員定義如下:
下圖是Binder

Binder數(shù)據(jù)包.png
成員 含義
union{ size_t handle; void *ptr;} target; 對于發(fā)送數(shù)據(jù)包的一方,該成員指明發(fā)送目的地。由于目的地是遠端,所以在這里填入的是對Binder實體的引用,存放在target.handle中。如前述,Binder的引用在代碼中也叫句柄(handle)。 當數(shù)據(jù)包到達接收方時,驅(qū)動已將該成員修改成Binder實體,即指向 Binder對象內(nèi)存的指針,使用target.ptr來獲取。該指針是接受方在將Binder實體傳輸給其他進程時提交給驅(qū)動的,驅(qū)動程序能夠自動將發(fā)送方填入的引用轉(zhuǎn)換成接收方的Binder對象的指針,故接收方可以直接將其當對象指針來使用(通常是將其reinpterpret_cast相應(yīng)類)
void *cookie; 發(fā)送方忽略該成員;接收方收到數(shù)據(jù)包時,該成員存放的是創(chuàng)建Binder實體時由該接收方自定義的任意數(shù)值,做為與Binder指針相關(guān)的額外信息存放在驅(qū)動中。驅(qū)動基本上不關(guān)心該成員
unsigned int code ; 該成員存放收發(fā)雙方約定的命令碼,驅(qū)動完全不關(guān)心該成員的內(nèi)容。通常是Server端的定義的公共接口函數(shù)的編號
unsigned int code; 與交互相關(guān)的標志位,其中最重要的是TF_ONE_WAY位。如果該位置上表明這次交互是異步的,Server端不會返回任何數(shù)據(jù)。驅(qū)動利用該位決定是否構(gòu)建與返回有關(guān)的數(shù)據(jù)結(jié)構(gòu)。另外一位TF_ACCEPT_FDS是處于安全考慮,如果發(fā)起請求的一方不希望在收到回復(fù)中接收文件的Binder可以將位置上。因為收到一個文件形式的Binder會自動為接收方打開一個文件,使用該位可以防止打開文件過多
pid_t send_pid uid_t sender_euid 該成員存放發(fā)送方的進程ID和用戶ID,由驅(qū)動負責(zé)填入,接收方可以讀取該成員獲取發(fā)送方的身份。
size_t data_size 驅(qū)動一般情況下不關(guān)心data.buffer里存放了什么數(shù)據(jù)。但如果有Binder在其中傳輸則需要將其對應(yīng)data.buffer的偏移位置指出來讓驅(qū)動知道。有可能存在多個Binder同時在數(shù)據(jù)中傳遞,所以須用數(shù)組表示所有偏移位置。本成員表示該數(shù)組的大小。
union{ struct{ const void *buffer; const void * offset; } ptr; uint8_t buf[8];} data; data.buffer存放要發(fā)送或接收到的數(shù)據(jù);data.offsets指向Binder偏移位置數(shù)組,該數(shù)組可以位于data.buffer中,也可以在另外的內(nèi)存空間中,并無限制。buf[8]是為了無論保證32位還是64位平臺,成員data的大小都是8字節(jié)。

PS:這里有必要強調(diào)一下offsets_size和data.offsets兩個成員,這是Binder通信有別于其他IPC的地方。就像前面說說的,Binder采用面向?qū)ο蟮脑O(shè)計思想,一個Binder實體可以發(fā)送給其他進程從而建立許多跨進程的引用;另外這些引用也可以在進程之間傳遞,就像java將一個引用賦值給另外一個引用一樣。為Binder在不同進程中創(chuàng)建引用必須有驅(qū)動參與,由驅(qū)動在內(nèi)核創(chuàng)建并注冊相關(guān)的數(shù)據(jù)結(jié)構(gòu)后接收方才能使用該引用。而且這些引用可以是強類型的,需要驅(qū)動為其維護引用計數(shù)。然后這些跨進程傳遞的Binder混雜在應(yīng)用程序發(fā)送的數(shù)據(jù)包里,數(shù)據(jù)格式由用戶定義,如果不把他們一一標記出來告知驅(qū)動,驅(qū)動將無法從數(shù)據(jù)中將他們提取出來。于是就是使用數(shù)組data.offsets存放用戶數(shù)據(jù)中每個Binder相對于data.buffer的偏移量,用offersets_size表示這個數(shù)組的大小。驅(qū)動在發(fā)送數(shù)據(jù)包時會根據(jù)data.offsets和offset_size將散落于data.buffer中的Binder找出來并一一為它們創(chuàng)建相關(guān)的數(shù)據(jù)結(jié)構(gòu)。

七、Binder的整體架構(gòu)

Binder的整體架構(gòu).png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容