Android 系統原始碼-2:Binder 通訊機制

WngShhng發表於2019-01-23

Binder 是 Android 系統中非常重要的組成部分。Android 系統中的許多功能建立在 Binder 機制之上。在這篇文章中,我們會對 Android 中的 Binder 在系統架構中的作用進行分析;然後,我們會從底層的實現角度簡要說明為什麼 Android 要開發出一套獨立的跨程式通訊機制;最後,我們會給出一個 AIDL 的使用示例來說明如何使用 Binder 來進行通訊。

1、什麼是 Binder? 為什麼說它對 Android 系統至關重要?

“什麼是 Binder? 為什麼說它對 Android 系統至關重要?” 在回答這個問題之前,我們先來說下其他的東西。

不知道你有沒有思考過這麼一個問題:為什麼當我們在 Android 中啟動一個頁面的時候需要呼叫 startActivity() 方法,然後還要傳入一個 Intent? 如果我們不使用這種傳遞值的方式,直接寫成靜態的變數有沒有問題?這也是之前有人問過我的一個問題。

對上面的兩個問題,我們先回答第二個。使用靜態的變數傳遞值在大部分情況下是可以的,當然要注意在使用完了值之後要及時釋放資源,不然會佔用太多記憶體,甚至 OOM. 但是,在特殊的情況下它是無法適用的,即跨程式的情況下。這是因為,靜態的變數的作用範圍只是其所在的程式,在其他程式訪問的時候屬於跨程式訪問,當然訪問不到了。對於第一個問題,Android 中的一個 Activity 的啟動過程遠比我們想象的複雜,其中就涉及跨程式的通訊過程。當我們呼叫 startActivity() 方法之後,我們的所有的 “意圖” 會經過層層過濾,直到一個稱之為 AMS 的地方被處理。處理完之後,再跨程式呼叫你啟動頁面時的程式進行後續處理,即回撥 onCreate() 等生命週期方法。

一個 Activity 的啟動過程涉及 Android 中兩種重要的通訊機制,Binder 和 Handler,我們會在以後的文章中對此進行分析。

下面我們通過一個簡單的圖來說明一下 Activity 的啟動過程:

一個Activity的啟動過程

當我們呼叫 startActivity() 方法的時候,首先會從 ServiceManager 中獲取到 ActivityManagerService (就是 AMS),然後將 ApplicationThread 作為引數傳遞給 AMS,然後執行 AMS 的方法來啟動 Activity. (在我們的應用程式中執行另一個程式的方法。)

AMS 是全域性的,在系統啟動的時候被啟動。當我們使用它的時候從 ServiceManager 中獲取這個全域性的變數即可。當我們呼叫它的方法的時候,方法具體的執行邏輯將在系統的程式中執行。我們傳入的 ApplicationThread 就像一個信使一樣。當 AMS 處理完畢,決定回撥 Activity 的生命週期方法的時候,就直接呼叫 ApplicationThread 的方法(這是在另一個程式中呼叫我們的應用程式)。這樣就實現了我們的 Activity 的生命週期的回撥。

看了上面的過程,也許有的同學會覺得。Binder 對 Android 系統至關重要,但是我們並沒有用到 Binder 啊。實際上,我們只是沒有直接使用 Binder. 以下圖為例,我們說下我們實際開發過程中是如何使用 Binder 的。

AIDL Manager

在大多數情況下,我們都在與各個 Manager 進行互動,而實際上這些 Manager 內部是使用 Binder 來進行跨程式通訊的。如上所示,當我們呼叫 Manager 的時候,Manager 會通過代理類來從 Binder 驅動中得到另一個程式的 Stub 物件,然後我們使用該 Stub 物件,遠端呼叫另一個程式的方法。只是這個過程被封裝了,我們沒有感知到而已,而這個跨程式通訊 (IPC) 的機制就是 Binder 機制。

