天天看點

Android Binder程序間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動

珍惜作者勞動成果,如需轉載,請注明出處。

http://blog.csdn.net/zhengzechuan91/article/details/50577631

Android使用Binder作為程序間通信的工具,基本遍布在Android系統的各個角落,其重要性不言而喻。我們了解了Binder的原理,對我們了解Android系統原理有非常重要的意義。Binder底層代碼及其複雜,我們如果沒有對Binder有一個系統的了解,那麼我們很可能會迷失在源碼中,半途放棄。是以這篇文章我會盡量少的分析源碼,而更多的是從原理角度來分析。如果搞明白了Binder實作的原理,那麼再去深入的研究源碼,就會更加遊刃有餘。

Binder概述

Binder通信過程中涉及到Client、Server、ServiceManager、Binder驅動,前三者運作在使用者空間,而Binder驅動運作在核心空間,ServiceManager是在系統啟動後就開始運作起來的,負責統一管理各式各樣的Server服務,每個Server服務首先将自己注冊到ServiceManager,Client根據服務名稱去ServiceManager查找Server,ServiceManager會将此服務的代理Binder(BpBinder)告訴Client,Client再将代理Binder包裝成代理Server服務(BpXxxService),Client就可以使用這個代理Server服務調用Server端的服務,就跟調用Server本身一樣。

經典的Binder通信代碼

ServiceManager

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
    bs = binder_open(*);
    binder_become_context_manager(bs);
    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
}
           

Server

int main(int argc, char** argv)
{
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    MediaPlayerService::instantiate();
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}
           

Client

int main(int argc, char **argv) {
    sp<IServiceManager> sm = defaultServiceManager(); 
    sp<IBinder> binder;  
    binder = sm->getService(String16("media.player"));  
    sMediaPlayerService = 
        interface_cast<IMediaPlayerService>(binder);  

}
           

資料結構

在開始學習Binder通信之前,我們先對會使用到的結構體有個簡單的了解,不用馬上全部記住,隻需要在分析時用到了再回頭來看:

【struct binder_proc】 執行mmap後的程序在核心空間對應的資料結構,裡面儲存了使用者空間位址、核心空間位址、核心緩沖區buffer、實體頁面、Binder線程池、Binder實體對象紅黑樹、Binder引用對象紅黑樹等,這是最重要的結構體。

【struct binder_node】:核心空間的Binder實體對象,主要儲存了Binder引用隊列清單、指向Server元件的指針、待處理的工作項。

【struct binder_ref】:核心空間的Binder引用對象,主要儲存了Binder引用對象的句柄、Binder實體對象指針。

【struct binder_buffer】:核心空間的核心緩沖區最終會被切分成一塊一塊的binder_buffer,有效資料儲存在内部變量data中。

【struct binder_thread】:處理Binder通信的線程池,裡面儲存了等待隊列、事務堆棧。

【struct binder_transaction】:Binder線程處理請求都是以事務為機關的,主要儲存發起事務線程、處理事務線程和程序、Binder驅動為事務配置設定的核心緩沖區。

【struct binder_write_read】:程序與Binder驅動互動時的資料結構。通過ioctl()向Binder驅動發送指令協定(BC)儲存在write_buffer,接收Binder驅動傳回協定(BR)的結果儲存在read_buffer。

【struct binder_transaction_data】:程序與Binder驅動通信時會将Parcel轉換為binder_transaction_data,其中的動态緩沖區中buffer儲存通信資料,offsets儲存Binder對象flat_binder_object的位置。

【struct flat_binder_object】:資料緩沖區中的Binder對象,Binder實體對象時儲存指向Server元件的位址,Binder引用對象時儲存對象的句柄。

Binder通信過程

ServiceManager的啟動過程

