這是關于Android中Binder機制的一系列純技術貼。花了一個多禮拜的時間,才終于将其整理完畢。行文于此,以做記錄;也是将自己所得與大家分享。
和以往一樣,介紹Binder時,先講解架構,然後再從設計和細節等方面一一展開。若文章若錯誤或纰漏,請不吝指出。謝謝!
注意:本文是基于Android 4.4.2版本進行介紹的!
1. Binder架構解析
1.1 Binder模型

上圖中涉及到Binder模型的4類角色:Binder驅動,ServiceManager,Server和Client。 因為後面章節講解Binder時,都是以MediaPlayerService和MediaPlayer為代表進行講解的;這裡就使用MediaPlayerService代表了Server,而MediaPlayer則代表了Client。
Binder機制的目的是實作IPC(Inter-Process Communication),即實作程序間通信。在上圖中,由于MediaPlayerService是Server的代表,而MediaPlayer是Client的代表;是以,對于上圖而言,Binder機制則表現為"實作MediaPlayerService和MediaPlayer之間的通信"。
1.2 Binder驅動存在的原因和意義
在回答"Binder機制中Binder驅動存在的原因和意義"之前,先介紹幾個基本的概念。
1. Linux系統中的記憶體劃分
Android是基于Linux核心而打造的作業系統。
以32位Linux系統而言,它的記憶體最大是4G。在這4G記憶體中,0~3G為使用者空間,3~4G為核心空間。應用程式都運作在使用者空間,而Kernel和驅動都運作在核心空間。使用者空間和核心空間若涉及到通信(即,資料互動),兩者不能簡單地使用指針傳遞資料,而必須在"核心"中通過copy_from_user(),copy_to_user(),get_user()或put_user()等函數傳遞資料。copy_from_user()和get_user()是将核心空間的資料拷貝到使用者空間,而copy_to_user()和put_user()則是将使用者空間的資料拷貝到核心空間。
2. 程序的基本概念
程序擁有獨立的記憶體單元,它是系統進行資源配置設定和排程的基本機關。對于Linux系統而言,每一個運作在使用者空間的應用程式都可以看作一個程序。
不同的程序在不同的記憶體中,是以當一個程式崩潰之後,不會對其它的程式造成影響。
通過上面的"Linux的記憶體劃分"和"程序",我們可以了解到:應用程式都運作在使用者空間,每個應用程式都有它自己獨立的記憶體空間;若不同的應用程式之間涉及到通信,需要通過核心進行中轉,因為需要用到核心的copy_from_user()和copy_to_user()等函數。
現在,再回到上面的架構圖中。圖中的ServiceManager, MediaPlayerService和MediaPlayer都位于使用者空間,它們是不同的程序。前面說過,Binder機制的最終目的是實作"MediaPlayerService和MediaPlayer這兩個不同程序之間的通信"。而這兩個不同程序的通信必須要核心進行中轉,對于Android而言,在核心中起中轉作用便是Binder驅動。那麼Binder驅動是如何進行資料中轉的呢?這裡概括的介紹一下,後面再詳細說明。
Android的通信是基于Client-Server架構的,程序間的通信無非就是Client向Server發起請求,Server響應Client的請求。這裡以發起請求為例:當Client向Server發起請求(例如,MediaPlayer向MediaPlayerService發起請求),Client會先将請求資料從使用者空間拷貝到核心空間(将資料從MediaPlayer發給Binder驅動);資料被拷貝到核心空間之後,再通過驅動程式,将核心空間中的資料拷貝到Server位于使用者空間的緩存中(Binder驅動将資料發給MediaPlayerService)。這樣,就成功的将Client程序中的請求資料傳遞到了Server程序中。
實際上,Binder驅動是整個Binder機制的核心。除了實作上面所說的資料傳輸之外,Binder驅動還是實作線程控制(通過中斷等待隊列實作線程的等待/喚醒),以及UID/PID等安全機制的保證。
1.3 ServiceManager存在的原因和意義
Binder是要實作Android的C-S架構的,即Client-Server架構。而ServiceManager,是以服務管理者的身份存在的。
ServiceManager也是運作在使用者空間的一個獨立程序。
(01) 對于Binder驅動而言,ServiceManager是一個守護程序,更是Android系統各個服務的管理者。Android系統中的各個服務,都是添加到ServiceManager中進行管理的,而且每個服務都對應一個服務名。當Client擷取某個服務時,則通過服務名來從ServiceManager中擷取相應的服務。
(02) 對于MediaPlayerService和MediaPlayer而言,ServiceManager是一個Server服務端,是一個伺服器。當要将MediaPlayerService等服務添加到ServiceManager中進行管理時,ServiceManager是伺服器,它會收到MediaPlayerService程序的添加服務請求。當MediaPlayer等用戶端要擷取MediaPlayerService等服務時,它會向ServiceManager發起擷取服務請求。
當MediaPlayer和MediaPlayerService通信時,MediaPlayerService是服務端;而當MediaPlayerService則ServiceManager通信時,ServiceManager則是服務端。這樣,就造就了ServiceManager的特殊性。于是,在Binder驅動中,将句柄0指定為ServiceManager對應的句柄,通過這個特殊的句柄就能擷取ServiceManager對象。這部分的知識後面會詳細介紹。
1.4 為什麼采用Binder機制,而不是其他的IPC通信方式
前面說過,Android是在Linux核心的基礎上設計的。而在Linux中,已經擁有"管道/消息隊列/共享記憶體/信号量/Socket等等"衆多的IPC通信手段;但是,Google為什麼單單選擇了Binder,而不是其它的IPC機制呢?
這肯定是因為Binder具有無可比拟的優勢。下面就從 "實用性(Client-Server架構)/傳輸效率/操作複雜度/安全性" 等幾方面進行分析。
第一. Binder能夠很好的實作Client-Server架構
對于Android系統,Google想提供一套基于Client-Server的通信方式。
例如,将"電池資訊/馬達控制/wifi資訊/多媒體服務"等等不同的服務都由不同的Server提供,當Client需要擷取某Server的服務時,隻需要Client向Server發送相應的請求,Server收到請求之後進行處理,處理完畢再将回報内容發送給Client。
但是,目前Linux支援的"傳統的管道/消息隊列/共享記憶體/信号量/Socket等"IPC通信手段中,隻有Socket是Client-Server的通信方式。但是,Socket主要用于網絡間通信以及本機中程序間的低速通信,它的傳輸效率太低。
第二. Binder的傳輸效率和可操作性很好
前面已經說了,Socket傳輸效率很低,已經被排除。而消息隊列和管道又采用存儲-轉發方式,使用它們進行IPC通信時,需要經過2次記憶體拷貝!效率太低!
為什麼消息隊列和管道的資料傳輸需要經過2次記憶體拷貝呢? 首先,資料先從發送方的緩存區(即,Linux中的使用者存儲空間)拷貝到核心開辟的緩存區(即,Linux中的核心存儲空間)中,是第1次拷貝。接着,再從核心緩存區拷貝到接收方的緩存區(也是Linux中的使用者存儲空間),這是第2次拷貝。
而采用Binder機制的話,則隻需要經過1次記憶體拷貝即可! 即,從發送方的緩存區拷貝到核心的緩存區,而接收方的緩存區與核心的緩存區是映射到同一塊實體位址的,是以隻需要1次拷貝即可。
至于共享記憶體呢,雖然使用它進行IPC通信時進行的記憶體拷貝次數是0。但是,共享記憶體操作複雜,也将它排除。
第三. Binder機制的安全性很高
傳統IPC沒有任何安全措施,完全依賴上層協定來確定。傳統IPC的接收方無法獲得對方程序可靠的UID/PID(使用者ID/程序ID),進而無法鑒别對方身份。而Binder機制則為每個程序配置設定了UID/PID來作為鑒别身份的标示,并且在Binder通信時會根據UID/PID進行有效性檢測。
1.5 Binder中各角色之間關系
先看看下面的關系圖
在解釋上面的圖之前,先解釋圖中涉及到的幾個非常重要的概念。
1. Binder實體
Binder實體,是各個Server以及ServiceManager在核心中的存在形式。
Binder實體實際上是核心中binder_node結構體的對象,它的作用是在核心中儲存Server和ServiceManager的資訊(例如,Binder實體中儲存了Server對象在使用者空間的位址)。簡言之,Binder實體是Server在Binder驅動中的存在形式,核心通過Binder實體可以找到使用者空間的Server對象。
在上圖中,Server和ServiceManager在Binder驅動中都對應的存在一個Binder實體。
2. Binder引用
說到Binder實體,就不得不說"Binder引用"。所謂Binder引用,實際上是核心中binder_ref結構體的對象,它的作用是在表示"Binder實體"的引用。換句話說,每一個Binder引用都是某一個Binder實體的引用,通過Binder引用可以在核心中找到它對應的Binder實體。
如果将Server看作是Binder實體的話,那麼Client就好比Binder引用。Client要和Server通信,它就是通過儲存一個Server對象的Binder引用,再通過該Binder引用在核心中找到對應的Binder實體,進而找到Server對象,然後将通信内容發送給Server對象。
Binder實體和Binder引用都是核心(即,Binder驅動)中的資料結構。每一個Server在核心中就表現為一個Binder實體,而每一個Client則表現為一個Binder引用。這樣,每個Binder引用都對應一個Binder實體,而每個Binder實體則可以多個Binder引用。
3. 遠端服務
Server都是以服務的形式注冊到ServiceManager中進行管理的。如果将Server本身看作是"本地服務"的話,那麼Client中的"遠端服務"就是本地服務的代理。如果你對代理模式比較熟悉的話,就很容易了解了,遠端服務就是本地服務的一個代理,通過該遠端服務Client就能和Server進行通信。
了解上面3個概念之後,下面再通過幾個典型的通信示例來解析該關系圖。
ServiceManager守護程序
ServiceManager是使用者空間的一個守護程序。當該應用程式啟動時,它會和Binder驅動進行通信,告訴Binder驅動它是服務管理者;對Binder驅動而言,它則會建立ServiceManager對應的Binder實體,并将該Binder實體設為全局變量。為什麼要将它設為全局變量呢?這點應該很容易了解--因為Client和Server都需要和ServiceManager進行通信,不将它設為全局變量的話,怎麼找到ServiceManager呢!
Server注冊到ServiceManager中
Server首先會向Binder驅動發起注冊請求,而Binder驅動在收到該請求之後就将該請求轉發給ServiceManager程序。但是Binder驅動怎麼才能知道該請求是要轉發給ServiceManager的呢?這是因為Server在發送請求的時候,會告訴Binder驅動這個請求是交給0号Binder引用對應的程序來進行處理的。而Binder驅動中指定了0号引用是與ServiceManager對應的。
在Binder驅動轉發該請求之前,它其實還做了兩件很重要的事:(01) 當它知道該請求是由一個Server發送的時候,它會建立該Server對應的Binder實體。 (02) 它在ServiceManager的"儲存Binder引用的紅黑樹"中查找是否存在該Server的Binder引用;找不到的話,就建立該Server對應的Binder引用,并将其添加到"ServiceManager的儲存Binder引用的紅黑樹"中。簡言之,Binder驅動會建立Server對應的Binder實體,并在ServiceManager的紅黑樹中添加該Binder實體的Binder引用。
當ServiceManager收到Binder驅動轉發的注冊請求之後,它就将該Server的相關資訊注冊到"Binder引用組成的單連結清單"中。這裡所說的Server相關資訊主要包括兩部分:Server對應的服務名 + Server對應的Binder實體的一個Binder引用。
Client擷取遠端服務
Client要和某個Server通信,需要先擷取到該Server的遠端服務。那麼Client是如何擷取到Server的遠端服務的呢?
Client首先會向Binder驅動發起擷取服務的請求。Binder驅動在收到該請求之後也是該請求轉發給ServiceManager程序。ServiceManager在收到Binder驅動轉發的請求之後,會從"Binder引用組成的單連結清單"中找到要擷取的Server的相關資訊。至于ServiceManager是如何從單連結清單中找到需要的Server的呢?答案是Client發送的請求資料中,會包括它要擷取的Server的服務名;而ServiceManager正是根據這個服務名來找到Server的。
接下來,ServiceManager通過Binder驅動将Server資訊回報給Client的。它回報的資訊是Server對應的Binder實體的Binder引用資訊。而Client在收到該Server的Binder引用資訊之後,就根據該Binder引用資訊建立一個Server對應的遠端服務。這個遠端服務就是Server的代理,Client通過調用該遠端服務的接口,就相當于在調用Server的服務接口一樣;因為Client調用該Server的遠端服務接口時,該遠端服務會對應的通過Binder驅動和真正的Server進行互動,進而執行相應的動作。
2. Binder設計解析
有了上面Binder模型的理論基礎,接下來就可以逐漸來講解Binder的設計了。實際上,在設計C-S架構時,要考慮以下兩個非常重要的因素。
第一,Server要提供接入點
如果C-S架構中的Client和Server屬于同一程序的話,那麼Client和Server之間的通信将非常容易。隻需要在Client端先擷取相應的Server端對象;然後,再通過Server對象調用Server的相應接口即可。但是,Binder機制中涉及到的Client和Server是位于不同的程序中的,這也就意味着,不可能直接擷取到Server對象。那麼怎麼辦呢? 那就需要Server提供一個接入點給Client。
這個接入點就是"Server的遠端服務代理"!
Client能夠擷取到Server的遠端服務,它就相當于Server的代理。Client要和Server通信時,它隻需要調用該遠端服務的相應接口即可,其他的工作都交給遠端服務來處理。遠端服務收到Client請求之後,會和Binder驅動通信;因為遠端服務中有Server在Binder驅動中的Binder引用資訊,是以遠端服務就能輕易的找到對應的Server,進而将Client的請求内容發送Server。
第二,通信協定
Binder機制中,涉及到大量的"核心的Binder驅動 和 使用者空間的引用程式"之間的通信。需要指定對應的通信協定,確定通信的安全和正常。關于這部分,稍候再詳細展開。
有了上面的兩個中心思想之後,再來對Binder驅動的設計和協定進行逐漸展開。
2.1 Binder設計
講解Binder設計時,分為"核心空間"和"使用者空間"這兩部分程序講解。核心空間就是Binder驅動中的Binder設計,而使用者空間則是Android的C++層中的Binder設計。
2.1.1 核心空間的Binder設計
核心空間的Binder設計涉及到3個非常重要的結構體:binder_proc,binder_node和binder_ref。由于本文的重點是介紹Binder機制的理論知識,是以,在這裡我并不打算展開這3個結構體對它們進行詳細介紹。當然,後面會再撰文對這些類進行詳細說明。這裡隻需要了解個大概即可。
binder_proc是描述程序上下文資訊的,每一個使用者空間的程序都對應一個binder_proc結構體。
binder_node是Binder實體對應的結構體,它是Server在Binder驅動中的展現。
binder_ref是Binder引用對應的結構體,它是Client在Binder驅動中的展現。
如上圖所示,binder_proc中包含了3棵紅黑樹。
(01) Binder實體紅黑樹是儲存"binder_proc對應的程序"所包含的Binder實體的,而Binder實體是與Server的服務對應的。可以将Binder實體紅黑樹了解為Server程序中包行的Server服務的紅黑樹。
(02) 圖中有兩棵Binder引用紅黑樹,這兩棵樹所包含的Binder引用都是一樣的。不同的是,紅黑樹的排序基準不同,一個是以Binder實體來排序,而另一個則是以Binder引用描述(Binder引用描述實際上就是一個32位的整型數)來排序。以Binder引用描述的紅黑樹是為了友善進行快速查找。
上圖是描述Binder驅動中Binder實體結構體的。如圖所示,Binder實體中有一個Binder引用的哈希表,專門來存放該Binder實體的Binder引用。這也如我們之前所說,每個Binder實體則可以多個Binder引用,而每個Binder引用則都隻對應一個Binder實體。
2.1.2 使用者空間的Binder設計
上面是使用者空間中Binder模型圖,該圖僅僅隻描述出Server的相關類圖,并沒有Client部分。不過不要緊,通過這個Server的模型圖,就能理清使用者空間的Binder架構。
前面說過,Server是以服務的形式注冊到ServiceManager中,而Server在Client中則是以遠端服務的形式存在的。是以,這個圖的主幹就是理清楚本地服務和遠端服務這兩者之間的關系。
"本地服務"就是Server提供的服務本身,而"遠端服務"就是服務的代理;"服務接口"則是抽象出了它們的通用接口。這3個角色都是通用的,對于不同的服務而言,它們的名稱都不相同。例如,對于MediaPlayerService服務而言,本地服務就是MediaPlayerService自身,遠端服務是BpMediaPlayerService,而服務接口是IMediaPlayerService。當Client需要向MediaPlayerService發送請求時,它需要先擷取到服務的代理(即,遠端服務對象),也就是BpMediaPlayerService執行個體,然後通過該執行個體和MediaPlayerService進行通信。
圖中的ProcessState和IPCThreadState都是采用單例模式實作的,它們的執行個體都是全局的,而且隻有唯一一個。
(01) 當Server啟動之後,它會先将自己注冊到ServiceManager中。注冊時,Binder驅動會建立Server對應的Binder實體,并将"Server對應的本地服務對象的位址"儲存到Binder實體中。注冊成功之後,Server就進入消息循環,等待Client的請求。
(02) 當Client需要和Server通信時,會先擷取到Server接入點,即擷取到遠端服務對象;而且Client要擷取的遠端服務對象是"服務接口"類型的。Client向ServiceManager發送擷取服務的請求時,會通過IPCThreadState和Binder驅動進行通信;當ServiceManager回報之後,IPCThreadState會将ServiceManager回報的"Server的Binder引用資訊"儲存BpBinder中(具體來說,BpBinder的mHandle成員儲存的就是Server的Binder引用資訊)。然後,會根據該BpBinder對象建立對應的遠端服務。這樣,Client就擷取到了遠端服務對象,而且遠端服務對象的成員中儲存了Server的Binder引用資訊。
(03) 當Client擷取到遠端服務對象之後,它就可以輕松的和Server進行通信了。當它需要向Server發送請求時,它會調用遠端服務接口;遠端服務能夠擷取到BpBinder對象,而BpBinder則通過IPCThreadState和Binder驅動進行通信。由于BpBinder中儲存了Server在Binder驅動中的Binder引用;是以,IPCThreadState和Binder驅動通信時,是知道該請求是需要傳給哪個Server的。Binder驅動通過Binder引用找到對應的Binder實體,然後将Binder實體中儲存的"Server對應的本地服務對象的位址"傳回給使用者空間。當IPC收到Binder驅動回報的内容之後,它從内容中找到"Server對應的本地服務對象",然後調用該對象的onTransact()。不同的本地服務都可以實作自己的onTransact();這樣,不同的服務就可以按照自己的需求來處理請求。
2.2 Binder通信
Binder通信協定是基于Command-Reply的方式的。
2.2.1 Binder通信模型
下面是Client和Server的互動模型圖。
圖中的原理很簡單。
(01) Server程序啟動之後,會進入中斷等待狀态,等待Client的請求。
(02) 當Client需要和Server通信時,會将請求發送給Binder驅動。
(03) Binder驅動收到請求之後,會喚醒Server程序。
(04) 接着,Binder驅動還會回報資訊給Client,告訴Client:它發送給Binder驅動的請求,Binder驅動已經收到。
(05) Client将請求發送成功之後,就進入等待狀态。等待Server的回複。
(06) Binder驅動喚醒Server之後,就将請求轉發給Server程序。
(07) Server程序解析出請求内容,并将回複内容發送給Binder驅動。
(08) Binder驅動收到回複之後,喚醒Client程序。
(09) 接着,Binder驅動還會回報資訊給Server,告訴Server:它發送給Binder驅動的回複,Binder驅動已經收到。
(10) Server将回複發送成功之後,再次進入等待狀态,等待Client的請求。
(11) 最後,Binder驅動将回複轉發給Client。
2.2.2 Binder通信資料
上面是使用者空間和核心空間進行互動時,資料的打包方式。例如,當Client向Server發送請求時,Client會将資料打包成上訴格式,然後通過ioctl()發送給Binder驅動。根據資料的層次,從外到裡分為3層進行說明。
第一層:這是使用者空間的程序調用ioctl(fd,BINDER_WRITE_READ,&bwr)時傳遞給Binder驅動的資訊。fd是Binder驅動的檔案句柄,BINDER_WRITE_READ是ioctl()的一個辨別,而bwr是傳遞的資料,它對應是途中的binder_write_read結構體的指針。binder_write_read中以write開頭的是儲存請求資料的,而read開頭的是儲存回報資料的。其中,write_size是請求資料的大小,write_buffer是請求資料的内容,而write_consumed是用來記錄請求資料中已經被Binder驅動處理過的資料的大小。
第二層:這層的資料是"事務指令"+"binder_transaction_data結構體"組成的。圖中給出的事務指令是BC_TRANSACTION,表示該事務是請求;如果是回複,則是BR_開頭的,例如BR_TRANSACTION。binder_transaction_data是描述事務互動資料的結構體;例如,target是指定事務目标,用來表示這個事務是交給誰進行處理的;code是事務編碼,用來表示這是一個什麼樣的事務(例如,注冊服務事務/擷取服務事務等待);data是儲存事務中具體資料的記憶體位址。
第三層:這層是有效資料。如果該請求是傳遞給ServiceManager進行處理的,則有效資料是:消息頭+"Server的相關資訊"。消息頭是用來進行有效性檢查的,而"Server的相關資訊"則是請求要處理的資訊。
http://wangkuiwu.github.io/2014/09/01/Binder-Introduce/
------------------越是喧嚣的世界,越需要甯靜的思考------------------
合抱之木,生于毫末;九層之台,起于壘土;千裡之行,始于足下。
積土成山,風雨興焉;積水成淵,蛟龍生焉;積善成德,而神明自得,聖心備焉。故不積跬步,無以至千裡;不積小流,無以成江海。骐骥一躍,不能十步;驽馬十駕,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓無爪牙之利,筋骨之強,上食埃土,下飲黃泉,用心一也。蟹六跪而二螯,非蛇鳝之穴無可寄托者,用心躁也。