至於什麼是 Stub 呢?Stub 是 AIDL 規範中的一部分。AIDL 為我們使用 Binder 提供了一套模板。在 Android 系統中大量使用了這種定義來完成跨程式通訊。稍後我們介紹 AIDL 的時候,你將看到它是如何作用的。

2、為什麼是 Binder 而不是其他通訊機制?

Android 是基於 Linux 的,Linux 本身已經具有了許多的 IPC 機制,比如:管道(Pipe)、訊號(Signal)和跟蹤(Trace)、插口(Socket)、訊息佇列(Message)、共享記憶體(Share Memory)和訊號量(Semaphore)。那麼,為什麼 Android 要特立獨行地搞出一套 IPC 機制呢?這當然是有原因的:

  1. 效率上 :Socket 作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程式間通訊和本機上程式間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。Binder 只需要一次資料拷貝,效能上僅次於共享記憶體

  2. 穩定性:Binder 基於 C|S 架構,客戶端(Client)有什麼需求就丟給服務端(Server)去完成,架構清晰、職責明確又相互獨立,自然穩定性更好。 共享記憶體雖然無需拷貝,但是控制負責,難以使用。從穩定性的角度講,Binder 機制是優於記憶體共享的。

  3. 安全性:Binder 通過在核心層為客戶端新增身份標誌 UID|PID,來作為身份校驗的標誌,保障了通訊的安全性。 傳統 IPC 訪問接入點是開放的,無法建立私有通道。比如,命名管道的名稱,SystemV 的鍵值,Socket 的 ip 地址或檔名都是開放的,只要知道這些接入點的程式都可以和對端建立連線,不管怎樣都無法阻止惡意程式通過猜測接收方地址獲得連線。

除了上面的原因之外,Binder 還擁有許多其他的特性,比如:1).採用引用計數,當某個 Binder 不再被任何客戶端引用的時候,會通知它的持有者可以將其釋放,這適用於 Android 這種常常因為資源不足而回收資源的應用場景。2).它內部維護了一個執行緒池;3).可以像觸發本地方法一樣觸發遠端的方法。4).支援同步和非同步 (oneway) 的觸發模型;5).可以使用 AIDL 模板進行描述和開發。

3、Binder 模型,Binder 中的 4 個主要角色

在 Binder 模型中共有 4 個主要角色,它們分別是:Client、Server、Binder 驅動和 ServiceManager. Binder 的整體結構是基於 C|S 結構的,以我們啟動 Activity 的過程為例,每個應用都會與 AMS 進行互動,當它們拿到了 AMS 的 Binder 之後就像是拿到了網路介面一樣可以進行訪問。如果我們將 Binder 和網路的訪問過程進行類比,那麼 Server 就是伺服器,Client 是客戶終端,ServiceManager 是域名伺服器(DNS),驅動是路由器。其中 Server、Client 和 ServiceManager 執行於使用者空間,驅動執行於核心空間

當我們的系統啟動的時候,會在啟動 SystemServer 程式的時候啟動各個服務,也包括上面的 AMS. 它們會被放進一個雜湊表中,並且雜湊表的鍵是字串。這樣我們就可以通過服務的字串名稱來找到對應的服務。這些服務就是一個個的 Binder 實體,對於 AMS 而言,也就是 IActivityManager.Stub 例項。這些服務被啟動的之後就像網路中的伺服器一樣一直等待使用者的訪問。

對於這裡的 ServiceManager,它也是一種服務,但是它比較特殊,它會在所有其他的服務之前被註冊,並且只被註冊一次。它的作用是用來根據字串的名稱從雜湊表中查詢服務,以及在系統啟動的時候向雜湊表中註冊服務。

Binder 模型