Android Binder程式間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動
  1. ServiceManager在系統一啟動在init.rc就運作起來;
  2. binder_open()打開Binder虛拟裝置,并且為目前程序建立binder_proc結構體,Bindder驅動為ServiceMamager在核心空間配置設定了128K的核心緩沖區,并且是隻讀的,隻能由核心去修改;
  3. 通過io指令BINDER_SET_CONTEXT_MGR将自己注冊為Binder驅動的”大管家”,并且建立一個binder_thread作為主線程,還會為ServiceManager的Binder本地對象建立一個Binder實體對象,這些都是在Binder驅動中完成的;
  4. 執行bind_loop()首先使用io指令BINDER_WRITE_READ将目前線程在Binder驅動中注冊為Binder線程,然後傳回到使用者控件。再執行死循環,使用BINDER_WRITE_READ指令來從Binder驅動中取出Client發來的消息:如果沒有消息,則目前線程睡眠在Binder驅動程式的binder_thread_read()函數中,否則通過binder_parse()來解析消息,而這個方法處理了從Binder驅動傳回的所有傳回協定(BR),而重點是在BR_TRANSACTION傳回協定的處理上,svcmgr_handler()中處理具體的請求類型,我們通過ServiceManager無非就是想查找server服務、添加server服務、周遊server服務,而這些功能都是通過内部維護的svclist清單來實作的。最後再将查找或添加的結果通過binder_send_reply()通知到Binder驅動,這樣,BR_TRANSACTION傳回協定才處理完畢。

注:前面Binder驅動在解析BINDER_WRITE_READ指令時會判斷,如果寫緩沖區bwr.write_buffer不為空,則從程序向Binder驅動寫指令協定(BC),通過copy_from_user()将使用者空間的結構體binder_write_read拷貝到核心空間,若讀緩沖區bwr.read_buffer不為空,則從Binder驅動向程序寫傳回協定(BR),通過copy_to_user()将核心空間的結構體拷貝到使用者空間。

Server/ServiceManager代理對象的擷取

在學習ServiceManager代理對象的擷取前,我們先想一下為什麼要使用代理,我們在學習代理模式的時候知道,使用代理就是為了對調用者屏蔽真實對象的細節,以免發生耦合,這裡也是一樣,Binder驅動在和ServiceManager互動時,無需知道ServiceManager内部的構造和具體實作,隻需和IServiceManager接口互動即可。

我們根據上面Client部分的代碼來分析下:首先defaultServiceManager()是個單例,傳回的是new BpServiceManager(new BpBinder(0)),全局中隻要調用此方法傳回的都是這個對象。然後通過BpServiceManager的getService()來擷取此Server服務的Binder代理對象,内部是通過BpBinder(0)的transact()方法與Binder驅動通信來從ServiceManager中查找的。再通過interface_cast()将Server服務的Binder代理對象包裝成BpXxxService(),此時代理Server就可以像使用Server服務一樣,因為它們都實作了IXxxService接口。

Client用來與Binder通信的從下往上分别為ProcessState/IPCThreadState -> BpBinder -> BpServiceManager/BpXxxService,越往上封裝的越抽象。BpBinder的transact()用來向驅動發送指令,實際調用的是IPCThreadState的transact()方法。

從上面我們清晰的看出,每一次代理對象方法的調用,都涉及到一次Binder通信。因為代理對象方法分調用本質上是調用了BpBinder的transact()方法向Binder驅動發送指令,而其實際上又調用了IPCThreadState的transact()方法,該方法内部則與Binder驅動進行了通信。

Server程序的啟動過程

Android Binder程式間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動

1. ProcessState::self()是單例模式,第一次調用會打開Binder裝置,并且Binder驅動會給Server程序配置設定(1M-4k * 2)大小的核心緩沖區;

2. 通過defaultServiceManager()擷取到ServiceManager的代理對象。

3. 初始化Server服務,就是建立一個Server本地對象,然後通過ServiceManager代理注冊到ServiceManager裡面,看似簡單,實際上這個過程比較複雜。

(1) 使用代理注冊時首先是将binder對象使用flat_binder_object結構體寫入到Parcel對象中,Parcel格式有兩個緩沖區,mData裡内容可能是整數、字元串或flat_binder_object,而mObjects儲存的是每個flat_binder_object的位置,而且兩個緩沖區也會根據需要自動擴充空間。

(2) 通過Binder代理對象的transaction()發送BC_TRANSACTION指令協定,由于BC_TRANSACTION是通過io指令BINDER_WRITE_READ發送到Binder驅動的,是以要将Parcel對象轉化為binder_transaction_data結構體,然後通過writeTransactionData()将此結構體寫入到IPCThreadState的指令協定緩沖區mOut中,然後waitForResponse()裡面執行一個死循環,調用talkWithDriver()将mOut轉換為binder_write_read發送到Binder驅動,另外還會從傳回協定緩沖區mIn中讀取傳回值。

