Android_Binder原理分析

Binder是什么?

Binder可以實現(xiàn)進(jìn)程與進(jìn)程之間的通信(IPC),
Binder是Android底層系統(tǒng)的一個特色了,它很好地解決了進(jìn)程間通訊的問題。

image

可能很多小伙伴對Binder感覺有點兒陌生,但是Binder在Android系統(tǒng)中無處不在,比如:

    1. 媒體的播放
    1. 音視頻捕獲
    1. 傳感器使用
    1. startActivity()/startService()
    1. 等...

Binder是Android獨有的跨線程通訊機(jī)制,它的運行機(jī)制和現(xiàn)實中的一個例子很像,我們來看一張圖

image

這張圖很形象的提現(xiàn)了Binder的運行機(jī)制,有Client(個人電腦),Server(應(yīng)用服務(wù)器),Binder(路由器),ServiceManager(DNS服務(wù)器)

Binder對服務(wù)端(Server)而言相當(dāng)于服務(wù)端提供特定服務(wù)的接入點,想要對接該服務(wù)就要從這個接入點入手
對于客戶端(Client)而言,Binder相當(dāng)于通向服務(wù)端(Server)管道的入口,要想和服務(wù)端(Server)某個服務(wù)通訊,必須先建立管道,并獲得管道的入口,也就是接入點

ServiceManager相當(dāng)于DNS服務(wù)器

注: 這里只是舉個形象的栗子,具體是怎樣的,都做了什么,下面會慢慢講~

Android為什么使用Binder做IPC?

為什么Android會采用Binder做IPC(進(jìn)程間通訊)呢?這也是Binder的由來,首先Linux中是有多種跨進(jìn)程通訊的方式,但是它們不太適用于Android的跨進(jìn)程通訊的場景,我們大概來看下:

  • 管道 大多是指半雙工管道,半雙工管道指的是,A給B數(shù)據(jù)和,B給A數(shù)據(jù)是兩件事情,只允許數(shù)據(jù)在一個方向上傳輸,類似于對講機(jī),同一時間雙方只能有一方發(fā)送數(shù)據(jù),而全雙工就像電話,可以雙方同時發(fā)送/接收數(shù)據(jù),這種方式是非常消耗內(nèi)存的(具體可百度~)
  • 共享內(nèi)存 共享內(nèi)存值得是多個進(jìn)程可以訪問同一塊內(nèi)存空間,這種方式管理會很混亂~
  • Socket Socket相對來說更適合的是網(wǎng)絡(luò)通訊,對于進(jìn)程間通訊顯然不夠和諧~

所以Binder是應(yīng)需求而生,前面三種方式只是說了不是和Android的進(jìn)程建通訊,那么Binder為什么適合呢?

主要是兩個方面

  1. 安全性 Binder協(xié)議支持對通訊雙方的身份信息進(jìn)行較校驗,既支持匿名的Binder也支持實名的Binder,像傳統(tǒng)的Socket通訊,并沒有嚴(yán)格的身份校驗,只要知道ip地址就可以訪問,在Android中每個應(yīng)用安裝成功都會分配一個唯一的UID,而每個進(jìn)程都有一個PID,例如在Android9.0源碼中startActivity()會對UIDPID做校驗,下面會提到~
  2. 性能 Binder機(jī)制在進(jìn)程間通訊時,數(shù)據(jù)只需Copy一次,而傳統(tǒng)的通訊方式,比如管道的方式需要Copy兩次,性能方面僅次于共享內(nèi)存的方式~

另外還有一點是為什么Binder設(shè)計的是Client/Server的形式,因為系統(tǒng)提供了一個服務(wù),可能很多app都需要使用該服務(wù),所以是一個一對多的場景,所以Binder采用的是Client/Server的形式

如圖(管道方式需要兩次Copy操作):

管道方式需要Copy兩次數(shù)據(jù)