所以,我們可以使用上面的這張圖來描述整個 Binder 模型:首先,在系統會將應用程式所需的各種服務通過 Binder 驅動註冊到系統中(ServiceManager 先被註冊,之後其他服務再通過 ServiceManager 進行註冊),然後當某個客戶端需要使用某個服務的時候,也需要與 Binder 驅動進行互動,Binder 會通過服務的名稱到 ServiceManager 中查詢指定的服務,並將其返回給客戶端程式進行使用。

4、Binder 的原理

上面我們梳理了 Binder 的模型,以及為什麼系統設計一套通訊機制的原因。那麼你是否也好奇神乎其神的 Binder 究竟是怎麼實現的呢?這裡我們來梳理下 Binder 內部實現的原理。

首先,Binder 的實現過程是非常複雜的,在《Android 系統原始碼情景分析》一書中有 200 頁的篇幅都在講 Binder. 在這裡我們不算詳細地講解它的具體的實現原理,我們只對其中部分內容做簡單的分析,並且不希望涉及大量的程式碼。

4.1 inder 相關的系統原始碼的結構

然後,我們需要介紹下 Binder 相關的核心類在原始碼中的位置,

-framework
    |--base
        |--core
            |--java--android--os  
                              |--IInterface.java
                              |--IBinder.java
                              |--Parcel.java
                              |-- IServiceManager.java
                              |--ServiceManager.java
                              |--ServiceManagerNative.java
                              |--Binder.java  
            |--jni
                |--android_os_Parcel.cpp
                |--AndroidRuntime.cpp
                |--android_util_Binder.cpp
    |--native
        |--libs--binder         
                  |--IServiceManager.cpp
                  |--BpBinder.cpp
                  |--Binder.cpp             // Binder 的具體實現
                  |--IPCThreadState.cpp
                  |--ProcessState.cpp
        |--include--binder                  // 主要是一些標頭檔案
                      |--IServiceManager.h
                      |--IInterface.h
        |--cmds--servicemanager
                    |--service_manager.c    // 用來註冊服務的 ServiceManager
                    |--binder.c
-kernel-drivers-staging-android
                         |--binder.c        
                         |--uapi-binder.h
複製程式碼

4.2 Binder 實現過程中至關重要的幾個函式

當我們檢視 binder.c 的原始碼的時候,或者檢視與 Binder 相關的操作的時候,經常看到幾個操作 ioctl, mmap 和 open. 那麼這幾個操作符是什麼含義呢?

首先,open 函式用來開啟檔案的操作符,在使用的時候需要引入標頭檔案,#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>,其函式定義如下,

int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
複製程式碼

這裡的 pathname 表示檔案路徑;flag 表示開啟方式;mode 表示開啟的模式和許可權等;若所有欲核查的許可權都通過了檢查則返回 0, 表示成功, 只要有一個許可權被禁止則返回-1.

然後是 ioctl 指令,使用的時候需要引入 #include <sys/ioctl.h> 標頭檔案,ioctl 是裝置驅動程式中對裝置的 I/O 通道進行管理的函式,用於向裝置發控制和配置命令。其函式定義如下:

int ioctl(int fd, ind cmd, …); 
複製程式碼

其中 fd 是使用者程式開啟裝置時使用 open 函式返回的檔案標示符,cmd 是使用者程式對裝置的控制命令,至於後面的省略號,那是一些補充引數,一般最多一個,這個引數的有無和 cmd 的意義相關。

最後是 mmap 函式,它用來實現記憶體對映。使用的時候需要引入標頭檔案 #include <sys/mman.h>. 與之對應的還有 munmap 函式。它們的函式定義如下,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
複製程式碼

這裡的引數的含義是:

  1. start:對映區的開始地址,設定為0時表示由系統決定對映區的起始地址;
  2. length:對映區的長度。長度單位是以位元組為單位,不足一記憶體頁按一記憶體頁處理;
  3. prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突。是以下的某個值,可以通過 o r運算合理地組合在一起;
  4. flags:指定對映物件的型別,對映選項和對映頁是否可以共享。它的值可以是一個或者多個以下位的組合體;
  5. fd:有效的檔案描述詞。一般是由 open() 函式返回,其值也可以設定為-1,此時需要指定 flags 引數中的 MAP_ANON,表明進行的是匿名對映;
  6. off_toffset:被對映物件內容的起點。