(3) Binder驅動調用binder_transaction()處理指令請求,根據句柄值找到Binder引用對象,再通過引用對象找到實體對象,然後在目标程序中找到一個等待其它事務但暫時空閑的線程去處理BR_TRANSACTION傳回協定,是通過将binder_transaction結構體封裝成BINDER_WORK_TRANSACTION工作項加入到目标todo隊列,這時候我們需要将源程序中傳遞的binder_transaction_data中的資料緩沖區拷貝到binder_transaction的資料緩沖區中,然後取出其中的binder對象,在Binder驅動中目标程序建立Binder實體對象和引用對象,并為引用對象配置設定句柄值。然後通知源程序之前發送的BC_TRANSACTION已經收到,将binder_work結構體封裝成BINDER_TRANSACTION_COMPLETE添加到源線程的todo隊列中,以便源線程在傳回使用者空間之前可以處理這個工作項,最後BR_TRANSACTION傳回協定就寫入到了IPCThreadState的傳回協定緩沖區mIn。

(4)如果是同步請求,則工作項會添加到todo隊列中,而如果是異步請求,則會添加到目标Binder實體對象的async_todo隊列中,為了節省資源,異步請求每次最多隻能執行一個工作項。如果是同步請求,則目标wait隊列會被喚醒,繼續執行binder_thread_read(),從todo隊列中去取BINDER_WORK_TRANSATION工作項,從這個工作項裡取出binder_transaction,處理這個工作項的方式是向目标線程發送一個BR_TRANSACTION傳回協定,這個協定裡包裝了binder_transaction_data,這裡将binder_transaction裡的通信資料拷貝到binder_transaction_data,實際上隻是将binder_transaction_data的指針指向了binder_transaction,這些緩沖區都是在核心空間,然後将BR_TRANSACTION傳回協定拷貝到目标線程提供的使用者空間的資料緩沖區中,這時候ServiceManager在Binder驅動的主線程就會在binder_thread_read被喚醒,傳回到使用者空間後,就會在binder_parse()中解析BR_TRANSACTION傳回協定并将Service添加到svclist中,再通過binder_send_reply()通知Binder驅動傳回結果,實際上是發送了BC_REPLY協定,Binder驅動處理完這個協定後,會在請求注冊service到servicemanager的線程的todo隊列z中添加一個BINDER_WORK_TRANSACTION的工作項,這時候此線程在binder_thread_read()被喚醒,處理這個工作項,然後binder_transaction中傳回的目标實體對象是空的,線程将binder_transaction中的通信資料轉換為binder_transaction_data,然後包裝在BR_REPLY傳回協定中拷貝到thread提供的使用者空間緩沖區,然後線程切換到使用者空間後,就可以在IPCThreadState的waitForResponse()中處理BR_REPLY傳回協定了。

4. 開啟Binder線程池首先建立主線程并把主線程加入到Binder線程池裡,這是通過向Binder驅動發送BC_ENTER_LOOPER指令協定實作的,如果不是主線程,則發送BC_REGISTER_LOOPER指令協定。

Binder通信的本質

Binder通信的本質有兩點:

  1. Binder驅動中目标線程代替源線程執行任務,源線程在源程序裡,而目标線程在目标程序裡,源線程要執行任務,隻需将其包裝成事務,放在目标線程的todo隊列中,目标線程就會代替源線程處理這個事務,而且Binder線程會在使用者空間和核心空間來回切換,充當Binder通信的信使。
  2. 使用者空間和核心空間共享同一塊實體頁面:2.6後linux系統會為每個程序配置設定4G的虛拟空間,其中0~3G的使用者空間是每個程序獨有的,而3G~4G的核心空間是所有程序共享的,程序B在mmap()後Binder驅動為程序配置設定一塊核心緩沖區,最大隻能申請4M,使用者空間和核心空間共享同一塊記憶體,B程序的使用者空間和核心空間同大小,有線性映射關系,核心空間的緩沖資料儲存在binder_proc->buffer中。如下圖,程序A要将資料發送到程序B,先用copy_from_user()将資料從使用者空間拷貝到核心空間的緩沖區中,由于B程序使用者空間和核心空間共享記憶體,當然可以在B程序的使用者空間通路。
Android Binder程式間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動
Android Binder程式間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動

Binder間互動

Android Binder程式間通信深入分析Binder概述經典的Binder通信代碼資料結構Binder通信過程Binder通信的本質Binder間互動

從圖中我們看出,Binder代理對象(BpBinder)在client發起請求時,會在Binder驅動中先找到Binder引用對象(binder_ref),在通過引用對象找到Binder實體對象(binder_node),而通過就Binder實體對象就能找到Server程序的本地對象。

java層的代碼與其相似,隻是通過JNI進行了包裝,這裡就不分析了。