Binder中四個重要的角色

    1. Client 客戶端
    1. Server 服務(wù)端
    1. ServiceManager 就像上文所述的DNS服務(wù)器,它的主要作用是ClientServer之間的橋梁,Client可以通過ServiceManager拿到ServerBinder實體的引用
    1. Binder驅(qū)動是連接 Client、ServerServiceManager的橋梁,Android重很多系統(tǒng)服務(wù)是通過Binder拿到的,比如context.getSystemService (Context.AUDIO_SERVICE)獲取音量的服務(wù)
image

其中前三者ClientServer 、ServiceManager都屬于用戶空間,而Binder驅(qū)動屬于內(nèi)核空間
注意用戶空間是不可以進(jìn)程間通訊的,內(nèi)核空間是可以進(jìn)程間通訊的
這里需要主要的是Binder驅(qū)動它是有個線程池的存在,有可能是并發(fā), 這個線程池是由Binder驅(qū)動管理的,一個進(jìn)程的Binder線程數(shù)默認(rèn)是16,超過這個數(shù)會阻塞等待~

Binder中四個重要的對象

首先需要簡單說下AIDL :
AIDL是Android Interface definition language 安卓接口定義語言,是BinderClient進(jìn)程Server進(jìn)程通訊的語言,是為了Binder簡化代碼的架構(gòu)

  • IBinder 是一個接口,表示可以實現(xiàn)跨進(jìn)程通訊的能力,只需要實現(xiàn)找個接口就可以跨進(jìn)程傳輸
  • Iinterface 代表的Server進(jìn)程具備什么樣的功能、能力,能夠提供哪些方法,對應(yīng)AIDL定義的接口
  • Binder Java層的Binder類,代表的是Binder的本地對象,有個重要的內(nèi)部類BinderProxy
  • Stub 是使用AIDL時編譯工具自動生成一個Stub的靜態(tài)內(nèi)部類,繼承自Binder,是個抽象類,具體實現(xiàn)Iinterface的接口的具體邏輯,開發(fā)者自己實現(xiàn)

Binder通訊流程

先看兩張圖,Binder通訊流程圖:

image

如圖: Binder通訊流程首先是,Client需要發(fā)送數(shù)據(jù),做了(只做一次)copy from userBinderProxy,BinderProxy是可以操作內(nèi)核的緩存區(qū),內(nèi)核的緩存區(qū)和Binder創(chuàng)建的內(nèi)存映射(Binder創(chuàng)建的接收緩存區(qū))是存在映射關(guān)系的,而服務(wù)端是與內(nèi)存映射(Binder創(chuàng)建得接收緩存區(qū))是存在直接的內(nèi)存映射關(guān)系,所以只需要一次copy操作,相當(dāng)于這一次復(fù)制,直接將數(shù)據(jù)復(fù)制到了Server進(jìn)程的內(nèi)存空間中去了。當(dāng)然這中間室友校驗的,比如: descriptorBinder實體的引用Binder實體是否匹配

詳細(xì)流程圖:

image

源碼分析

下面從源碼角度簡單分析內(nèi)核層主要做的以下步驟:

  • 打開binder設(shè)備
  • buffer創(chuàng)建 (用于進(jìn)程間數(shù)據(jù)傳遞)
  • 開辟內(nèi)存映射 (128K)
  • ServiceManager啟動
  • 打包Parcel中,數(shù)據(jù)寫入binder設(shè)備,copy_from_user
  • 服務(wù)注冊,添加到鏈表svclist中
  • 定義主線程中的線程池
  • 循環(huán)從mIn和mOut中取出讀寫請求,發(fā)到binder設(shè)備中

我們從Android源碼中都可以看到這些,下面代碼以Android9.0為例:

有興趣的小伙伴可以自行翻閱:Android在線源碼閱讀

首先我們看下ServiceManager啟動,ServiceManager是在Android系統(tǒng)啟動時就就會喚起的服務(wù)可見system/core/rootdir/init.rc407行:

start servicemanager

ServiceManager會完成打開binder設(shè)備和開辟內(nèi)存映射 (128K)的動作,可見device/google/cuttlefish_kernel/4.4-x86_64/System.map25306行(該文件需要下載查看,不支持在線瀏覽):

