BINDER詳解


1.   進程間通信的本質(2個進程)

用戶空間的進程如果想相互通信, 必須經過內核, 因為不同進程的用戶地址空間是獨立的, 但是共享同一個內核空間.

 

內核為了支持進程間通信, 一般會有一個驅動, 以字符設備的形式存在(也可以是其它形式, 這個驅動的本質就是在不同的進程間傳遞數據). 兩個進程間通信一般以client/server的形式進行, 大致流程如下:

         server進程打開內核設備節點, 並開始監聽該節點是否有數據寫入;

         client進程打開內核設備節點, 往這個節點里面寫入一個數據(copy_from_user;

         內核驅動喚醒阻塞在該節點上的server進程, 並把數據傳遞給它(copy_to_user;

         server進程收到數據, 解析數據, 然后執行相應的動作.

 

Android, binder驅動充當上述流程中的內核驅動, 它是一個字符設備, 設備節點是/dev/binder.

為了便於理解, 我們先假設整個系統只有2個進程, 一個是Client進程, 另外一個是Server進程. 它們通信的圖示如下:

1.1 ProcessState & IPCThreadState

為了方便進程間通信, Android封裝了兩個類: frameworks/native/libs/binder/ProcessState.cpp frameworks/native/libs/binder/IPCThreadState.cpp.

這兩個cpp文件里面既包含了Server進程需要用到的一些接口, 也包含了Client進程需要用到的一些接口(.., 不清楚Android為啥不分開?). 當這兩個文件被編譯到Server進程時, 它們就是Server進程的一部分; 當被編譯到Client進程時, 就是Client進程的一部分.

 

ProcessState提供的接口主要有:

         ProcessState::self() : 主要目的是open(/dev/binder), Server/Client都會用到, 因為它們都得打開內核設備節點, 讀寫數據

         ProcessState::startThreadPool() : Server進程會用到, 主要目的是創建一個線程, 在線程里面while循環, 監聽是否有數據寫入設備節點.

 

IPCThreadState的主要目的是通過ioctl/dev/binder交互, 也就是說它負責讀寫設備節點,完成用戶空間與內核空間的數據交互. 主要接口有:

         IPCThreadState::transact : Client進程會用到, 目的是往設備節點寫入數據.

         transact里面會分waitForResponse(reply)waitForResponse(NULL)這兩種情況, 前者的意思是Client發送一個數據(請求/命令...)給Server, 需要等待Server的執行結果, 結果存儲在reply; 后者的意思是Client只是發送個請求, 不需要等待對應的執行結果.

         waitForResponse會調用talkWithDriver, 后者會調用ioctl/dev/binder交互

         IPCThreadState::joinThreadPool : Server進程會用到, ProcessState::startThreadPool()最終就是調用的IPCThreadState::joinThreadPool, 目的就是監聽設備節點. joinThreadPool會調用getAndExecuteCommand(), 后者會調用talkWithDriver()監聽設備節點, 當收到一個數據后, 就解析並執行相應的操作(ExecuteCommand).

 

加上ProcessState & IPCThreadState, 圖示變成了下面這個樣子:

1.2 IBinder, BBinder, BpBinder, BpRefBase

為了更進一步封裝, Android系統抽象出了IBinder, BBinder, BpBinder,BpRefBase4個類. 抽象出這4個類的原因不僅僅是為了進一步封裝, 更是為了多個進程間的通信, 由於本章暫時只討論2個進程, 所以關於多進程的細節后文在說, 本章只考慮雙進程的情況下, 4個類扮演什么樣的角色.

 

3個類的頭文件以及實現文件如下:

         IBinder : frameworks/native/include/binder/IBinder.h

IBinder是一個接口類, 也就是C++中的純虛基類. 它是BBinderBpBinder的基類, 它里面定義的一個重要接口函數是transact.

         BpBinder : frameworks/native/include/binder/BpBinder.h

實現文件 : frameworks/native/libs/binder/BpBinder.cpp

BpBinder繼承了IBinder, 實現了IBinder中定義的transact函數.

BpBinder用於Client, BpBinder::transact會調用IPCThreadState::transact, 繼而往/dev/binder節點寫入數據, 完成封裝動作.

Client端的代碼會繼承BpBinder, Client想往Server發送數據時, 直接調用BpBinder:: transact函數即可? 但實際上並不是這樣, Client端的代碼實際上會繼承BpRefBase, 通過BpRefBase調用BpBinder:: transact函數. 至於Android系統為什么要這么設計, 我也不清楚原因.

         BpRefBase : frameworks/native/include/binder/Binder.h

實現文件 : frameworks/native/libs/binder/Binder.cpp

BpRefBase里面定義了一個變量 : IBinder* const mRemote; 這個mRemote實際上指的就是BpBinder.

BpRefBase用於Client, Client端的代碼會繼承BpRefBase, Client端想往Server發送數據時, 會使用mRemote->transact()的形式, 實際上也就是調用BpBinder:: transact

         BBinder : frameworks/native/include/binder/Binder.h

實現文件 : frameworks/native/libs/binder/Binder.cpp

BBinder繼承了IBinder, 實現了IBinder中定義的transact函數.前文我們說過Server端會開啟一個線程, 監聽/dev/binder節點. 當收到數據時, Server端的這個線程會調用BBinder::transact.

BBinder用於Server, BBinder除了繼承了IBinder中的transact接口, 它自己也定義了一個onTransact接口, Server端的代碼一般會實現onTransact接口. BBinder::transact里面會調用onTransact函數, 繼而把Client端通過內核傳遞過來的數據交給Server端的代碼進行處理, 從而完成一次進程間通信.

 

加上IBinder, BBinder, BpBinder, BpRefBase, 圖示變成了下面這個樣子:

1.3 Ixxx, BpInterface, BnInterface

前文我們看到了Android系統提供的很多封裝, 通過這些封裝, 進程與進程之間的數據管道已經建立完畢了, 類似於網絡傳輸中的數據鏈路層已經准備好了, 此時ClientServer已經可以盡情的傳遞數據了.

 

但依舊還有一個問題需要考慮: 類似於網絡傳輸中的應用層協議, Server/Client端也需要約定相應的協議, 例如Client端和Server端事先約定消息打印服務的cmd id0, Client端想要Serverprintf一個消息時, 就會發送數字0Server, Server收到0后知道應該去執行打印服務.

 

這種應用層的協議Android系統沒法幫你封裝, 需要Server/Client編寫者自己去約定, 不過Android(更確切的說是C++)提供了一種機制, 讓程序員可以更加方便的完成這種約定.

 

這種機制就是程序員事先定義一個接口類Ixxx, Ixxx中定義一個枚舉類型, 代表cmd id; 然后定義相應的接口函數, 每個接口函數會對應一個cmd id. 例如如下這接口類:

//frameworks/native/include/binder/IPermissionController.h

 

class IPermissionController : public IInterface

{

public:

    DECLARE_META_INTERFACE(PermissionController);  //暫時忽略, 這個地方也很重要, 不過主要是跟多進程間的通信相關

 

    virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;

 

    virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;

 

    virtual bool isRuntimePermission(const String16& permission) = 0;

 

    enum {

        CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,

        GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,

        IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2

    };

};

 

然后Client端的代碼會繼承Ixxx & BpRefBase, 實現Ixxx中定義的接口函數, 在接口函數中會調用mRemote->transact函數, 繼而把數據寫往/dev/binder節點.

Android系統中, 幾乎所有Client端的代碼都會雙繼承Ixxx & BpRefBase, 因此Android系統定義了一個模板類BpInterface, 方便這種雙繼承的動作.

下面我們看看代碼中BpInterface的定義以及IPermissionController的客戶端代碼:

//frameworks/native/include/binder/IInterface.h

template<typename INTERFACE>

class BpInterface : public INTERFACE, public BpRefBase

{

public:

                                BpInterface(const sp<IBinder>& remote);

 

protected:

    virtual IBinder*            onAsBinder();

};

 

 

//frameworks/native/libs/binder/IPermissionController.cpp

class BpPermissionController : public BpInterface<IPermissionController>

{

public:

    BpPermissionController(const sp<IBinder>& impl)

        : BpInterface<IPermissionController>(impl)

    {

    }

 

    virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid)

    {

        Parcel data, reply;

        data.writeInterfaceToken(IPermissionController::getInterfaceDescriptor());

        data.writeString16(permission);

        data.writeInt32(pid);

        data.writeInt32(uid);

        //remote()調用會返回mRemote這個變量

        remote()->transact(CHECK_PERMISSION_TRANSACTION, data, &reply);

        // fail on exception

        if (reply.readExceptionCode() != 0) return 0;

        return reply.readInt32() != 0;

    }

 

    ......

}

 

再來看看Server, Server端的代碼會繼承Ixxx & BBinder. 首先會實現BBinder中定義的onTransact函數, onTransact函數會中解析/dev/binder節點傳上來的數據, 並根據不同的數據調用Ixxx中定義的不同的接口函數. 而這些接口函數的實現都是在Server端的代碼中, Server端的代碼會實現Ixxx中定義的所有的接口函數.

類似Client, Server端的代碼幾乎都會雙繼承Ixxx & BBinder, 為了方便這種雙繼承動作, Android系統定義了一個模板類BnInterface.

相關實例代碼如下:

//frameworks/native/include/binder/IInterface.h

template<typename INTERFACE>

class BnInterface : public INTERFACE, public BBinder

{

public:

    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);

    virtual const String16&     getInterfaceDescriptor() const;

 

protected:

    virtual IBinder*            onAsBinder();

};

 