成功執行時,mmap() 返回被對映區的指標,munmap() 返回0。失敗時,mmap() 返回 MAP_FAILED[其值為(void *)-1],munmap() 返回 -1.

4.3 ServiceManger 啟動

Binder 中的 ServiceManager 並非 Java 層的 ServiceManager,而是 Native 層的。啟動 ServiceManager 由 init 程式通過解析 init.rc 檔案而建立。啟動的時候會找到上述原始碼目錄中的 service_manager.c 檔案中,並呼叫它的 main() 方法,

// platform/framework/native/cmds/servicemanager.c
int main(int argc, char** argv)
{
    struct binder_state *bs;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }
    // 1. 開啟 binder 驅動
    bs = binder_open(driver, 128*1024);
    // ...
    // 2. 將當前的 ServiceManger 設定成上下文
    if (binder_become_context_manager(bs)) {
        return -1;
    }
    // ...
    // 3. 啟動 binder 迴圈,進入不斷監聽狀態
    binder_loop(bs, svcmgr_handler);
    return 0;
}
複製程式碼

ServcieManager 啟動的過程就是上面三個步驟,無需過多說明。下面我們給出這三個方法具體實現的。在下面的程式碼中你將看到我們之前介紹的三個函式的實際應用。相應有了前面的鋪墊之後你理解起來不成問題 :)

// platform/framework/native/cmds/servicemanager.c
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }
    // 開啟裝置驅動
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    // 向驅動傳送指令,獲取binder版本資訊
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
    bs->mapsize = mapsize;
    // 通過系統呼叫,mmap 記憶體對映,mmap 必須是 page 的整數倍
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map;
    }
    return bs;
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}
複製程式碼

在上面的程式碼中,先使用 open() 函式開啟裝置驅動(就是一個開啟檔案的操作),然後使用 ioctl() 函式向上面的裝置驅動傳送指令以獲取裝置資訊。最後,通過 mmap() 函式實現記憶體對映,並將上述的檔案描述符傳入。這裡的 binder_state 是一個結構體,定義如下。其實就是用來描述 binder 的狀態。從上面我們也能看到它的三個變數的賦值過程。

// platform/framework/native/cmds/servicemanager.c
struct binder_state
{
    int fd;
    void *mapped;
    size_t mapsize;
};
複製程式碼

當然,在上面的程式碼中,我們又見到了久違的 goto 指令。它們主要用來處理髮生一些異常的情況。

開啟了驅動之後,註冊為上下文的方法更加簡單,

// platform/framework/native/cmds/servicemanager.c
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
複製程式碼

就是一個 ioctl 函式,使用指令 BINDER_SET_CONTEXT_MGR 將當前的 ServiceManager 註冊為上下文。

最後就是啟動 Binder 迴圈了。它的邏輯也沒有想象中得複雜,就是啟動了 for 迴圈,

// platform/framework/native/cmds/servicemanager.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    // 將 BC_ENTER_LOOPER 命令傳送給 binder 驅動,內部呼叫 ioctl 函式
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // 使用 iotcl 函式讀取
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) {
            break;
        }
        // 解析
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            break;
        }
        if (res < 0) {
            break;
        }
    }
}
複製程式碼

從上面看出,函式將會在 binder_write() 中將命令傳送給 Binder 驅動,以啟動迴圈。其實內部也是呼叫 ioctl 函式實現的。然後程式會啟動一個迴圈來不斷讀取、解析。這是伺服器很典型的操作了。

當然,我們上面分析的是 ServiceManager 中向 Binder 寫命令的過程,而驅動如何解析呢?當然是在驅動中實現了,詳細的過程可以檢視 Binder 驅動部分的原始碼。