ffffffff815dbf50 t binder_mmap

frameworks/native/cmds/servicemanager/service_manager.cmain()方法中有:

 if (argc > 1) {
        driver = argv[1];
    } else {
        //打開Binder設(shè)備文件,返回文件描述符
        driver = "/dev/binder";
    }
    //Binder的buffer創(chuàng)建,用于進(jìn)程間數(shù)據(jù)傳輸,開啟128k大小的內(nèi)存映射,路徑見下方
    bs = binder_open(driver, 128*1024);

其實Service的注冊也是在service_manager中的do_add_service()方法中完成的,這個不是Binder的核心知識,簡單提下,感興趣的可以看下

    //權(quán)限檢查
  if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }
    //根據(jù)服務(wù)名在svclist鏈表上查找,看服務(wù)是否已經(jīng)注冊
    si = find_svc(s, len);
    if (si) {
        if (si->handle) {
        //注冊過
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
        //沒注冊,分配一個服務(wù)管理的結(jié)構(gòu)svcinfo,并將其添加到鏈表的list當(dāng)中
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",
                 str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
        //將代表該服務(wù)的結(jié)構(gòu)插入到鏈表
        si->next = svclist;
        svclist = si;
    }

    //增加Binder的應(yīng)用計數(shù)
    binder_acquire(bs, handle);
    //該服務(wù)退出需要通知ServiceManager
    binder_link_to_death(bs, handle, &si->death);
    return 0;

打開Binder設(shè)備驅(qū)動是在frameworks/native/cmds/servicemanager/binder.cbinder_open()方法中有這么一行代碼:

//打開Binder設(shè)備驅(qū)動的時候,開啟128k大小的內(nèi)存映射是在這里執(zhí)行的
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

打包Parcel,數(shù)據(jù)寫入binder設(shè)備,copy_from_user可見frameworks/native/libs/binder/IServiceManager.cppaddService()方法:
這里會將Service相關(guān)信息打包成Parcel對象,并且調(diào)用remote()->transact()方法往下一步傳輸

 virtual status_t addService(const String16& name, const sp<IBinder>& service,
                                bool allowIsolated, int dumpsysPriority) {
        Parcel data, reply;
        //Parcel對象打包過程
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        data.writeInt32(dumpsysPriority);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }

數(shù)據(jù)寫入binder設(shè)備的過程在frameworks/native/libs/binder/IPCThreadState.cpp中實現(xiàn)的writeTransactionData()方法,

//這里主要是將`Parcel`對象中的信息封裝成結(jié)構(gòu)體,并且寫入到`mOut`當(dāng)中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
...
//將數(shù)據(jù)寫入Binder的設(shè)備當(dāng)中,并等待返回結(jié)果
err = waitForResponse(reply);

另外從Binder設(shè)備中不停地讀寫的實現(xiàn)方式,是通過線程池的方式(上文有提到),不停地去讀寫,具體可見:
frameworks/native/libs/binder/IPCThreadState.cppjoinThreadPool()方法,主要是定義了一個主線程中的線程池,

//將對象設(shè)為當(dāng)前線程的私有
 pthread_setspecific(gTLS, this);
 clearCaller();
 //輸入buffer預(yù)分配256大小的空間
 mIn.setDataCapacity(256);
 //輸出buffer預(yù)分配256大小的空間
 mOut.setDataCapacity(256);

對Binder設(shè)備數(shù)據(jù)的讀寫,主要的工作就是循環(huán)的對mInmOut進(jìn)行IO的讀寫,然后發(fā)送到Binder的設(shè)備中
對于數(shù)據(jù)是否需要讀取/寫的

     // Is the read buffer empty?,是否有讀的請求
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();

    // We dont want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    // 是否有寫的請求
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
    
    ...
    
    //將讀寫的請求數(shù)據(jù)發(fā)送到Binder設(shè)備中
     if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
?著作權(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ù)。

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