1. 程式人生 > >讓你一看就明白的binder機制

讓你一看就明白的binder機制

寫在前面

網上有很多學習android binder機制的文章和部落格,但是大部分或者是深入native不能自拔,看的雲裡霧裡(本人一直使用java,C語言較渣);或者是隻講理論缺乏實際程式設計的過程。所以就想總結下binder的基本理論並附帶一個基於Aidl的程序間的通訊的Demo,希望能對初步接觸android binder機制的小夥伴們提供些幫助。當然本人能力有限,如有錯誤之處還請指教。

binder機制是什麼?

我們一直在說binder機制,那麼binder機制到底是什麼東西呢?講binder之前,我們需要先來了解下IPC(inter process communication)—-程序間通訊或者是跨程序通訊,其實也就是指的是兩個程序之間通訊(資料交換)的過程。

每一個作業系統都有各自的IPC機制。windows可以通過剪貼簿、管道或者是郵槽進行執行緒間通訊;Linux上可以通過命名管道、共享內同、訊號量等進行程序間通訊。Android是基於Linux的移動作業系統,但是它有著自己比較特色的程序間通訊方式那就是binder。binder可以輕鬆地實現程序間的通訊。

binder架構

網路上的流傳的binder架構根據是否使用serviceManager分為兩種:一個是含有serviceManager參與的binder架構,一種是client與service直接使用binder進行程序通訊的binder架構。當然本質上其實他們的過程都是一樣的。現在為了儘可能詳細介紹,還是決定分開介紹下。第一部分是含有serviceManager的binder通訊過程(實名binder),側重於整個流程的介紹。第二部分則側重應用級程式碼的介紹(匿名binder)。

含有serviceManager的binder機制

Binder框架定義了四個角色:Server,Client,ServiceManager以及Binder驅動。其中Server,Client,ServiceManager運行於使用者空間,驅動運行於核心空間。這四個角色的關係和網際網路類似:Server是伺服器,Client是客戶終端,ServiceManager是域名伺服器(DNS),驅動是路由器。

  • Binder 驅動

    和路由器一樣,Binder驅動雖然默默無聞,卻是通訊的核心。儘管名叫‘驅動’,實際上和硬體裝置沒有任何關係,只是實現方式和裝置驅動程式是一樣的。它工作於核心態,驅動負責程序之間Binder通訊的建立,Binder在程序之間的傳遞,資料包在程序之間的傳遞和互動等一系列底層支援。

  • ServiceManager 與實名Binder

    和DNS類似,ServiceManager的作用是將字元形式的Binder名字轉化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。註冊了名字的Binder叫實名Binder,就象每個網站除了有IP地址外還有自己的網址。Server建立了Binder實體,為其取一個字元形式可讀易記的名字,將這個Binder連同名字以資料包的形式通過Binder驅動傳送給ServiceManager,通知ServiceManager註冊一個名叫張三的Binder,它位於某個Server中。驅動為這個穿過程序邊界的Binder建立位於核心中的實體節點以及SMgr對實體的引用,將名字及新建的引用打包傳遞給ServiceManager。ServiceManager收資料包後,從中取出名字和引用填入一張查詢表中。

  • Client 獲得實名Binder的引用

    Server向ServiceManager註冊了Binder實體及其名字後,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向ServiceManager請求訪問某個Binder:我申請獲得名字叫張三的Binder的引用。ServiceManager收到這個連線請求,從請求資料包裡獲得Binder的名字,在查詢表裡找到該名字對應的條目,從條目中取出Binder的引用,將該引用作為回覆傳送給發起請求的Client。

圖形化過程圖如下所示:

這裡寫圖片描述

以上就是實名binder(通過service向serviceManager進行註冊)的跨程序處理流程過程。

匿名binder與client

在我們日常開發中,我們需要根據各種各樣的需求來定義我們自己功能的service,如果該service和client同處於一個程序,那麼他們的呼叫以及資料交換沒有任何問題。當不處於一個程序的時候,該service也沒有像前一部分那樣向serviceManager進行註冊(匿名binder),那麼我們該怎樣去實現跨程序通訊呢?

首先將圖形化流程圖展示如下:

這裡寫圖片描述

上面圖表中的方法我們會在接下來的demo實踐中逐個介紹,我們可以先從整體上來看下這部分的邏輯:

binder通訊是一種client-server的通訊結構,
1.從表面上來看,是client通過獲得一個server的代理介面,對server進行直接呼叫;
2.實際上,代理介面中定義的方法與server中定義的方法是一一對應的;
3.client呼叫某個代理介面中的方法時,代理介面的方法會將client傳遞的引數打包成為Parcel物件;
4.代理介面將該Parcel傳送給核心中的binder driver.
5.server會讀取binder driver中的請求資料,如果是傳送給自己的,解包Parcel物件,處理並將結果返回;
6.整個的呼叫過程是一個同步過程,在server處理的時候,client會block住。

Binder如何精確制導,找到目標Binder

Binder實體服務如本文所述其實有兩種,一是通過addService註冊到ServiceManager中的服務,比如ActivityManagerService、PackageManagerService、PowerManagerService等,一般都是系統服務;還有一種是通過bindService拉起的一些服務,一般是開發者自己實現的服務。這裡先看通過addService新增的被ServiceManager所管理的服務。有很多分析ServiceManager的文章,本文不分析ServiceManager,只是簡單提一下,ServiceManager是比較特殊的服務,所有應用都能直接使用,因為ServiceManager對於Client端來說Handle控制代碼是固定的,都是0,所以ServiceManager服務並不需要查詢,可以直接使用。