//frameworks/native/include/binder/IPermissionController.h

class BnPermissionController : public BnInterface<IPermissionController>

{

public:

    virtual status_t    onTransact( uint32_t code,

                                    const Parcel& data,

                                    Parcel* reply,

                                    uint32_t flags = 0);

};

 

//frameworks/native/libs/binder/IPermissionController.cpp

status_t BnPermissionController::onTransact(

    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

{

    switch(code) {

        case CHECK_PERMISSION_TRANSACTION: {

            CHECK_INTERFACE(IPermissionController, data, reply);

            String16 permission = data.readString16();

            int32_t pid = data.readInt32();

            int32_t uid = data.readInt32();

            bool res = checkPermission(permission, pid, uid);

            reply->writeNoException();

            reply->writeInt32(res ? 1 : 0);

            return NO_ERROR;

        } break;

 

    ......

}

        注意, BnPermissionController不能被實例化哦, 因此它繼承了IPermissionController, 但是IPermissionController中定義的checkPermission這個存虛函數BnPermissionController這個類中還是沒有定義

        一般情況下, 我們還需要寫一個Server端的cpp文件, 定義一個類, 繼承BnPermissionController, 然后實現Ixxx中所有的存虛函數, 然后就萬事大吉了.

 

前述的這些封裝是為了實現面向對象的通信方式而對協議層的封裝, 至此管道,協議都已經准備好, Client端和Server端就可以用面向對象的方式進行通信了.

 

2.   多進程通信

在第一章我們假設系統中只有2個進程, 這種情況下很多事情都變得簡單了, 例如: client端只管打開/dev/binder節點, 然后往節點傳送數據; server端只管監聽/dev/binder節點, 收到數據后就開始解析數據, 執行相應的服務.

 

但是真實系統中會有很多個進程, client端在往/dev/binder節點寫入數據時, 必須告訴binder驅動這些數據該送往哪個server進程; 同樣, 所有的server進程都會監聽/dev/binder節點, 這種監聽動作其實就是block/dev/binder節點上, 理想的情況應該是只有收到與本server相關的數據時, server進程才會被un-block並開始解析數據.

 

如何解決這個問題? Binder系統采用的辦法簡單來說就是handle! 這個handle其實是一個int32的整數, 類似於linux的文件描述符fd.

linux, 我們想與一個文件交互時, 調用open得到此文件的fd, 后面操作fd即可, 也就是說fd代表這個文件.

binder, client想與某個service通信, 首先會得到servicehandle, 后面操作此handle即可, 也就是handle代表這個service.

fdopen得到的一個隨機整數, handle是一個隨機整數, 只不過它不是用open得到, 而是從ServiceManager得到; 另外handle >=1. 為什么0不行? 因為0handle特殊用途, 是專門代表ServiceManager這個service.

 

還不是很明白? 關系, 接下來看看這個神秘的ServiceManger!

2.1  0handle/ServiceManager

servicemanagerAndroid中是一個獨立的進程, 它的實現文件是frameworks/native/cmds/servicemanager/service_manager.c , 它會被編譯成一個可執行文件servicemanager, init.rc中把它作為一個service運行.

 

servicemanager負責管理Android中所有Server進程, 它主要提供了兩個接口: addSerivcegetService.

Server進程會調用addSerivce, 把自己servicemanager注冊. 注冊的時候需要提供servicenameService基類(BBinder)

Client進程會調用getService(name), 通過nameservicemanager查詢Server, 最終的結果就是會獲取到name對應的Server線程的編號(handle).

    這里可以看出, nameservicemanager必須是唯一的, 否則servicemanager無法區分不同Server. 隨便提一下, 這個name字符串.

 

注意, servicemanager的角度來說, Server進程和Client進程都是自己的client. 它們在調用addSerivce/getService, 必須跨進程通信, 因為servicemanager / Server進程 / Client進程 這三者是不同的進程.

回想一下前述內容, 如果要跨進程通信, 本質上來說, 就是client進程得拿到Server的編號. Server進程/Client進程此時沒有任何手段能查詢servicemanager這個進程的編號(因為不知道向誰去查). 如何解決? 很簡單, servicemanager這個特殊進程的編號寫死, 置為0. 這樣當Server進程/Client進程想與servicemanager通信時, 直接通過0號即可.

 

既然servicemanager作為所有Server進程/Client進程的服務端, 那是不是應該提供相應的客戶端的封裝, 以便客戶端能方便與servicemanager通信呢? Android系統確實提供了這樣的封裝, 它就是frameworks/native/libs/binder/IServiceManager.cpp. 這個文件的細節現在就不看了, 等讀完本文的所有內容, 你自然就能理解了.

 

接下來, 我們詳細看看Server端是如何ServiceManger注冊以及Client如何獲取handle.

更詳細的說明整個過程, 我們必須先介紹內核Binder驅動中的幾個重要的數據結構, 它們 : binder_proc; binder_node; binder_ref; 在此之后, 介紹注冊服務(addService獲取服務(getService.

2.2 Binder驅動

代碼路徑

kernel/drivers/ (不同Linux分支路徑略有不同)

  - staging/android/binder.c

 

  - android/binder.c

 

Binder驅動是Android專用的,但底層的驅動架構與Linux驅動一樣。binder驅動在以misc設備進行注冊,作為虛擬字符設備,沒有直接操作硬件,只是對設備內存的處理。主要是驅動設備的初始化(binder_init),打開 (binder_open),映射(binder_mmap),數據操作(binder_ioctl).

2.2.1        binder_proc

1.1        中有提到, 不管Server進程還是Client進程, 會調用ProcessState::self().

 

ProcessState::self()open(/dev/binder), 這個open動作會導致驅動層創建一個binder_proc結構. 如下:

 

也就是, 每個用戶空間的進程, 如果想通過binder通信, 不管Server進程還是Client進程, 內核空間都會一個與之對應的數據結構binder_proc.

另外, 由於ProcessState設計為單例模式, 因此每個進程只會open一次/dev/binder, 也就是說每個進程只會存在唯一一個對應binder_proc.

 

如果說我們把binder通信想象成電力傳輸: 從一端傳到另外一端需要通過電線, binder_open相當於在內核空間創建了這樣一根電線, 通過這根電線, Client/Server可以交換數據了. binder_proc相當於這個電線的最外層包皮.

 

但是單有這個最外層包皮是無法傳輸的, 我們還需要電線里面的銅芯”, 這個銅芯就是下面介紹的binder_node.

2.2.2        binder_node

一根電線里面會有多跟銅芯”, 典型的是3根(三相交流. 同樣, 一個Server進程可以提供一個或者多個Service, 例如main_mediaserver.cpp這個進程就注冊MediaPlayerServiceCameraService多個服務. 每個服務就相當於一根銅芯”.

 

binder驅動層面, 每個服務都對應一個binder_node. 類似於銅芯線外層包皮”, 每個binder_node都是隸屬於對應進程binder_proc, 通過紅黑色進行管理, 代碼片段如下:

nodes就是本binder_proc所有binder_node根節點.

 

2.2.1》我們提到Server進程Client進程都會創建對應的binder_proc, 是不是它倆都會創建binder_node? 這個問題的根本在於進程中是否有注冊服務, 一般來講Server進程肯定會注冊服務的, 因此Server進程的binder_proc下會掛有一個或多個binder_node; Client進程如果是純粹的獲取服務, 那么它的binder_proc不會掛有binder_node, 有的Client進程即需要獲取服務, 會想它人提供自己的服務, 此時它的binder_proc任然會掛有binder_node.

 

一下binder_node里面會主要會存儲什么東西? 前文說過在服務的注冊過程中, 會傳遞servicenameBBinder, 這里BBinder其實就是Service實現代碼的基類指針, 通過它我們可以訪問Service所有實現函數(因為C++規定, 類指針可以指向它的任何派生類的對象). binder_node里面會存儲這個BBinder指針, 代碼如下:

ptrcookie都是用於存儲BBinder, 兩個稍微有點區別ptr == BBinder->getWeakRefs(); cookie == BBinder, 這個區別暫時不用去管它. 主要用到的是cookie.

 

存儲BBinder很有意義的. 設想一下, 服務端的代碼派生了BBinder, 然后實現了很多自己的功能函數, 客戶端與服務端通信的主要目的就是想調用服務端自己實現的這些功能函數, 只不過兩者屬於不同進程, 不可直接調用, 所以Android有了Binder這套通信機制.

 

前面我們說過, 客戶端要想服務端通信, 首先會拿到一個handle, 然后操作這個handle即可. 進一步來說, 客戶端會把這個handle傳給內核, 然后跟內核說要調用這個handle對應的服務的函數, 內核拿到這個handle之后, 如果能通過某種機制, 找到它對應的binder_nodehandle代表一個服務, 每個服務在內核層都會創建一個binder_node, 所以肯定能找到, 繼而就知道對應的binder_node->cookie, 也就是服務的BBinder, 然后通過BBinder能調用服務端的相應函數了.

實事上目前的Binder機制確實是這樣做的, handlebinder_node對應關系是靠下一節介紹的binder_ref維護.

 

還有一個值得思考的問題, binder_procopen創建的, binder_node在何時創建的呢? 答案是在注冊服務(addService時候.

2.2.3        binder_ref

2.2.2》說到當Server進程注冊服務的時候, 驅動層會創建對應的binder_node; 對應的, Client進程在獲取服務的時候, 驅動層會創建對應的binder_ref.

 

binder_ref主要目的是維護handlebinder_node之間的對應關系, 代碼如下:

desc就是handle, node指向對應的服務的binder_node.

 

binder_ref在獲取服務(getService時候創建的, Client獲取某個服務, 會把服務name傳給ServiceManager, 這個過程中, 驅動層會創建對應的binder_ref, 依靠ServiceManager通過name找到對應的binder_node, 賦值binder_ref->node; 然后驅動層會分配一個型數, 也就是desc; 然后這個desc反饋給Client進程, 也就是Client拿到handle.

 

反過來在看看ClientServer通信的過程. 假設Client已經拿到了Serverhandle, ClientServer交互時, 會把這個handle寫入內核. Binder驅動收到這個handle之后, 從當前進程(此時Clientbinder_proc找到與handle對應binder_ref, 繼而就找到了binder_ref->node, 這個node就是代指對應的服務, 從而完成ClientServer一次通信.

2.3 注冊服務(addService)

注冊服務指的Server進程把自己的某個服務注冊到ServiceManager里面, 此時Server進程是客戶端, ServiceManager進程是服務端. 兩者通過0handle通信.

 

首先必須意識到, ServiceManger也是一個進程, 因此它在驅動層存在一個與之對應的binder_proc; 另外這個進程也提供了自己的服務(addService/getService, 因此在驅動層存在一個與之對應的binder_node, 這個node名字叫binder_context_mgr_node. 是一個特殊的node, 整個binder驅動里面有且只能有一個binder_context_mgr_node. 這個node固定的與0handle對應, 也就是說任何時候, 只要驅動收到的handle0, 就一定會對應到binder_context_mgr_node, 就是為什么任何進程在與ServiceManager交互的時候直接使用0handle即可的根本原因.

 

我們在從需要注冊服務的Server進程看一下, 首先會得到ServiceManager代理IServiceManager, 這個過程其實就是把0handle層層封裝, 最后變成IServiceManager, 具體代碼細節請參考這里. 然后它會把自己的Service nameService BBinder指針通過IServiceManager寫入內核驅動, 意思就是: , ServiceManger進程, 把我的name “BBinder指針保存起來.

 

IServiceManager收到Service nameService BBinder指針, 會把它層層封裝, 變成一個數據包, 然后通過ioctl這個數據包寫入內核驅動. 注意IServiceManagerServiceManger進程在客戶端的代理, 也就是上述動作發生在注冊服務的Server進程.

 

驅動收到數據包之后, 把數據包拆分: 首先得到handle == 0, 得知這是一次與ServiceManger通信的請求; 然后拆分出具體動作, 得知一次服務添加過程(數據包中有特定的數據代表是添加還是獲取服務). 對於首次添加的服務, 在驅動層創建一個binder_node結構體, Service BBinder指針保存到這個結構體里面, 然后把這個結構體掛載到該服務隸屬的進程的binder_proc. 此時的圖示類似於下面這樣:

然后, 驅動層會ServiceManger進程對應的binder_proc創建一個binder_ref, 這個binder_ref指向剛剛創建的binder_node, 同時驅動層會計算出對應的handle值(計算的規則詳見后文的代碼分析, 此時圖示變成了這樣:

 

這里有一個疑問, 上面計算出來的handle有何意義? 前文說道客戶端想與服務端通信時, 首先會向ServiceManager查詢, 最終得到一個handle, 難道這里創建的handle就是以后給客戶端使用的? 答案No. 這里handle只是ServiceManager內部使用的(具體用途2.4說明, 客戶端拿到的handle沒有什么關系(客戶端如何獲取handle, 查看2.4: 獲取服務.

 

之后, 驅動層會把Service name(從數據包中拆解出來)和handle1剛剛計算出來的)封裝成數據包, 然后傳遞給用戶空間的ServiceManager進程. ServiceManager進程在收到數據包之后, 拆解出Service name“handle1”, 然后把它倆存儲起來. “Service name”“handle1ServiceManager是一一對應的關系, 並且都是唯一. 圖示如下:

 

上述就是注冊服務的宏觀邏輯, 如果想配合源代碼仔細研究, 網上的這篇文章寫的很好, 可以參考 : Binder系列5—注冊服務(addService)

2.4  獲取服務(getService)

理解注冊服務的過程, 來看獲取服務相當就比較容易了.

獲取服務的起點是Client知道服務的Service name, 終點是拿到與這個Service相對應handle. 下面細看一下這個流程.

 

Client首先也是獲取到ServiceManger代理IServiceManager, 然后通過IServiceManagerService name寫入驅動層, 驅動收到這個請求后, Service name傳給用戶空間的ServiceManager進程.

ServiceManager進程中, 由於已經存在着Service name“handle”一一對應關系, 因此ServiceManager進程可以通過“Service name”找到對應的handle, 然后把handle回傳給驅動層.

驅動層收到傳回的handle之后, ServiceManager進程對應的binder_proc紅黑樹中找到對應的binder_ref, 關鍵字就是“handle”.

找到binder_ref之后, 能找到它所指向的binder_node. 注意這個binder_node其實就是Client想要找的那個服務端, 也就是與“Service name”對應的那個服務端.

 

說道這里大家可能又有一個疑問, 難道這里描述的handle不是最終返回給Client的那個嗎? 答案是不是的. 這里handleServiceManager內部使用的, 是在注冊服務的過程中創建的(前文已經介紹如何創建的), 這個handle主要作用是為了通過Service name最終找到對應的binder_node.

 

Client需要handle怎么來的呢?

我們找到與“Service name”對應binder_node之后, 驅動層會創建一個binder_ref, 這個binder_ref指向剛剛那個binder_node, 創建binder_ref過程中會計算出一個handle(圖中handle2, 最終會把這個handle2反饋給Client. 圖示如下:

 

至此, Client就完成了通過Service name獲取handle整個過程, 之后Client可以通過這個handle2服務端通信了.

 

這里同樣只是宏觀描述了整個過程, 如果對代碼細節有興趣, 可以參考這篇文章: Binder系列6—獲取服務(getService)

 

關於handle的計算規則, 文中有一段總結比較經典:

handle值計算方法規律:

         每個進程binder_proc所記錄的binder_refhandle值是從1開始遞增的;

         所有進程binder_proc所記錄的handle=0binder_ref都指向service manager

         同一個服務的binder_node在不同進程的binder_refhandle值可以不同;

3.   Binder數據傳輸與解析

Binder通信一般是兩個進程交互, 示意圖如下:

Client會把請求寫入驅動層, Server則會從驅動層讀取請求, 然后執行相應的服務. 下文分別闡述兩種情況的細節.

3.1             Client把數據寫入驅動層

數據傳輸涉及到兩部分 : 數據傳輸的路徑和數據包的封裝/解封過程.

下圖完整了顯示了一次從上至下的數據傳輸流程. 宏觀上來說: 用戶空間, 數據是逐步封裝, 最終變成一個binder_write_read數據包; 內核空間, 數據是逐步解封, 最終生成了一個binder_transaction事物.

         BpxxxFunc客戶端的實現代碼, 這里有兩種典型情況:

         一種普通的客戶端, 例如BpInputFlinger:: doSomething , 這種情況下會有3主要的數據: code, 一般enum, 意思是想調用服務端的哪個服務(也就是服務端的哪個函數); data , 一個parcel的數據, 代表想傳給服務端的數據; reply, 也是一個parcel, 代表想從服務端獲得的返回值.

         一種是ServiceManager客戶端, 例如BpServiceManager:: addService, 這種情況與普通客戶端的唯一不同之處在於, 會在data這個parcel里面多加入一個flat_binder_object數據. 是添加服務, flat_binder_object里面存儲是服務BBinder指針; 如果是獲取服務, flat_binder_object里面存儲的是驅動反饋上來的handle.

         BpBinder::transact, binder框架層的實現. 里面有一個mHandle, 也就是前文所說的通過ServiceManager獲取到的服務端的handle. mHandle在創建BpBinder對象時賦值的.

         IPCThreadState.writeTransactionData, 同樣binder框架層的實現. 它首先會創建一個binder_transaction_data, 前面傳下來的數據都寫入到這個結構體里面. 然后它會在binder_transaction_data加入BC_XXX, 封裝mOut.

BX_XXX是與binder驅動交互的協議, 其中BC_XXX代表用戶空間到驅動層; BR_XXX代表驅動層到用戶空間.

BC_XXX常見的就是BC_TRANSACTION, 代表ClientBinder驅動發送請求數據. 其它取值參見BC_PROTOCOL.

         IPCThreadState.talkWithDriver, 會把前一環創建的mOutmIn兩者是同一種數據類型, 只不過mOut里面填充很多數據, mIn相當於一個空的存儲池. 驅動層最終會把反饋給用戶空間的數據寫入mIn)封裝成binder_write_read, 然后通過ioctl與驅動層交互. binder_write_read數據包會傳入驅動層.

         驅動收到收到用戶空間的binder_write_read數據包后會依次解析數據包的內容, 然后進行相應的處理, 這里細看了.

3.2             Server端從驅動層讀取數據

         Server端注冊完服務之后, block驅動層, 等待客戶端自己發送請求.

客戶端發送完請求, 最終會生產一個binder_transaction事物, binder_transaction.work加入到對應的server的隊列, 這個隊列可以是thread->todo, 可以是proc->todo(第4會詳述這幾個概念. 然后調用wake_up_interruptible喚醒服務端.

服務端被喚醒后, 從調出block狀態, 然后thread->todo或者proc->todo取出一個binder_work.

         binder_workbinder_transaction內部的一個數據, 因此使用container_of可以獲得binder_work對應binder_transaction指針.

         通過binder_transaction.buffer的數據(這些數據是客戶端傳過來的, 可以構建一個binder_transaction_data數據結構.

         binder_transaction_data最終會被封裝成binder_write_read, 然后返回給用戶空間.

         用戶空間在收到數據后, 調用IPCThreadState::executeCommand解析數據包, 拆分BR_XXX, BR_XXX最常見的值是BR_TRANSACTION, 代表Binder驅動向Server端發送請求數據. 其它取值參見BR_PROTOCOL.

         如果BR_XXXBR_TRANSACTION, 會從數據包binder_transaction_data取出tr.cookie, 然后將其轉換為<BBinder*>(tr.cookie), 然后調用<BBinder*>(tr.cookie)->transact函數, 傳入相應的參數(tr.code, buffer, &reply, tr.flags).

這些參數的具體意義是:

         tr.code代表要調用服務端的哪個函數

         buffer一個parcel數據包, 從客戶端傳過來的

         reply也是一個parcel數據包, 代表需要回傳給客戶端的數據. 此時它是空的, 內容由服務端填充.

         tr.flags標志位, 定義參考transaction_flags, 最常見的取值是TF_ONE_WAY

         Binder.cpp實現了一個默認的transact函數, 函數里面會調用onTransact. onTransactBBinder定義的一個接口, 任何服務端都必須繼承並實現onTransact函數

         服務端的BnXXX::onTransact調用.

         BnXXX::onTransact函數, 根據tr.code, 調用不用的實現函數BnXXXFunc

4.   Binder事物

每一次數據傳輸, Binder驅動層都會生成一個事物binder_transaction, 把這個事物掛載到相應的鏈表todo. 所謂相應的鏈表的意思是: 這個事物應該由誰來處理, 掛載到誰的鏈表上. 比如當客戶端向服務端發送一個請求時, 客戶端會先把數據寫入驅動層, 驅動生產一個事物binder_transaction, 這個事物顯然應該由對應的服務端來處理, 因此這個事物會掛載到對應的服務端的todo下面.

 

本章主要介紹與binder事物相關的幾個數據結構, 它們binder_transaction / binder_work / binder_thread / binder_proc->todo.

binder_transaction

定義文件: drivers/android/binder.c # L355

類型

成員變量

解釋

int

debug_id

用於調試

struct binder_work

work

binder工作類型

struct binder_thread *

from

發送端線程

struct binder_transaction *

from_parent

上一個事務

struct binder_proc *

to_proc

接收端進程

struct binder_thread *

to_thread

接收端線程

struct binder_transaction *

to_parent

下一個事務

unsigned

need_reply

是否需要回復

struct binder_buffer *

buffer

數據buffer

unsigned int

code

通信方法,比如startService

unsigned int

flags

標志,比如是否oneway

long

priority

優先級

long

saved_priority

保存的優先級

kuid_t

sender_euid

發送端uid

這個結構體是在binder_transaction()函數中創建的

         binder_work代表此次事物的具體類型, 取值范圍后文詳述

         debug_id:是一個全局靜態變量,每當創建一個binder_transactionbinder_nodebinder_ref對象,則++debug_id

         fromto_thread是一對,分別是發送端線程和接收端線程

         from_parentto_parent是一對,分別是上一個和下一個binder_transaction,組成一個鏈表

         執行binder_transaction()方法過程,當非onewayBC_TRANSACTION時,則設置當前事務t->from_parent等於當前線程的transaction_stack. transaction_stack代表當前線程正在處理的事物.

         執行binder_thread_read()方法過程,當非onewayBR_TRANSACTION時,則設置當前事務t->to_parent等於當前線程的transaction_stack

         binder_buffer用來存儲數據的, 的細節在《Binder Buffer一節中詳述

binder_work

定義文件 : drivers/android/binder.c # L214

struct binder_work {

    struct list_head entry;

    enum {

        BINDER_WORK_TRANSACTION = 1,

        BINDER_WORK_TRANSACTION_COMPLETE,

        BINDER_WORK_NODE,

        BINDER_WORK_DEAD_BINDER,

        BINDER_WORK_DEAD_BINDER_AND_CLEAR,

        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,

    } type;

};

binder_work.type設置時機:

  • binder_transaction()
  • binder_thread_write()
  • binder_new_node()

binder_thread

binder_thread結構體代表當前binder操作所在的線程.

類型

成員變量

解釋

struct binder_proc *

proc

線程所屬的進程

struct rb_node

rb_node

 

int

pid

線程pid

int

looper

looper的狀態

struct binder_transaction *

transaction_stack

線程正在處理的事務

struct list_head

todo

將要處理的鏈表

uint32_t

return_error

write失敗后,返回的錯誤碼

uint32_t

return_error2

write失敗后,返回的錯誤碼2

wait_queue_head_t

wait

等待隊列的隊頭

struct binder_stats

stats

binder線程的統計信息

         transaction_stack代表當前線程正在處理的事物

         todo代表屬於本線程的事物鏈表

         wait等待隊列頭, 假設當前線程沒有事物需要處理, 線程會blockwait, 直到有人喚醒它.

         looper代表當前線程的狀態, 具體意義Binder Thread一節中詳述.

可能還對binder_thread具體意義摸不着頭腦, 關系, 后面Binder Thread會專門講解它. 這里我們只關注與Binder事物相關的部分.

binder_proc.todo

binder_proc前文描述過, 每一個需要Binder通信的進程在驅動層都會有一個對應的binder_proc. 這個結構體里面有一個元素如下:

struct binder_proc {

    ......

    struct list_head todo;

    wait_queue_head_t wait;

    ......

};

         todo代表屬於該進程的事物的鏈表

         wait等待隊列, 如果進程中沒有需要處理的事物, 則會blockwait.

thread.todo or proc.todo

既然binder_thread.todobinder_proc.todo下面都可以掛載事物, 到底應該掛載到哪個下面?

  • reply的過程會找到target_thread.todo
  • reply則一般找到target_proc.todo
  • 對特殊的嵌套binder call會根據transaction_stack來決定是插入事務到目標線程還是目標進程

5.   Binder Thread

Note : 可閱讀http://blog.csdn.net/kc58236582/article/details/51744601這篇文章, 寫得貌似比這里還清晰些.

5.1             簡介

Binder通信C/S模式, 服務端會等待客戶端向自己發起請求. 所謂等待, 就是服務端會有一個循環looper, 在循環里面不停的查看是否有請求.

 

了不讓循環卡死Server進程, 一般會在進程里面創建一個線程, 然后把這個循環丟到線程里面去運行. 這個線程就是這里的binder_thread.

 

Binder通信機制里, 客戶端與服務端之間的通信是在專門的IPC通信線程中進行的. 這些線程構成一個線程池. 線程的創建和銷毀是在用戶空間進行的, 而對線程的控制是在驅動層進行的, 即驅動控制線程池中線程的生命, 而線程本身則是運行在用戶空間的.

5.2             數據結構

與線程池相關的幾個變量設置在struct binder_proc結構體中:

struct binder_proc {

  1.   ...  
  2.     int max_threads;//max thread  
  3.     int requested_threads;//  
  4.     int requested_threads_started;//requested thread seq No  
  5.     int ready_threads;//available for use  
  6.     ...  
  7. };  

         max_threads代表當前進程最多可以創建多少個線程包括5.3所說的那兩個默認線程), 用戶空間可以通過ioctl命令BINDER_SET_MAX_THREADS來設置這個值, 默認情況下是15.

         ready_threads表示當前線程池中有多少可用的空閑線程.

         requested_threads請求開啟線程的數量.

         requested_threads_started表示當前已經接受請求開啟的線程數量

5.3             默認情況下會有幾個thread

Server代碼中(例如main_mediaserver.cpp, 一般我們會看到如下兩句:

  1. ProcessState::self()->startThreadPool();  
  2. IPCThread::self()->joinThreadPool(); 

第一句話會新創建一個線程, 在線程里面一個looper, looper里面循環讀取和解析binder驅動傳上來的數據.

 

第二句話在進程的主線程里放一個looper, 然后looper里面循環讀取和解析binder驅動層傳上來的數據. 這個動作有兩個目的: 一是相當於在進程main函數的最后了一個while(1), 防止進程退出. 第二個目的就是相當於多創建了一個looper.

 

總結來看, 默認情況下, 會有兩個thread處理驅動層上報的數據. 一般情況下這已經足夠了.

 

如果通信量很大, 可能會出現thread處理不過來的情況, 向超市排隊的人多了, 收銀的忙不過來, 時候就需要多開幾個收銀窗口.

那么何時創建新的thread? 參考下節.

 

另外還有一個題外話, 網上有人說上面第二句代碼是多余的, 去掉也可以運行. 本質在於, 如果有其它代碼可以block進程不讓其退出, 這里是可以去掉, 帶來影響就是默認thread只有1, 相當於通信管道變窄了.

5.4             何時會創建新的thread

當有多個並發的IPC請求時,可能會觸發內核增加更多的IPC通信線程來服務這些請求。具體的情境如下代碼所示:

  1. if (proc->requested_threads + proc->ready_threads == 0 &&  
  2.         proc->requested_threads_started < proc->max_threads &&  
  3.         (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |  
  4.          BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */  
  5.          /*spawn a new thread if we leave this out */) {  
  6.         proc->requested_threads++;  
  7.         binder_debug(BINDER_DEBUG_THREADS,  
  8.                  "%d:%d BR_SPAWN_LOOPER\n",  
  9.                  proc->pid, thread->pid);  
  10.         if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))  
  11.             return -EFAULT;  
  12.         binder_stat_br(proc, thread, BR_SPAWN_LOOPER);  
  13.     }  

在請求的線程數和空閑的線程數為零, 且已經請求並開啟的線程數小於線程池的最大允許線程數量時, 驅動會向用戶空間發送一個BR_SPAWN_LOOPER命令以開啟新的接收線程來處理請求。

 

我們再來看看用戶空間對這個命令的處理, IPCThreadState::executeCommand相關代碼如下:

  1. case BR_SPAWN_LOOPER:  
  2.     mProcess->spawnPooledThread(false);  
  3.     break;  

直接調用ProcessStatespawnPooledThread函數創建一個新的線程. 注意這里傳入的參數是false, 稱之為binder線程. 5.3中介紹的兩個線程, 它們都沒有顯示指定參數, 從而會使用參數的默認值true, 因此倆稱為binder線程.

 

binder主線程和非主線程有何區別? 詳見下節.

5.5             biner線程與非主線程區別

用戶空間創建線程的函數是ProcessState::spawnPooledThread, 代碼如下:

  1. void ProcessState::spawnPooledThread(bool isMain)  
  2. {  
  3.     if (mThreadPoolStarted) {  
  4.         String8 name = makeBinderThreadName();  
  5.         ALOGV("Spawning new pooled thread, name=%s\n", name.string());  
  6.         sp<Thread> t = new PoolThread(isMain);  
  7.         t->run(name.string());  
  8.     }  
  9. }  

這里會借用AndroidThread機制創建一個線程PoolThread, 然后調用run運行此線程.

 

線程的threadloop里面, 調用joinThreadPool:

  1. protected:
  2. virtual bool threadLoop()
  3. {
  4.         IPCThreadState::self()->joinThreadPool(mIsMain);
  5.         return false;
  6.     }
  7.  
  8.     const bool mIsMain;
  9. };

注意這個threadLoop里面returnfalse, 意味着一旦joinThreadPool結束運行, 整個線程就會退出. 因此joinThreadPool里面應該是一個while循環(也就是前文所說的looper, 一般情況下不會退出.

 

joinThreadPool實現函數如下:

  1. void IPCThreadState::joinThreadPool(bool isMain)  
  2. {  
  3.     LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());  
  4.   
  5.     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);  
  6.       
  7.     // This thread may have been spawned by a thread that was in the background  
  8.     // scheduling group, so first we will make sure it is in the foreground  
  9.     // one to avoid performing an initial transaction in the background.  
  10.     set_sched_policy(mMyThreadId, SP_FOREGROUND);  
  11.           
  12.     status_t result;  
  13.     do {  
  14.         processPendingDerefs();  
  15.         // now get the next command to be processed, waiting if necessary  
  16.         result = getAndExecuteCommand();  
  17.   
  18.         if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {  
  19.             ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",  
  20.                   mProcess->mDriverFD, result);  
  21.             abort();  
  22.         }  
  23.           
  24.         // Let this thread exit the thread pool if it is no longer  
  25.         // needed and it is not the main process thread.  
  26.         if(result == TIMED_OUT && !isMain) {//isMainfalse,並且是timeout的時候線程退出  
  27.             break;  
  28.         }  
  29.     } while (result != -ECONNREFUSED && result != -EBADF);  
  30.   
  31.     LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",  
  32.         (void*)pthread_self(), getpid(), (void*)result);  
  33.       
  34.     mOut.writeInt32(BC_EXIT_LOOPER);  
  35.     talkWithDriver(false);  
  36. }  

isMaintrue, 代表binder線程; false代表主線程. 代碼分析可知它們的區別如下:

         26, 說明主線程是不會退出的, 一直存在. 但是非主線程result == TIMED_OUT時候會退出.

進一步來說, 當驅動發現沒有足夠的線程處理數據, 發送BR_SPAWN_LOOPER用戶空間申請創建線程. 數據處理完畢之后, 這些新創建的非主線程就會退出了.

         5, 線程會往驅動寫入BC_ENTER_LOOPER; 非主線程寫入的是BC_REGISTER_LOOPER.

BC_ENTER_LOOPER會讓驅動層的looper進入BINDER_LOOPER_STATE_ENTERED狀態; BC_REGISTER_LOOPER讓驅動層的looper進入BINDER_LOOPER_STATE_REGISTERED狀態.

 

另外, BC_REGISTER_LOOPER還會更新驅動層的幾個數據:

  1. case BC_REGISTER_LOOPER:  
  2.               
  3.             } else {  
  4.                 proc->requested_threads--;  
  5.                 proc->requested_threads_started++;  
  6.             }  
  7.             thread->looper |= BINDER_LOOPER_STATE_REGISTERED;  
  8.             break;  

驅動會更新proc->requested_threads_started來統計當前已經請求開啟並成功開啟的線程數量,這個值將作為判斷線程池是否已經滿的依據

5.6             looper的狀態遷移

驅動層, binder_thread有一個元素用於表示threadlooper狀態:

  1. struct binder_thread {  
  2.     struct binder_proc *proc;  //線程所屬的進程  
  3.     struct rb_node rb_node;  //紅黑樹的結點,進程通過該結點將線程加入到紅黑樹中  
  4.     int pid; //線程的pid  
  5.     int looper;  //線程所處的狀態  

 

looper取值范圍如下

  1. enum {
  2.     BINDER_LOOPER_STATE_REGISTERED  = 0x01, // 創建注冊線程BC_REGISTER_LOOPER
  3.     BINDER_LOOPER_STATE_ENTERED     = 0x02, // 創建主線程BC_ENTER_LOOPER
  4.     BINDER_LOOPER_STATE_EXITED      = 0x04, // 已退出
  5.     BINDER_LOOPER_STATE_INVALID     = 0x08, // 非法
  6.     BINDER_LOOPER_STATE_WAITING     = 0x10, // 等待中
  7.     BINDER_LOOPER_STATE_NEED_RETURN = 0x20, // 需要返回
  8. };

 

looper狀態遷移的時機:

         收到 BC_REGISTER_LOOPER,則線程狀態為BINDER_LOOPER_STATE_REGISTERED;

         收到 BC_ENTER_LOOPER,則線程狀態為 BINDER_LOOPER_STATE_ENTERED;

         收到 BC_EXIT_LOOPER, 則線程狀態為BINDER_LOOPER_STATE_EXITED;

 

其他3個狀態的時機:

         BINDER_LOOPER_STATE_WAITING:

         當停留在binder_thread_read()wait_event_xxx過程, 則設置該狀態;

         BINDER_LOOPER_STATE_NEED_RETURN:

         binder_get_thread()過程, 根據binder_proc查詢不到當前線程所對應的binder_thread,會新建binder_thread對象;

         binder_deferred_flush()過程;

         BINDER_LOOPER_STATE_INVALID:

         binder_thread創建過程狀態不正確時會設置.

5.7             最多能創建多少個thread

線程池的大小可以設置, 如果沒有主動去設置這個大小,則默認大小為15,如下代碼所示:

  1. static int open_driver()  
  2. {  
  3.     int fd = open("/dev/binder", O_RDWR);  
  4.     if (fd >= 0) {  
  5.         ...  
  6.         size_t maxThreads = 15;  
  7.         result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);  
  8.         if (result == -1) {  
  9.             ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));  
  10.         }  
  11.     } else {  
  12.         ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));  
  13.     }  
  14.     return fd;  
  15. }  

 

  1. ProcessState::self()->setThreadPoolMaxThreadCount(4);  

setThreadPoolMaxThreadCount可以修改線程池的大小, 例如上述代碼把線程池的大小設置為4. 意味着該進程最多只能創建4線程. 注意線程池的大小不包括線程, 也就是5.3節介紹2主線程不占用線程池空間. 所以總體來說該進程最多可以有6線程用於處理數據.

6.   Binder Buffer

ProcessState構造函數如下:

  1. ProcessState::ProcessState()
  2.     : mDriverFD(open_driver())
  3.    ......
  4. {
  5.     if (mDriverFD >= 0) {
  6.         // mmap the binder, providing a chunk of virtual address space to receive transactions.
  7.         mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
  8.         if (mVMStart == MAP_FAILED) {
  9.             // *sigh*
  10.             ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
  11.             close(mDriverFD);
  12.             mDriverFD = -1;
  13.         }
  14.     }
  15.  
  16.     LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
  17. }

主要2事情:

         17open(/dev/binder), 這個好理解, 目的就是了和binder驅動交互嘛

         22, 驅動層mmapBINDER_VM_SIZE大小的空間(#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))為何要mmap?

 

mmap意味着用戶空間和內核空間共享同一塊物理內存, 它們在交互數據時不用copy_to_user. 這個特性主要是用於驅動層向用戶空間傳遞數據的過程. 具體來講就是 : Client端向Server端發送請求, 請求數據會先copy_from_user到內核, 然后內核把這個數據交給Server端時就會用到上面的共享內存, 不會copy_to_user; 反過來, Server端處理完請求, 打算把結果返回給Client端時, Server端也會先通過copy_from_user的方式把數據交給內核, 然后內核用共享內存的方式把數據返回給Client.

 

關於這塊的邏輯, 可以進一步看兩個代碼.

         首先看看Server端是如何與內核共享的:

IPCThreadState::executeCommandServer端負責處理請求的, 它里面有如下一段:

Server端的buffer是直接用的內核傳上來的tr.data.ptr.buffer作為地址的. freeBuffer表示上層用完這個數據后, 回調freeBuffer把占用的空間釋放掉, 否則共享的1M空間很快會被擠爆了.

         其次看看Client端是如何與內核共享的:

IPCThreadState::waitForResponse代表Client端請求Server處理請求並等待反饋結果, 里面有如下一段:

邏輯同上, 不在贅述.

 

從上可知binder一次通信過程只發生一次用內存拷貝。這也是Binder優勢之一,普通IPC通信機制, 一般都會有2數據拷貝(Client端進程空間拷貝到內核空間,再由內核空間拷貝到Server進程空間,會發生兩次拷貝

對於進程和內核虛擬地址映射到同一個物理內存的操作是發生在數據接收端,而數據發送端還是需要將用戶態的數據復制到內核態。到此,可能有讀者會好奇,為何不直接讓發送端和接收端直接映射到同一個物理空間,那樣就連一次復制的操作都不需要了,0次復制操作那就與Linux標准內核的共享內存的IPC機制沒有區別了,對於共享內存雖然效率高,但是對於多進程的同步問題比較復雜,而管道/消息隊列等IPC需要復制2次,效率較低。這里就不先展開討論Linux現有的各種IPC機制跟Binder的詳細對比,總之Android選擇Binder的基於速度和安全性的考慮。

 

下面這圖是從Binder在進程間數據通信的流程圖,從圖中更能明了Binder的內存轉移關系。

首先發送把數據地址傳入內核; 然后在內核的binder_transaction函數調用binder_alloc_buf共享的內存中分配一塊binder_buffer, 然后調用copy_from_user發送放的data用戶空間拷貝到內核的binder_buffer.data; 然后這塊binder_buffer傳遞給數據接收方, 由於接收方與內核共享同一塊物理內存, 因此接收方訪問data就不需要在做copy動作了.

 

注意上述的一次拷貝只是針對data. 對於BINDER協議targetcode這些字段, 發送方會copy_from_user, 接收方copy_to_user. 原因是這些字段數據量很少, 兩次拷貝不會有太大影響.

7.   flat_binder_object的本質

7.1             第一印象 : flat_binder_object用於注冊服務和獲取服務

從前面的章節我們知道flat_binder_object主要是用於服務的注冊和服務的獲取:

         當我們向ServiceManager注冊服務時, 會有一個writeStrongBinder的動作, 這個動作會把代表服務的BBinderIBinder是基類, 指向BBinder)封裝成flat_binder_object, 並最終傳入驅動層. 代碼如下:

         驅動層收到flat_binder_object, 會創建這個服務對應的binder_node. 代碼如下:

         當我們向ServiceManager獲取服務時, 驅動層會把handle值封裝成flat_binder_object對象反饋給用戶空間, 用戶空間通過readStrongBinder的動作即可拿到handle, 並創建基於handleBpBinder, 這個BpBinder就是服務在客戶端的代理了. 用戶空間的代碼如下:

至此, flat_binder_object給人的第一個感覺是用於注冊服務和獲取服務: 注冊服務時它被傳入驅動層, 里面包含代表服務的BBinder; 獲取服務時它由驅動層創建, 然后返回給用戶層, 用戶層通過其中的handle值創建BpBinder, 之后就可以通過這個BpBinder與服務端通信了.

7.2             匿名binder通信

既然flat_binder_object給人的感覺是用於注冊服務和獲取服務, 那我們先思考一下服務的注冊與獲取是否一定要與ServiceManager掛鈎? 答案是否定的!

 

所謂注冊服務, 其實就是讓驅動層創建一個與服務對應的binder_node; 所謂獲取服務, 其實就是要拿到服務的handle. 這兩個過程並不一定要通過ServiceManager, 它們只與writeStrongBinderreadStrongBinder有關: 任何進程, 只要它調用了writeStrongBinder, 並把自己服務的BBinder傳給writeStrongBinder, 驅動層就會為這個服務創建一個binder_node. 並且驅動層會立即創建一個flat_binder_object對象, 里面包含了handle, 然后把這個對象返回給用戶空間, 此時用戶空間只要調用readStrongBinder就能拿到服務的handle, 繼而就可以與服務端通信了.

 

驅動層的關鍵代碼如下: 第一個紅框創建與服務對應的binder_node; 第二個紅框立馬創建一個包含handleflat_binder_object

 

這種不通過ServiceManager進行服務注冊與獲取的方式稱作匿名binder通信, 它的意義是服務只提供給某個特定的客戶端進程, 只有這個進程才能與本服務通信. 而通過ServiceManager注冊的服務則可以被任何進程獲取.

 

從代碼的角度來說, 其實匿名服務的注冊與ServiceManager服務注冊時同一套邏輯, 唯一的不同之處在於匿名服務注冊時, target_proc是那個特定的客戶端進程; ServiceManager服務注冊時, target_procservicemanger進程.

 

目前已知的匿名服務注冊主要用於APK進程與ActivityManagerService之間.

7.3             flat_binder_object只能用於服務注冊和獲取嗎

如題, flat_binder_object給人的感覺貌似就是用於服務注冊和獲取的, 它的使用模式也很固: 對於注冊服務的進程, 它通過writeStrongBinder生成一個flat_binder_object並傳入驅動層; 驅動層創建服務對應的binder_node並生成一個包含handle值的flat_binder_object對象反饋給用戶空間; 對於獲取服務的進程, 則可通過readStrongBinder從內核傳上來的flat_binder_object中拿到handle, 並用這個handle創建一個BpBinder.

 

flat_binder_object只能這樣用嗎? writeStrongBinder只能被服務端進程調用嗎? readStrongBinder是不是又只能被客戶端進程調用呢?

其實不然, flat_binder_object的本質是它代表着一個服務, writeStrongBinderreadStrongBinder只是在不同進程間傳遞服務的方法. 注意writeStrongBinderreadStrongBinder是成對出現的, 任何兩個進程可以通過writeStrongBinder->readStrongBinder的方式傳遞flat_binder_object這個服務. 即使是兩個客戶端進程! 例如某個提供服務的進程Aflat_binder_object傳遞給了客戶進程B, 客戶進程B可以直接通過writeStrongBinder->readStrongBinder把這個flat_binder_object傳遞給客戶進程C, 然后客戶進程C就可以與服務進程A通信了. 甚至客戶進程B也可以通過writeStrongBinder->readStrongBinder這個flat_binder_object傳遞給服務進程A, 然后客戶進程A就可以調用自己提供的服務了(只是這個調用不需要在通過binder, 直接是進程內的函數調用)!

 

好像不是很好理解是嗎? 那我們在從代碼的角度來分析一下. 一個服務可以存在於不同的進程, 只是它存在的形式略有不同: 在服務進程中, 它是BBinder; 在客戶進程中, 它是BpBinder. BBinderBpBinder都是繼承於IBinder, 因此不管在服務進程還是客戶進程的代碼中, 通常都用IBinder指針代表服務. 因此不管是服務進程還是客戶進程, 當你想把某個服務傳遞給另外一個進程時, 你只管以IBinder為參數調用writeStrongBinder就可以了, writeStrongBinder中會做如下處理:

只有你傳遞的不是非法指針(binder != NULL, writeStrongBinder就會根據情況自動生成的不同的flat_binder_object對象: 如果binder->localBinder()的返回值為空, 說明此時是在客戶端進程調用的writeStrongBinder, 這種情況下生成的flat_binder_objectBINDER_TYPE_HANDLE類型的, 而且既然是客戶端進程, 那肯定能拿到handle值(proxy->handle(); 如果binder->localBinder()的返回值不為空, 則說明此時實在服務端進程調用writeStrongBinder, 這種情況下生成的flat_binder_objectBINDER_TYPE_BINDER類型的, 而且既然是服務端進程, 那肯定能知道服務的BBinder指針, 把這個指針賦值給obj.binderobj.cookie.

 

上面代碼中參數binderIBinder類型的, 為什么通過binder->localBinder()就能知道此時到底處於服務端進程還是客戶端進程呢? localBinderIBinder這個基類定義的一個接口函數, 基類中有一個默認實現:

BBinder重載了這個實現, 但是BpBinder沒有重載. BBinder重載的代碼如下:

前面也說過, 一個服務在服務端進程是以BBinder的形式存在的, 在客戶端進程是以BpBinder的形式存在的. 因此IBinder->localBinder()如果是在服務進程, 則代表的是調用BBinder->localBinder(); 如果是在客戶進程, 則代表的是調用BpBinder->localBinder(). 所以通過localBinder()的返回值, 就能知道IBinder對象到底是處於服務進程還是客戶進程.

 

當驅動層收到flat_binder_object, 會根據它類型的不同區別對待:

         如果fp->typeBINDER_TYPE_BINDER類型的, 毫無疑問, 此時是服務端進程想把這個的服務傳遞某個客戶端進程. 如果這個服務對應的binder_node還不存在, 驅動層會先創建它, 然后反饋給客戶端進程一個包含handle、類型為BINDER_TYPE_HANDLEflat_binder_object. 客戶端通過readStrongBinder來獲取和解析這個object.

         如果fp->typeBINDER_TYPE_HANDLE類型的, 那此時代表某個客戶端進程想把handle這個服務傳遞給另外一個進程, 另外的這個進程即可以是某個其它的客戶端進程, 也可以是handle這個服務所在的服務端進程:

         如果ref->node->proc == target_proc, 則代表想把handle這個服務傳遞給這個服務所在的服務端進程. 此時驅動層會生產一個BINDER_TYPE_BINDER類型的flat_binder_object並反饋給用戶空間, 服務端進程通過readStrongBinder既可以獲取和解析這個object.

         如果是else, 則代表想把handle這個服務傳遞某個其它的客戶端進程. 此時驅動層會生產一個BINDER_TYPE_HANDLE類型的flat_binder_object並反饋給用戶空間, 其它的客戶端進程同樣通過readStrongBinder獲取和解析這個object.

 

OK, 既然用戶空間都是通過readStrongBinder獲取和解析object, 那我們看看這個readStrongBinder的細節:

代碼清楚的顯示, 根據flat->type的不同, 會生產不同的對象: 如果typeBINDER_TYPE_BINDER, 說明某人想把服務回傳給服務本身所在的進程, 此時根據flat->cookie生成一個IBinder對象即可, 注意此時IBinder其實是代表BBinder; 如果typeBINDER_TYPE_HANDLE, 說明某人想把服務傳遞給某個客戶端進程, 此時根據flat->handle生成一個IBinder對象(getStrongProxyForHandle中會創建一個BpBinder, 此時IBinder其實是代表BpBinder.

 

說了這么多, 不知道自己表達清楚沒有. 對於flat_binder_object的本質, 我們可以下結論了.

7.4             結論

對於flat_binder_object的本質, 我們的結論是:

一個flat_binder_object代表一個服務, 它的主要作用是在不同的進程之間傳遞服務, 以便不同的進程都可以訪問這個服務. 傳遞的方法是writeStrongBinderreadStrongBinder, 任何一個src進程如果想把服務傳遞給一個dst進程, src進程只需調用writeStrongBinder, 然后dst進程馬上通過readStrongBinder就可以拿到這個服務. 服務在服務端進程中是以BBinder的形式存在的, 在所有的客戶端進程中都是以BpBinder的形式存在的.

 

另外還需意識, 傳遞服務的目的主要是讓其它進程訪問此服務, 但這不是唯一功能. 很多地方都會用IBinder作為key構建map, 此時傳遞服務的目的就是只是傳遞一個普通的數據(就像傳遞一個int型參數一樣).

8.   Bindersp的關系

sp指的是strongpoint.

 

問題源自全志的在BootAnimation里面做的一件事情: MediaPlayer類播放視頻, 代碼如下:

 

這段代碼播放視頻沒有問題, 出題出在視頻播放完畢后, 做了這樣一個賦值:

原意是想通過賦值為NULL, 執行MediaPlayer()的析構函數, 從而釋放MediaPlayer.

mPlayersp類型的, sp類型的賦NULL, decStrong, 也就是把引用計數減1. 當引用計數減到0, 系統會做delete對象, 進而執行對象的析構函數. 所以單從上面的代碼來看, mPlayer = NULL貌似可以達到執行析構的效果.

 

但實際情況下卻沒有, 因此在代碼中加了兩個ALOGD, 把引用計數打印出來看看.

第一次new動作之后, 結果 : D/BootAnimation( 2133): startBootMedia, sc(1), wc(2)

第二次setDataSource, 結果 : D/BootAnimation( 2133): startBootMedia : setDataSource, sc(2), wc(4)

 

看來是因為setDataSource把引用計數加1了(總數為2了), mPlayer = NULL只會decStrong一次, 所以無法執行析構函數.

 

為什么setDataSource會把引用計數加1?

因為MediaPlayer實現了一個Binder服務端 : BnMediaPlayerClient. setDataSource過程中, 會通過writeStrongBinder的方式把這個服務端傳遞給MediaPlayerService. MPS會通過readStrongBinder的方式拿到這個服務的客戶端代理, 后面MPS要通過這個代理向MediaPlayer進程反饋播放時的一些消息(例如是否暫停, 是否eos, 是否遇到錯誤), 這個過程陳notify.

 

回到引用計數的問題上, 現在問題變為 : 為什么一個Binder服務端把服務傳遞給另外一個進程后, 這個服務的引用計數會加1?

8.1             從邏輯上看, Binder服務端的引用計數取決於什么?

引用計數的目的是為了保證只要資源還在被使用, 那它就不應該被釋放. Binder服務也可以理解為一種資源, 與普通資源的不同之處在於它可以被其它進程使用, 因此從正常邏輯來說, 只要有任何一個客戶端正在使用服務, 此服務在服務端進程中就不應該被釋放.

 

那怎么做到這一點呢? 還是借助引用計數的功能, 只要有任何一個客戶端獲取了本服務, 那么在服務端進程中就把本服務的引用計數加1.

 

客戶端獲取服務, 從本質上來說就是客戶端進程執行了readStrongBinder()操作, 所以在這個環節把服務端進程中的服務的引用計數加1是最合理的.

 

下面從Android代碼層面看下系統是如何保障這個機制的.

8.2             客戶端獲取服務 -> Binder服務引用計數加1

廢話少說, 先看一下客戶端獲取服務的代碼流程:

Parcel::readStrongBinder() -> unflatten_binder -> ProcessState::getStrongProxyForHandle.

 

getStrongProxyForHandle中會新建BpBinder對象, 如下:

注意最后一句話, 它把BpBinder賦值給了一個sp<IBinder>類型, 這個賦值動作會導致sp執行構造函數:

 

這個構造函數會執行decStrong動作, 也就是執行BpBinder->decStrong, 最終會導致BpBinder-> onFirstRef被執行:

 

onFirstRef中執行了incStrongHandle:

這句話的意思是讓服務端進程執行BC_ACQUIRE請求. 注意是服務端進程哦.

 

我們在來看服務端進程是如何響應的:

看見沒, 這里把服務的引用計數加1.

8.3             客戶端釋放服務 -> Binder服務引用計數減1

有加1的地方就要有減1的地方. 那客戶端何時會去做減1的動作呢?

 

這次我們倒推, 如果要讓Binder服務引用計數減1, BpBinder要執行onLastStrongRef, 里面請求服務端進程執行BC_RELEASE動作.

 

服務端收到BR_RELEASE請求后, mPendingStrongDerefs.push(obj). 最終在processPendingDerefs函數里面針對每一個obj執行obj->decStrong(mProcess.get()).

 

所以現在的問題是客戶端何時才會執行onLastStrongRef? 答案是在BpBinder對象的引用計數減為0:

 

BpBinder對象何時才會減為0?

 

我們以MediaPlayerService為例來說明. 前面說過, MediaPlayer實現了Binder服務端, 然后在setDataSource把這個服務傳遞給MPS. 我們看下MPS是如何處理傳遞過來的服務的:

這里有兩個關鍵 : 第一個是readStrongBinder, 它最終會創建BpBinder並返回一個sp<IBinder>.

第二個是interface_cast. 它會經歷如下這串流程:

asInterface實現如下:

所有的BpXXX都會繼承至BpInterface, 因此里面的new動作會導致基類構造函數被執行:

構造函數里面調用了BpRefBase(remote), 注意這里的remote就是前面readStrongBinder返回的結果.

 

BpRefBase實現如下:

它的入參是o, 那就是上面的remote. 該函數的主要目的是初始化mRemote. 這里調用了一下mRemote->incStrong增加了一下BpBinder的引用計數.

 

大家可能會有疑問, 前面readStrongBinder的時候new BpBinder, 然后通過sp<IBinder>的形式返回, 此時已經讓BpBinder的引用計數加1. 為什么又要做一次mRemote->incStrong?

 

因為當interface_cast執行完畢時, sp<IBinder>這個返回值的生命周期就結束了, 結束時會有一個減1動作, 因此mRemote要自己做incStrong.

 

對於MPS來說, 通過onTransact->create流程, 會把上面創建的這個客戶端存儲在MediaPlayerService:: Client:: mClient這個變量中:

 

當執行disconnect操作時, 會有這樣一句話:

 

這句話像多米洛骨牌一樣, 會觸發這樣一整串流程:

mClient.clear()  ->  BpRefBase::onLastStrongRef  ->  mRemote->decStrong  ->  BpBinder::onLastStrongRef  ->  ipc->decStrongHandle  ->  binder通信  -> 服務進程中服務引用計數減1.

9.   Binder示例代碼

假設你想自己動手寫一個binder通信程序, 可以參考Android官方代碼:

 

或者參考他人自己做的示例:

 Binder系列8如何使用Binder , 這里NativeJAVA的示例.

 

另外, 如果想在JAVA里面簡單實現一個服務, 不一定要定義AIDL. 示例代碼如下:

//服務端代碼

    private final IBinder mToken = new Binder() {

        private static final int CMD_GET_TOKEN_NAME = 0;

 

        @Override

        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)

                  throws RemoteException

        {

            switch (code) {

                case CMD_GET_TOKEN_NAME:

                    Log.e(TAG, "CMD_GET_TOKEN_NAME " + this.toString());

                    reply.writeString(this.toString());

                    return true;

                default:

                    return super.onTransact(code, data, reply, flags);

            }

        }

    };

 

 

//客戶端代碼

    private String getTokenName(IBinder cb) {

        if (cb == null)

            return "ERROR cb == null";

 

        Parcel data = Parcel.obtain();

        Parcel reply = Parcel.obtain();

        String res;

 

        try {

            cb.transact(0, data, reply, 0);

            res = reply.readString();

        } catch (RemoteException e) {

            res=  "RemoteException";

        } finally {

            data.recycle();

            reply.recycle();

        }

 

        return res;

    }

10.         Reflink

原理:

[November 1, 2015] Binder系列1—Binder Driver初探

  • binder驅動的基本結構: open/mmap/ioctl
  • 主要的數據結構
    • binder_proc
    • binder_node

[November 2, 2015] Binder系列2—Binder Driver再探

  • binder_thread_write過程詳解
  • binder_thread_read過程詳解
  • Binder內存機制

[November 7, 2015] Binder系列3啟動ServiceManager

init.rc如何啟動ServiceManager進程, 如何與驅動交互

[November 8, 2015] Binder系列4—獲取ServiceManager

如果想與ServiceManager進程通信, 如何獲取它在客戶端的代理

[November 14, 2015] Binder系列5注冊服務(addService)

注冊服務

[November 15, 2015] Binder系列6—獲取服務(getService)

獲取服務

[November 21, 2015] Binder系列7—framework層分析

Java層的binder機制

[November 22, 2015] Binder系列8如何使用Binder

Native層和Java層的Binder示例

[November 23, 2015] Binder系列9如何使用AIDL

AIDL的使用方法

[November 28, 2015] Binder系列10總結

 

[October 2, 2016] binderDied()過程分析

Binder die機制

[October 3, 2016] Binder死亡通知機制之linkToDeath

如何接收die消息

 

調試:

[August 27, 2016] Binder子系統之調試分析()

Binder驅動層有很多開關它們的作用以及如何使用它們

[August 28, 2016] Binder子系統之調試分析()

同上

[September 3, 2016] Binder子系統之調試分析()

同上

[May 1, 2017] Binder異常解析

如何根據logcat中的異常報錯, 分析binder通信出錯的原因

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM