Android_Binder原理分析(上)

Binder是什麼?

Binder能夠實現進程與進程之間的通訊(IPC), Binder是Android底層系統的一個特點了,它很好地解決了進程間通信的問題。 android

可能不少小夥伴對 Binder感受有點兒陌生,可是 Binder在Android系統中無處不在,好比:

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

Binder是Android獨有的跨線程通信機制,它的運行機制和現實中的一個例子很像,咱們來看一張圖緩存

這張圖很形象的提現了Binder的運行機制,有Client(我的電腦),Server(應用服務器),Binder(路由器),ServiceManager(DNS服務器)安全

Binder對服務端(Server)而言至關於服務端提供特定服務的接入點,想要對接該服務就要從這個接入點入手 對於客戶端(Client)而言,Binder至關於通向服務端(Server)管道的入口,要想和服務端(Server)某個服務通信,必須先創建管道,並得到管道的入口,也就是接入點bash

ServiceManager至關於DNS服務器服務器

注: 這裏只是舉個形象的栗子,具體是怎樣的,都作了什麼,下面會慢慢講~網絡

Android爲何使用Binder作IPC?

爲何Android會採用Binder作IPC(進程間通信)呢?這也是Binder的由來,首先Linux中是有多種跨進程通信的方式,可是它們不太適用於Android的跨進程通信的場景,咱們大概來看下:架構

  • 管道 大可能是指半雙工管道,半雙工管道指的是,A給B數據和,B給A數據是兩件事情,只容許數據在一個方向上傳輸,相似於對講機,同一時間雙方只能有一方發送數據,而全雙工就像電話,能夠雙方同時發送/接收數據,這種方式是很是消耗內存的(具體可百度~)
  • 共享內存 共享內存值得是多個進程能夠訪問同一塊內存空間,這種方式管理會很混亂~
  • Socket Socket相對來講更適合的是網絡通信,對於進程間通信顯然不夠和諧~

因此Binder是應需求而生,前面三種方式只是說了不是和Android的進程建通信,那麼Binder爲何適合呢?併發

主要是兩個方面app

  1. 安全性 Binder協議支持對通信雙方的身份信息進行較校驗,既支持匿名的Binder也支持實名的Binder,像傳統的Socket通信,並無嚴格的身份校驗,只要知道ip地址就能夠訪問,在Android中每一個應用安裝成功都會分配一個惟一的UID,而每一個進程都有一個PID,例如在Android9.0源碼中startActivity()會對UIDPID作校驗,下面會提到~
  2. 性能 Binder機制在進程間通信時,數據只需Copy一次,而傳統的通信方式,好比管道的方式須要Copy兩次,性能方面僅次於共享內存的方式~

另外還有一點是爲何Binder設計的是Client/Server的形式,由於系統提供了一個服務,可能不少app都須要使用該服務,因此是一個一對多的場景,因此Binder採用的是Client/Server的形式工具

如圖(管道方式須要兩次Copy操做):

管道方式須要Copy兩次數據

Binder中四個重要的角色

    1. Client 客戶端
    1. Server 服務端
    1. ServiceManager 就像上文所述的DNS服務器,它的主要做用是ClientServer之間的橋樑,Client能夠經過ServiceManager拿到ServerBinder實體的引用
    1. Binder驅動是鏈接 ClientServerServiceManager的橋樑,Android重不少系統服務是經過Binder拿到的,好比context.getSystemService (Context.AUDIO_SERVICE)獲取音量的服務

其中前三者ClientServerServiceManager都屬於用戶空間,而Binder驅動屬於內核空間 注意用戶空間是不能夠進程間通信的,內核空間是能夠進程間通信的 這裏須要主要的是Binder驅動它是有個線程池的存在,有多是併發, 這個線程池是由Binder驅動管理的,一個進程的Binder線程數默認是16,超過這個數會阻塞等待~

Binder中四個重要的對象

首先須要簡單說下AIDL : AIDL是Android Interface definition language 安卓接口定義語言,是BinderClient進程Server進程通信的語言,是爲了Binder簡化代碼的架構

  • IBinder 是一個接口,表示能夠實現跨進程通信的能力,只須要實現找個接口就能夠跨進程傳輸
  • Iinterface 表明的Server進程具有什麼樣的功能、能力,可以提供哪些方法,對應AIDL定義的接口
  • Binder Java層的Binder類,表明的是Binder的本地對象,有個重要的內部類BinderProxy
  • Stub 是使用AIDL時編譯工具自動生成一個Stub的靜態內部類,繼承自Binder,是個抽象類,具體實現Iinterface的接口的具體邏輯,開發者本身實現

Binder通信流程

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

如圖: Binder通信流程首先是,Client須要發送數據,作了(只作一次)copy from userBinderProxy,BinderProxy是能夠操做內核的緩存區,內核的緩存區和Binder建立的內存映射(Binder建立的接收緩存區)是存在映射關係的,而服務端是與內存映射(Binder建立得接收緩存區)是存在直接的內存映射關係,因此只須要一次copy操做,至關於這一次複製,直接將數據複製到了Server進程的內存空間中去了。固然這中間室友校驗的,好比: descriptorBinder實體的引用Binder實體是否匹配

詳細流程圖:

源碼分析

下面從源碼角度簡單分析內核層主要作的如下步驟:

  • 打開binder設備
  • buffer建立 (用於進程間數據傳遞)
  • 開闢內存映射 (128K)
  • ServiceManager啓動
  • 打包Parcel中,數據寫入binder設備,copy_from_user
  • 服務註冊,添加到鏈表svclist中
  • 定義主線程中的線程池
  • 循環從mIn和mOut中取出讀寫請求,發到binder設備中

咱們從Android源碼中均可以看到這些,下面代碼以Android9.0爲例:

有興趣的小夥伴能夠自行翻閱:Android在線源碼閱讀

首先咱們看下ServiceManager啓動,ServiceManager是在Android系統啓動時就就會喚起的服務可見system/core/rootdir/init.rc407行:

start servicemanager
複製代碼

ServiceManager會完成打開binder設備和開闢內存映射 (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設備文件,返回文件描述符
        driver = "/dev/binder";
    }
    //Binder的buffer建立,用於進程間數據傳輸,開啓128k大小的內存映射,路徑見下方
    bs = binder_open(driver, 128*1024);
複製代碼

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

//權限檢查
  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;
    }
    //根據服務名在svclist鏈表上查找,看服務是否已經註冊
    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 {
        //沒註冊,分配一個服務管理的結構svcinfo,並將其添加到鏈表的list當中
        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;
        //將表明該服務的結構插入到鏈表
        si->next = svclist;
        svclist = si;
    }

    //增長Binder的應用計數
    binder_acquire(bs, handle);
    //該服務退出須要通知ServiceManager
    binder_link_to_death(bs, handle, &si->death);
    return 0;
複製代碼

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

//打開Binder設備驅動的時候,開啓128k大小的內存映射是在這裏執行的
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
複製代碼

打包Parcel,數據寫入binder設備,copy_from_user可見frameworks/native/libs/binder/IServiceManager.cppaddService()方法: 這裏會將Service相關信息打包成Parcel對象,而且調用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;
    }
複製代碼

數據寫入binder設備的過程在frameworks/native/libs/binder/IPCThreadState.cpp中實現的writeTransactionData()方法,

//這裏主要是將`Parcel`對象中的信息封裝成結構體,而且寫入到`mOut`當中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
...
//將數據寫入Binder的設備當中,並等待返回結果
err = waitForResponse(reply);
複製代碼

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

//將對象設爲當前線程的私有
 pthread_setspecific(gTLS, this);
 clearCaller();
 //輸入buffer預分配256大小的空間
 mIn.setDataCapacity(256);
 //輸出buffer預分配256大小的空間
 mOut.setDataCapacity(256);
複製代碼

對Binder設備數據的讀寫,主要的工做就是循環的對mInmOut進行IO的讀寫,而後發送到Binder的設備中 對於數據是否須要讀取/寫的

// 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;
    
    ...
    
    //將讀寫的請求數據發送到Binder設備中
     if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
複製代碼
相關文章
相關標籤/搜索