4.4 Binder 的跨程式通訊過程

下面我們以 AMS 作為例子來講解下 Binder 跨程式通訊的實現過程。首先,當我們呼叫 startActivity() 方法的時候,最終將會進入 ActivityManager 以獲取 AMS,

    // platform/framework/base/core/java/android/app/ActivityManager.java
    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
    final IActivityManager am = IActivityManager.Stub.asInterface(b);
    return am;
複製程式碼

這裡會使用 ServiceManger 來按名稱查詢 AMS,查詢到 Binder 物件之後將其轉換成 AMS 就可以使用了。之前,我們也說過用來查詢 AMS 的 SeerviceManager 本身也是一種服務。所以,它這裡的方法也是通過 Binder 來實現的。那麼,我們就從這裡的 getService() 方法入手。

    // platform/framework/base/core/java/android/os/ServiceManager.java
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) { /* ... */ }
        return null;
    }
複製程式碼

這裡會先嚐試從快取當中取 Binder,取不到的話就從遠端進行獲取。這裡使用 rawGetService() 方法來從遠端獲取 Binder,程式碼如下,

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IBinder rawGetService(String name) throws RemoteException {
        final IBinder binder = getIServiceManager().getService(name);
        // ...		
        return binder;
    }

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }
複製程式碼

rawGetService() 方法中會使用 ServiceManagerNativegetService() 方法從遠端獲取 Binder. 這裡的 ServiceManagerNative 本質上只是一個代理類,它實際的邏輯是由 BinderInternal.getContextObject() 返回的 Binder 實現的。

也許你已經暈了,怎麼那麼多 Binder……我來說明下。當要查詢 AMS 的時候實際上是一個跨程式的呼叫過程,也就是實際的查詢的邏輯是在另一個程式實現,因此需要 Binder 來通訊。而查詢 AMS 的遠端物件實際上就是我們上面所說的 ServiceManager (Native 層的而不是 Java 層的,Java 層的 ServiceManager 是一個代理類,是用來從遠端獲取服務的)。

因此,按照上面的描述,BinderInternal.getContextObject() 返回的就應該是遠端的 Binder 物件。於是方法進入 Native 層,

// platform/framework/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}
複製程式碼

這裡的 ProcessState::self() 是否熟悉呢?你是否還記得在上一篇文章中,我們介紹 Android 系統啟動過程的時候介紹過它。我們曾經使用它來開啟 Binder 的執行緒池。這裡的 self() 方法其實是用來獲取一個單例物件的。我們可以直接由 getContextObject() 進入 getStrongProxyForHandle() 方法。從下面的方法中我們可以看出,這裡呼叫了 BpBindercreate() 方法建立了一個 BpBinder 例項並返回,也就是我們的 ServiceManager.

// plaftorm/framework/native/libs/binder/ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    AutoMutex _l(mLock);
    handle_entry* e = lookupHandleLocked(handle);
    if (e != nullptr) {
        IBinder* b = e->binder;
        if (b == nullptr || !e->refs->attemptIncWeak(this)) {
            // ...
			// 呼叫 BpBinder
            b = BpBinder::create(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }
複製程式碼

當我們拿到了 ServiceManager 的 Binder 之後就可以呼叫它的 getService() 方法來獲取服務了,

    // platform/framework/base/core/java/android/os/ServiceManagerNative.java
    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }
複製程式碼

這裡的 mRemote 就是之前返回的 BpBinder,這裡呼叫它的 transact() 方法,並傳入了一個方法標記 GET_SERVICE_TRANSACTION.

// platform/framework/native/libs/binder/BpBinder.cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}
複製程式碼

顯然這裡會呼叫 IPCThreadState 的 self() 方法先獲取一個單例的物件,然後呼叫它的 transact() 方法繼續方法的執行。

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle, uint32_t code, 
    const Parcel& data, Parcel* reply, uint32_t flags)
{
    status_t err;
    // ...
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
    // ...
    if ((flags & TF_ONE_WAY) == 0) { // OneWay 型別的呼叫,同步的
        // ...
		if (reply) {
            // 等待相應
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)" << endl;
        }
    } else { // 非同步的
        err = waitForResponse(nullptr, nullptr);
    }
    return err;
}
複製程式碼