理解Binder定向制導的關鍵是理解Binder的四棵紅黑樹,本文定界為binder的初始介紹文章,所以不再過度涉及JNI層面的東西。

Binder一次拷貝原理

Android選擇Binder作為主要程序通訊的方式同其效能高也有關係,Binder只需要一次拷貝就能將A程序使用者空間的資料為B程序所用。這裡主要涉及兩個點:

  • Binder的map函式,會將核心空間直接與使用者空間對應,使用者空間可以直接訪問核心空間的資料
  • A程序的資料會被直接拷貝到B程序的核心空間(一次拷貝)

一次拷貝原理圖示

這裡寫圖片描述

資料拷貝操作:當資料從使用者空間拷貝到核心空間的時候,是直從當前程序的使用者空間接拷貝到目標程序的核心空間,這個過程是在請求端執行緒中處理的,操作物件是目標程序的核心空間

而由於Binder核心空間的資料能直接對映到使用者空間,這裡就不在需要拷貝到使用者空間。這就是一次拷貝的原理。核心空間的資料對映到使用者空間其實就是新增一個偏移地址,並且將資料的首地址、資料的大小都複製到一個使用者空間的Parcel結構體

系統服務與bindService等啟動的服務的區別

服務可分為系統服務與普通服務,系統服務一般是在系統啟動的時候,由SystemServer程序建立並註冊到ServiceManager中的。而普通服務一般是通過ActivityManagerService啟動的服務,或者說通過四大元件中的Service元件啟動的服務。這兩種服務在實現跟使用上是有不同的,主要從以下幾個方面:

  • 服務的啟動方式
  • 服務的註冊與管理
  • 服務的請求使用方式

首先看一下服務的啟動上,

系統服務一般都是SystemServer程序負責啟動,比如AMS,WMS,PKMS,電源管理等,這些服務本身其實實現了Binder介面,作為Binder實體註冊到ServiceManager中,被ServiceManager管理,而SystemServer程序裡面會啟動一些Binder執行緒,主要用於監聽Client的請求,並分發給響應的服務實體類,可以看出,這些系統服務是位於SystemServer程序中(有例外,比如Media服務)。

而bindService型別的服務,這類服務一般是通過Activity的startService或者其他context的startService啟動的,這裡的Service元件只是個封裝,主要的是裡面Binder服務實體類,這個啟動過程不是ServcieManager管理的,而是通過ActivityManagerService進行管理的,同Activity管理類似。

再來看一下服務的註冊與管理:

系統服務一般都是通過ServiceManager的addService進行註冊的,這些服務一般都是需要擁有特定的許可權才能註冊到ServiceManager。

而bindService啟動的服務可以算是註冊到ActivityManagerService,只不過ActivityManagerService管理服務的方式同ServiceManager不一樣,而是採用了Activity的管理模型,詳細的可以自行分析

最後看一下使用方式

使用系統服務一般都是通過ServiceManager的getService得到服務的控制代碼,這個過程其實就是去ServiceManager中查詢註冊系統服務。

而bindService啟動的服務,主要是去ActivityManagerService中去查詢相應的Service元件,最終會將Service內部Binder的控制代碼傳給Client。

這裡寫圖片描述

Binder請求的同步與非同步

很多人都會說,Binder是對Client端同步,而對Service端非同步,其實並不完全正確,在單次Binder資料傳遞的過程中,其實都是同步的。只不過,Client在請求Server端服務的過程中,是需要返回結果的,即使是你看不到返回資料,其實還是會有個成功與失敗的處理結果返回給Client,這就是所說的Client端是同步的。

至於說服務端是非同步的,可以這麼理解:在服務端在被喚醒後,就去處理請求,處理結束後,服務端就將結果返回給正在等待的Client執行緒,將結果寫入到Client的核心空間後,服務端就會直接返回了,不會再等待Client端的確認,這就是所說的服務端是非同步的。

Binder傳輸資料的大小限制

雖然APP開發時候,Binder對程式設計師幾乎不可見,但是作為Android的資料運輸系統,Binder的影響是全面性的,所以有時候如果不瞭解Binder的一些限制,在出現問題的時候往往是沒有任何頭緒,比如在Activity之間傳輸BitMap的時候,如果Bitmap過大,就會引起問題,比如崩潰等,這其實就跟Binder傳輸資料大小的限制有關係,在上面的一次拷貝中分析過,mmap函式會為Binder資料傳遞對映一塊連續的虛擬地址,這塊虛擬記憶體空間其實是有大小限制的,不同的程序可能還不一樣。

普通的由Zygote孵化而來的使用者程序,所對映的Binder記憶體大小是不到1M的,準確說是 110241024) - (4096 *2) :這個限制定義在ProcessState類中,如果傳輸說句超過這個大小,系統就會報錯,因為Binder本身就是為了程序間頻繁而靈活的通訊所設計的,並不是為了拷貝大資料而使用。

基於binder跨程序通訊的DEMO

以上就是關於binder機制的總體介紹了,大部分都是文字介紹,接下里我們將基於AIDL去實現一個跨程序通訊的經典Demo,並在該demo中介紹binder的應用級別函式的作用以及呼叫過程。