上面會呼叫 writeTransactionData() 方法用來將資料寫入到 Parcel 中。然後將會進入 waitForResponse() 方法處理與 ServiceManager 互動的結果。而真實的互動發生的地方位於 talkWithDriver() 方法,

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();

    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        // 通過 ioctl 讀寫操作,與 Binder Driver 進行互動
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
    } while (err == -EINTR);
    // ...
    return err;
}
複製程式碼

binder_write_read 結構體用來與 Binder 裝置交換資料的結構, 通過 ioctl 與 mDriverFD 通訊,是真正與 Binder 驅動進行資料讀寫互動的過程。先向service manager程式傳送查詢服務的請求(BR_TRANSACTION)。然後,service manager 會在之前開啟的迴圈中監聽到,並使用 svcmgr_handler() 方法進行處理。

// platform/framework/native/cmds/servicemanager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    // ...
    switch(txn->code) {
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            s = bio_get_string16(msg, &len);
            if (s == NULL) {
                return -1;
            }
            handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
            if (!handle)
                break;
            bio_put_ref(reply, handle);
            return 0;
        case SVC_MGR_ADD_SERVICE: // ...
        case SVC_MGR_LIST_SERVICES: // ...
    }
    return 0;
}
複製程式碼

顯然,這裡會從 binder_transaction_data 中取出 code,即 SVC_MGR_GET_SERVICE,然後使用 do_find_service() 方法查詢服務。然後再 binder_send_reply() 應答發起者將結果返回即可。

4.5 Binder 高效通訊的原因

上面我們梳理了 Binder 通訊的過程,從上面我們似乎並沒有看到能證明 Binder 高效的證據。那麼 Binder 究竟靠什麼實現高效的呢?

實際上,Binder 之所以高效,從我們上面的程式碼還真看不出來。因為,我們上面的程式碼並沒有涉及 Binder 驅動部分。正如我們之前描述的那樣,ServiceManager、客戶端和伺服器實際是靠 Binder 驅動這個中間媒介進行互動的。而 Binder 高效的地方就發生在 Binder 驅動部分。

圖片來自-寫給 Android 應用工程師的Binder原理剖析-https://zhuanlan.zhihu.com/p/35519585

圖片來自 《寫給 Android 應用工程師的Binder原理剖析》

就像圖片描述的那樣,當兩個程式之間需要通訊的時候,Binder 驅動會在兩個程式之間建立兩個對映關係:核心快取區和核心中資料接收快取區之間的對映關係,以及核心中資料接收快取區和接收程式使用者空間地址的對映關係。這樣,當把資料從 1 個使用者空間拷貝到核心緩衝區的時候,就相當於拷貝到了另一個使用者空間中。這樣只需要做一次拷貝,省去了核心中暫存這個步驟,提升了一倍的效能。實現記憶體對映靠的就是上面的 mmap() 函式。

4、Binder 的使用

4.1 代理模式

Binder 本質上只是一種底層通訊方式,和具體服務沒有關係。為了提供具體服務,Server 必須提供一套介面函式以便 Client 通過遠端訪問使用各種服務。這時通常採用代理設計模式:將介面函式定義在一個抽象類中,ServerClient 都會以該抽象類為基類實現所有介面函式。所不同的是 Server 端是真正的功能實現,而 Client 端是對這些函式遠端呼叫請求的包裝。為了簡化這種設計模式,Android 中提供了 AIDL 供我們使用。下文中我們會介紹 AIDL 相關的內容以及它的一些基本的使用方式。

4.2 AIDL

AIDL (Android Interface Definition Language,Android 介面定義語言) 是一種檔案格式,用來簡化 Binder 的使用。當使用 Binder 的時候,只需要建立一個字尾名為 .aidl 的檔案,然後像定義介面一樣定義方法。定義完畢之後,使用工具 aidl.exe 即可生成 Binder 所需要的各種檔案。當然,我們的 AS 已經為我們整合了 aidl.exe,所以,只需要在定義了 AIDL 檔案之後,編譯即可生成使用 Binder 時所需的檔案。當然,不使用 AIDL,直接編寫 Binder 所需的 java 檔案也是可以的。

AIDL 是一種介面定義語言,它與 Java 中定義介面的方式有所區別。下面我們通過一個例子來說明 AIDL 的使用方式。

這裡我們模擬一個筆記管理的類,通過在 Activity 中與一個遠端的 Service 進行互動來實現 IPC 的效果。這裡,我們先要定義資料實體 Note,它只包含兩個欄位,並且實現了 Parcelable。這裡 Note 所在的目錄是 me.shouheng.advanced.aidl,然後,我們需要在 src/main 建立一個同樣的包路徑,然後定義所需的 AIDL 檔案:

    // INoteManager.aidl
    package me.shouheng.advanced.aidl;
    import me.shouheng.advanced.aidl.Note;
    interface INoteManager {
        Note getNote(long id);
        void addNote(long id, String name);
    }

    // Note.aidl
    package me.shouheng.advanced.aidl;
    parcelable Note;
複製程式碼

注意,在 INoteManager 檔案中,我們定義了遠端服務所需的各種方法。這裡只定義了兩個方法,一個用來獲取指定 id 的筆記,一個用來向遠端服務中新增一條筆記記錄。

這樣定義完了之後,我們可以對專案進行編譯,這樣就可以 build 目錄下面得到為我們生成好的 INoteManager 類檔案。以後,我們就可以使用這個檔案中生成類和方法來進行遠端通訊。但在使用該介面之前,我們還是先來看一下其中都生成了些什麼東西:

package me.shouheng.advanced.aidl;

public interface INoteManager extends android.os.IInterface {

    // 交給遠端來實現具體的業務邏輯
    public static abstract class Stub extends android.os.Binder implements me.shouheng.advanced.aidl.INoteManager {

        private static final java.lang.String DESCRIPTOR = "me.shouheng.advanced.aidl.INoteManager";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        // 使用代理包裝遠端物件
        public static me.shouheng.advanced.aidl.INoteManager asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof me.shouheng.advanced.aidl.INoteManager))) {
                return ((me.shouheng.advanced.aidl.INoteManager)iin);
            }
            // 返回代理物件
            return new me.shouheng.advanced.aidl.INoteManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        // 真實地傳送資料交換的地方
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    // 使用模板方法來實現業務
                    me.shouheng.advanced.aidl.Note _result = this.getNote(_arg0);
                    reply.writeNoException();
                    if ((_result!=null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    // 使用模板方法來實現業務
                    this.addNote(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        // 代理物件,包裝了遠端物件,內部呼叫遠端物件獲取遠端的服務資訊
        private static class Proxy implements me.shouheng.advanced.aidl.INoteManager {

            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                me.shouheng.advanced.aidl.Note _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    // 實際內部呼叫遠端物件,在另一個程式實現業務邏輯
                    mRemote.transact(Stub.TRANSACTION_getNote, _data, _reply, 0);
                    _reply.readException();
                    if ((0!=_reply.readInt())) {
                        _result = me.shouheng.advanced.aidl.Note.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addNote(long id, java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    _data.writeString(name);
                    // 實際內部呼叫遠端物件,在另一個程式實現業務邏輯
                    mRemote.transact(Stub.TRANSACTION_addNote, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        // 方法 id,用來標記當前呼叫的是哪個方法
        static final int TRANSACTION_getNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException;

    public void addNote(long id, java.lang.String name) throws android.os.RemoteException;
}
複製程式碼

如果只是看這上面的生成的程式碼,也許你仍然無法瞭解這些生成的類究竟有什麼作用。下面就讓我們通過使用上面生成的類來說明 AIDL 的具體工作流程。

首先,我們要定義遠端的服務,並在該服務中實現業務邏輯:

public class NoteService extends Service {

    private CopyOnWriteArrayList<Note> notes = new CopyOnWriteArrayList<>();

    // 當前服務執行於另一個程式,這裡實現業務邏輯
    private Binder binder = new INoteManager.Stub() {
        @Override
        public Note getNote(long id) {
            return Observable.fromIterable(notes).filter(note -> note.id == id).singleOrError().blockingGet();
        }

        @Override
        public void addNote(long id, String name) {
            notes.add(new Note(id, name));
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        notes.add(new Note(100, "Note 100"));
        notes.add(new Note(101, "Note 101"));
    }

    // 將 binder 返回,客戶端可以使用連線來獲取並呼叫
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}
複製程式碼

這裡在 onCreate() 方法中建立了兩條記錄,並且建立了 INoteManager.Stub 的例項,並在 onBind() 方法中將其返回。然後,我們在一個 Activity 中啟動該遠端服務,並嘗試從該服務中獲取指定 id 的筆記記錄。從期望的結果來看,它的功能有些類似於 ContentProvider,即用來向呼叫者提供資料。

下面是該 Activity 的實現。這裡我們在 onCreate() 方法中啟動上述服務。並將例項化的 ServiceConnection 作為引數啟動該服務。在 ServiceConnection 的方法中,我們呼叫 INoteManager.StubasInterface(IBinder) 方法來講 service 轉換成 INoteManager,然後從其中獲取指定 id 的筆記記錄即可。

    // 建立服務連線
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 返回代理物件
            INoteManager noteManager = INoteManager.Stub.asInterface(service);
            try {
                // 使用代理物件
                Note note = noteManager.getNote(100);
                LogUtils.d(note);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) { }
    };

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        Intent intent = new Intent(this, NoteService.class);
        // 繫結服務
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁服務
        unbindService(connection);
    }
}
複製程式碼

根據 INoteManager.StubasInterface() 方法的定義,該方法中會將傳入的 service 包裝成一個 INoteManager.Stub.Proxy 返回,所以,我們在 onServiceConnected() 方法中實際呼叫的是該代理類的 getNote() 方法。而該代理類的 getNote() 方法中又呼叫了傳入的 mRemote.transact() 方法。而這裡的 service 正是我們在 NoteService 中建立的 binder。也就是說,當我們在 onServiceConnected() 中呼叫 getNote() 方法的時候,實際上呼叫了 INoteManager.Stubtransact() 方法。

所以,從上面我們看出:

  1. 這裡就像是在當前程式中呼叫了另一個程式的方法一樣。這個呼叫的過程是通過 Binder 來實現的。
  2. 當呼叫 INoteManager.Stubtransact() 方法的時候,通過傳入了一個整型的 code 來作為要觸發的方法的標識,這就是我們上面提到的方法的編號。

於是,我們可以通過下面的這張圖來總結在上面使用 AIDL 的過程中各部分扮演的角色:

AIDL

也就是客戶端通過 Proxy 訪問 Binder 驅動,然後 Binder 驅動呼叫 Stub,而 Stub 中呼叫我們的業務邏輯。這裡的 ProxyStub 用來統一介面函式,Proxy 用來告訴我們遠端服務中有哪些可用的方法,而具體的業務邏輯則由 Stub 來實現。Binder 的程式通訊就發生在 ProxyStub 之間。

總結

以上就是 Binder 的工作原理,如有疑問,歡迎評論區交流。

參考資料

原始碼Android-references

相關文章