理解 Android Binder 機制(二):C++層

liuxuhui發表於2021-09-09

本文是Android Binder機制解析的第二篇文章,會講解Binder Framework的C++部分邏輯。

Binder機制分析的第一篇文章,請移步這裡:理解 Android Binder 機制(一):驅動篇

前言

Framework是一箇中間層,它對接了底層實現,封裝了複雜的內部邏輯,並提供供外部使用的介面。Framework層是應用程式開發的基礎。

Binder Framework層分為C++和Java兩個部分,為了達到功能的複用,中間通過JNI進行銜接。

Binder Framework的C++部分,標頭檔案位於這個路徑:/frameworks/native/include/binder/,實現位於這個路徑:/frameworks/native/libs/binder/ 。Binder庫最終會編譯成一個動態連結庫:libbinder.so,供其他程式連結使用。

為了便於說明,下文中我們將Binder Framework 的C++部分稱之為libbinder。

主要結構

libbinder中,將實現分為Proxy和Native兩端。Proxy對應了上文提到的Client端,是服務對外提供的介面。而Native是服務實現的一端,對應了上文提到的Server端。類名中帶有小寫字母p的(例如BpInterface),就是指Proxy端。類名帶有小寫字母n的(例如BnInterface),就是指Native端。

Proxy代表了呼叫方,通常與服務的實現不在同一個程式,因此下文中,我們也稱Proxy端為“遠端”端。Native端是服務實現的自身,因此下文中,我們也稱Native端為”本地“端。

這裡,我們先對libbinder中的主要類做一個簡要說明,瞭解一下它們的關係,然後再詳細的講解。

類名 說明
BpRefBase RefBase的子類,提供remote()方法獲取遠端Binder
IInterface Binder服務介面的基類,Binder服務通常需要同時提供本地介面和遠端介面
BpInterface 遠端介面的基類,遠端介面是供客戶端呼叫的介面集
BnInterface 本地介面的基類,本地介面是需要服務中真正實現的介面集
IBiner Binder物件的基類,BBinder和BpBinder都是這個類的子類
BpBinder 遠端Binder,這個類提供transact方法來傳送請求,BpXXX實現中會用到
BBinder 本地Binder,服務實現方的基類,提供了onTransact介面來接收請求
ProcessState 代表了使用Binder的程式
IPCThreadState 代表了使用Binder的執行緒,這個類中封裝了與Binder驅動通訊的邏輯
Parcel 在Binder上傳遞的資料的包裝器

下圖描述了這些類之間的關係:

另外說明一下,Binder服務的實現類(圖中紫色部分)通常都會遵守下面的命名規則:

  • 服務的介面使用I字母作為字首
  • 遠端介面使用Bp作為字首
  • 本地介面使用Bn作為字首

看了上面這些介紹,你可能還是不太容易理解。不過不要緊,下面我們會逐步拆分講解這些內容。

在這幅圖中,淺黃色部分的結構是最難理解的,因此我們先從它們著手。

我們先來看看IBinder這個類。這個類描述了所有在Binder上傳遞的物件,它既是Binder本地物件BBinder的父類,也是Binder遠端物件BpBinder的父類。這個類中的主要方法說明如下:

方法名 說明
localBinder 獲取本地Binder物件
remoteBinder 獲取遠端Binder物件
transact 進行一次Binder操作
queryLocalInterface 嘗試獲取本地Binder,如何失敗返回NULL
getInterfaceDescriptor 獲取Binder的服務介面描述,其實就是Binder服務的唯一標識
isBinderAlive 查詢Binder服務是否還活著
pingBinder 傳送PING_TRANSACTION給Binder服務

BpBinder的例項代表了遠端Binder,這個類的物件將被客戶端呼叫。其中handle方法會返回指向Binder服務實現者的控制程式碼,這個類最重要就是提供了transact方法,這個方法會將遠端呼叫的引數封裝好傳送的Binder驅動。

由於每個Binder服務通常都會提供多個服務介面,而這個方法中的uint32_t code引數就是用來對服務介面進行編號區分的。Binder服務的每個介面都需要指定一個唯一的code,這個code要在Proxy和Native端配對好。當客戶端將請求傳送到服務端的時候,服務端根據這個code(onTransact方法中)來區分呼叫哪個介面方法。

BBinder的例項代表了本地Binder,它描述了服務的提供方,所有Binder服務的實現者都要繼承這個類(的子類),在繼承類中,最重要的就是實現onTransact方法,因為這個方法是所有請求的入口。因此,這個方法是和BpBinder中的transact方法對應的,這個方法同樣也有一個uint32_t code引數,在這個方法的實現中,由服務提供者通過code對請求的介面進行區分,然後呼叫具體實現服務的方法。

IBinder中定義了uint32_t code允許的範圍:

FIRST_CALL_TRANSACTION  = 0x00000001,
LAST_CALL_TRANSACTION   = 0x00ffffff,

Binder服務要保證自己提供的每個服務介面有一個唯一的code,例如某個Binder服務可以將:add介面code設為1,minus介面code設為2,multiple介面code設為3,divide介面code設為4,等等。

講完了IBinder,BpBinder和BBinder三個類,我們再來看看BpReBase,IInterface,BpInterface和BnInterface。

每個Binder服務都是為了某個功能而實現的,因此其本身會定義一套介面集(通常是C++的一個類)來描述自己提供的所有功能。而Binder服務既有自身實現服務的類,也要有給客戶端程式呼叫的類。為了便於開發,這兩中類裡面的服務介面應當是一致的,例如:假設服務實現方提供了一個介面為add(int a, int b)的服務方法,那麼其遠端介面中也應當有一個add(int a, int b)方法。因此為了實現方便,本地實現類和遠端介面類需要有一個公共的描述服務介面的基類(即上圖中的IXXXService)來繼承。而這個基類通常是IInterface的子類,IInterface的定義如下:

class IInterface : public virtual RefBase
{
public:
            IInterface();
            static sp<IBinder>  asBinder(const IInterface*);
            static sp<IBinder>  asBinder(const sp<IInterface>&);

protected:
    virtual                     ~IInterface();
    virtual IBinder*            onAsBinder() = 0;
};

之所以要繼承自IInterface類是因為這個類中定義了onAsBinder讓子類實現。onAsBinder在本地物件的實現類中返回的是本地物件,在遠端物件的實現類中返回的是遠端物件。onAsBinder方法被兩個靜態方法asBinder方法呼叫。有了這些介面之後,在程式碼中便可以直接通過IXXX::asBinder方法獲取到不用區分本地還是遠端的IBinder物件。這個在跨程式傳遞Binder物件的時候有很大的作用(因為不用區分具體細節,只要直接呼叫和傳遞就好)。

下面,我們來看一下BpInterface和BnInterface的定義:

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();
};

// ----------------------------------------------------------------------

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};

這兩個類都是模板類,它們在繼承自INTERFACE的基礎上各自繼承了另外一個類。這裡的INTERFACE便是我們Binder服務介面的基類。另外,BnInterface繼承了BBinder類,由此可以通過複寫onTransact方法來提供實現。BpInterface繼承了BpRefBase,通過這個類的remote方法可以獲取到指向服務實現方的控制程式碼。在客戶端介面的實現類中,每個介面在組裝好引數之後,都會呼叫remote()->transact來傳送請求,而這裡其實就是呼叫的BpBinder的transact方法,這樣請求便通過Binder到達了服務實現方的onTransact中。這個過程如下圖所示:

基於Binder框架開發的服務,除了滿足上文提到的類名規則之外,還需要遵守其他一些共同的規約:

  • 為了進行服務的區分,每個Binder服務需要指定一個唯一的標識,這個標識通過getInterfaceDescriptor返回,型別是一個字串。通常,Binder服務會在類中定義static const android::String16 descriptor;這樣一個常量來描述這個識別符號,然後在getInterfaceDescriptor方法中返回這個常量。
  • 為了便於呼叫者獲取到呼叫介面,服務介面的公共基類需要提供一個android::sp<IXXX> asInterface方法來返回基類物件指標。

由於上面提到的這兩點對於所有Binder服務的實現邏輯都是類似的。為了簡化開發者的重複工作,在libbinder中,定義了兩個巨集來簡化這些重複工作,它們是:

#define DECLARE_META_INTERFACE(INTERFACE)                            \
    static const android::String16 descriptor;                       \
    static android::sp<I##INTERFACE> asInterface(                    \
            const android::sp<android::IBinder>& obj);               \
    virtual const android::String16& getInterfaceDescriptor() const; \
    I##INTERFACE();                                                  \
    virtual ~I##INTERFACE();                                         \

#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                    \
    const android::String16 I##INTERFACE::descriptor(NAME);          \
    const android::String16&                                         \
            I##INTERFACE::getInterfaceDescriptor() const {           \
        return I##INTERFACE::descriptor;                             \
    }                                                                \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(             \
            const android::sp<android::IBinder>& obj)                \
    {                                                                \
        android::sp<I##INTERFACE> intr;                              \
        if (obj != NULL) {                                           \
            intr = static_cast<I##INTERFACE*>(                       \
                obj->queryLocalInterface(                            \
                        I##INTERFACE::descriptor).get());            \
            if (intr == NULL) {                                      \
                intr = new Bp##INTERFACE(obj);                       \
            }                                                        \
        }                                                            \
        return intr;                                                 \
    }                                                                \
    I##INTERFACE::I##INTERFACE() { }                                 \
    I##INTERFACE::~I##INTERFACE() { }                                \

有了這兩個巨集之後,開發者只要在介面基類(IXXX)標頭檔案中,使用DECLARE_META_INTERFACE巨集便完成了需要的元件的宣告。然後在cpp檔案中使用IMPLEMENT_META_INTERFACE便完成了這些元件的實現。

Binder的初始化

在講解Binder驅動的時候我們就提到:任何使用Binder機制的程式都必須要對/dev/binder裝置進行open以及mmap之後才能使用,這部分邏輯是所有使用Binder機制程式共同的。對於這種共同邏輯的封裝便是Framework層的職責之一。libbinder中,ProcessState類封裝了這個邏輯,相關程式碼見下文。

這裡是ProcessState建構函式,在這個函式中,初始化mDriverFD的時候呼叫了open_driver方法開啟binder裝置,然後又在函式體中,通過mmap進行記憶體對映。

ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
    , mExecutingThreadsCount(0)
    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
    , mStarvationStartTimeMs(0)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

open_driver的函式實現如下所示。在這個函式中完成了三個工作:

  • 首先通過open系統呼叫開啟了dev/binder裝置
  • 然後通過ioctl獲取Binder實現的版本號,並檢查是否匹配
  • 最後通過ioctl設定程式支援的最大執行緒數量

關於這部分邏輯背後的處理,在講解Binder驅動的時候,我們已經講解過了。

static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    if (fd >= 0) {
        int vers = 0;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}

ProcessState是一個Singleton(單例)型別的類,在一個程式中,只會存在一個例項。通過ProcessState::self()介面獲取這個例項。一旦獲取這個例項,便會執行其建構函式,由此完成了對於Binder裝置的初始化工作。

關於Binder傳遞資料的大小限制

由於Binder的資料需要跨程式傳遞,並且還需要在核心上開闢空間,因此允許在Binder上傳遞的資料並不是無無限大的。mmap中指定的大小便是對資料傳遞的大小限制:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k

mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

這裡我們看到,在進行mmap的時候,指定了最大size為BINDER_VM_SIZE,即 1M – 8k的大小。 因此我們在開發過程中,一次Binder呼叫的資料總和不能超過這個大小。

對於這個區域的大小,我們也可以在裝置上進行確認。這裡我們還之前提到的system_server為例。上面我們講解了通過procfs來獲取對映的記憶體地址,除此之外,我們也可以通過showmap命令,來確定這塊區域的大小,相關命令如下:

angler:/ # ps  | grep system_server                                            
system    1889  526   2353404 135968 SyS_epoll_ 72972eeaf4 S system_server
angler:/ # showmap 1889 | grep "/dev/binder"                                   
    1016        4        4        0        0        4        0        0    1 /dev/binder

這裡可以看到,這塊區域的大小正是 1M – 8K = 1016k。

Tips: 通過showmap命令可以看到程式的詳細記憶體佔用情況。在實際的開發過程中,當我們要對某個程式做記憶體佔用分析的時候,這個命令是相當有用的。建議讀者嘗試通過showmap命令檢視system_server或其他感興趣程式的完整map,看看這些程式都依賴了哪些庫或者模組,以及記憶體佔用情況是怎樣的。

與驅動的通訊

上文提到ProcessState是一個單例類,一個程式只有一個例項。而負責與Binder驅動通訊的IPCThreadState也是一個單例類。但這個類不是一個程式只有一個例項,而是一個執行緒有一個例項。

IPCThreadState負責了與驅動通訊的細節處理。這個類中的關鍵幾個方法說明如下:

方法 說明
transact 公開介面。供Proxy傳送資料到驅動,並讀取返回結果
sendReply 供Server端寫回請求的返回結果
waitForResponse 傳送請求後等待響應結果
talkWithDriver 通過ioctl BINDER_WRITE_READ來與驅動通訊
writeTransactionData 寫入一次事務的資料
executeCommand 處理binder_driver_return_protocol協議命令
freeBuffer 通過BC_FREE_BUFFER命令釋放Buffer

BpBinder::transact方法在傳送請求的時候,其實就是直接呼叫了IPCThreadState對應的方法來傳送請求到Binder驅動的,相關程式碼如下:

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::transact方法主要邏輯如下:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    if (err == NO_ERROR) {
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }

    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }

    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }

    return err;
}

這段程式碼應該還是比較好理解的:首先通過writeTransactionData寫入資料,然後通過waitForResponse等待返回結果。TF_ONE_WAY表示此次請求是單向的,即:不用真正等待結果即可返回。

writeTransactionData方法其實就是在組裝binder_transaction_data資料:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else if (statusBuffer) {
        tr.flags |= TF_STATUS_CODE;
        *statusBuffer = err;
        tr.data_size = sizeof(status_t);
        tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
        tr.offsets_size = 0;
        tr.data.ptr.offsets = 0;
    } else {
        return (mLastError = err);
    }

    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));

    return NO_ERROR;
}

對於binder_transaction_data在講解Binder驅動的時候我們已經詳細講解過了。而這裡的Parcel我們還不瞭解,那麼接下來我們馬上就來看一下這個類。

資料包裝器:Parcel

Binder上提供的是跨程式的服務,每個服務包含了不同的介面,每個介面的引數數量和型別都不一樣。那麼當客戶端想要呼叫服務端的介面,引數是如何跨程式傳遞給服務端的呢?除此之外,服務端想要給客戶端返回結果,結果又是如何傳遞回來的呢?

這些問題的答案就是:Parcel。Parcel就像一個包裝器,呼叫者可以以任意順序往裡面放入需要的資料,所有寫入的資料就像是被打成一個整體的包,然後可以直接在Binde上傳輸。

Parcel提供了所有基本型別的寫入和讀出介面,下面是其中的一部分:

...
status_t            writeInt32(int32_t val);
status_t            writeUint32(uint32_t val);
status_t            writeInt64(int64_t val);
status_t            writeUint64(uint64_t val);
status_t            writeFloat(float val);
status_t            writeDouble(double val);
status_t            writeCString(const char* str);
status_t            writeString8(const String8& str);

status_t            readInt32(int32_t *pArg) const;
uint32_t            readUint32() const;
status_t            readUint32(uint32_t *pArg) const;
int64_t             readInt64() const;
status_t            readInt64(int64_t *pArg) const;
uint64_t            readUint64() const;
status_t            readUint64(uint64_t *pArg) const;
float               readFloat() const;
status_t            readFloat(float *pArg) const;
double              readDouble() const;
status_t            readDouble(double *pArg) const;
intptr_t            readIntPtr() const;
status_t            readIntPtr(intptr_t *pArg) const;
bool                readBool() const;
status_t            readBool(bool *pArg) const;
char16_t            readChar() const;
status_t            readChar(char16_t *pArg) const;
int8_t              readByte() const;
status_t            readByte(int8_t *pArg) const;

// Read a UTF16 encoded string, convert to UTF8
status_t            readUtf8FromUtf16(std::string* str) const;
status_t            readUtf8FromUtf16(std::unique_ptr<std::string>* str) const;

const char*         readCString() const;
...

因此對於基本型別,開發者可以直接呼叫介面寫入和讀出。而對於非基本型別,需要由開發者將其拆分成基本型別然後寫入到Parcel中(讀出的時候也是一樣)。 Parcel會將所有寫入的資料進行打包,Parcel本身可以作為一個整體在程式間傳遞。接收方在收到Parcel之後,只要按寫入同樣的順序讀出即可。

這個過程,和我們現實生活中寄送包裹做法是一樣的:我們將需要寄送的包裹放到硬紙盒中交給快遞公司。快遞公司將所有的包裹進行打包,然後集中放到運輸車中送到目的地,到了目的地之後然後再進行拆分。

Parcel既包含C++部分的實現,也同時提供了Java的介面,中間通過JNI銜接。Java層的介面其實僅僅是一層包裝,真正的實現都是位於C++部分中,它們的關係如下圖所示:

特別需要說明一下的是,Parcel類除了可以傳遞基本資料型別,還可以傳遞Binder物件:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

這個方法寫入的是sp<IBinder>型別的物件,而IBinder既可能是本地Binder,也可能是遠端Binder,這樣我們就不可以不用關心具體細節直接進行Binder物件的傳遞。

這也是為什麼IInterface中定義了兩個asBinder的static方法,如果你不記得了,請回憶一下這兩個方法:

static sp<IBinder>  asBinder(const IInterface*);
static sp<IBinder>  asBinder(const sp<IInterface>&);

而對於Binder驅動,我們前面已經講解過:Binder驅動並不是真的將物件在程式間序列化傳遞,而是由Binder驅動完成了對於Binder物件指標的解釋和翻譯,使呼叫者看起來就像在程式間傳遞物件一樣。

Framework層的執行緒管理

在講解Binder驅動的時候,我們就講解過驅動中對應執行緒的管理。這裡我們再來看看,Framework層是如何與驅動層對接進行執行緒管理的。

ProcessState::setThreadPoolMaxThreadCount 方法中,會通過BINDER_SET_MAX_THREADS命令設定程式支援的最大執行緒數量:

#define DEFAULT_MAX_BINDER_THREADS 15

status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
    status_t result = NO_ERROR;
    if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {
        mMaxThreads = maxThreads;
    } else {
        result = -errno;
        ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
    }
    return result;
}

由此驅動便知道了該Binder服務支援的最大執行緒數。驅動在執行過程中,會根據需要,並在沒有超過上限的情況下,通過BR_SPAWN_LOOPER命令通知程式建立執行緒:

IPCThreadState在收到BR_SPAWN_LOOPER請求之後,便會呼叫ProcessState::spawnPooledThread來建立執行緒:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    ...
    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;
    ...
}

ProcessState::spawnPooledThread方法負責為執行緒設定名稱並建立執行緒:

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

執行緒在run之後,會呼叫threadLoop將自身新增的執行緒池中:

virtual bool threadLoop()
{
   IPCThreadState::self()->joinThreadPool(mIsMain);
   return false;
}

IPCThreadState::joinThreadPool方法中,會根據當前執行緒是否是主執行緒傳送BC_ENTER_LOOPER或者BC_REGISTER_LOOPER命令告知驅動執行緒已經建立完畢。整個呼叫流程如下圖所示:

C++ Binder服務舉例

單純的理論知識也許並不能讓我們非常好的理解,下面我們以一個具體的Binder服務例子來結合上文的知識進行講解。

下面以PowerManager為例,來看看C++的Binder服務是如何實現的。

下圖是PowerManager C++部分的實現類圖(PowerManager也有Java層的介面,但我們這裡就不討論了)。

圖中Binder Framework中的類我們在上文中已經介紹過了,而PowerManager相關的四個類,便是在Framework的基礎上開發的。

IPowerManager定義了PowerManager所有對外提供的功能介面,其子類都繼承了這些介面。

  • BpPowerManager是提供給客戶端呼叫的遠端介面
  • BnPowerManager中只有一個onTransact方法,該方法根據請求的code來對接每個請求,並直接呼叫PowerManager中對應的方法
  • PowerManager是服務真正的實現

在IPowerManager.h中,通過DECLARE_META_INTERFACE(PowerManager);宣告一些Binder必要的元件。在IPowerManager.cpp中,通過IMPLEMENT_META_INTERFACE(PowerManager, "android.os.IPowerManager");巨集來進行實現。

本地實現:Native端

服務的本地實現主要就是實現BnPowerManager和PowerManager兩個類,PowerManager是BnPowerManager的子類,因此在BnPowerManager中呼叫自身的virtual方法其實都是在子類PowerManager類中實現的。

BnPowerManager類要做的就是複寫onTransact方法,這個方法的職責是:根據請求的code區分具體呼叫的是那個介面,然後按順序從Parcel中讀出打包好的引數,接著呼叫留待子類實現的虛擬函式。需要注意的是:這裡從Parcel讀出引數的順序需要和BpPowerManager中寫入的順序完全一致,否則讀出的資料將是無效的。

電源服務包含了好幾個介面。雖然每個介面的實現邏輯各不一樣,但從Binder框架的角度來看,它們的實現結構是一樣。而這裡我們並不關心電源服務的實現細節,因此我們取其中一個方法看其實現方式即可。

首先我們來看一下BnPowerManager::onTransact中的程式碼片段:

status_t BnPowerManager::onTransact(uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags) {
  switch (code) {
  ...
      case IPowerManager::REBOOT: {
      CHECK_INTERFACE(IPowerManager, data, reply);
      bool confirm = data.readInt32();
      String16 reason = data.readString16();
      bool wait = data.readInt32();
      return reboot(confirm, reason, wait);
    }
  ...
  }
}

這段程式碼中我們看到了實現中是如何根據code區分介面,並通過Parcel讀出呼叫引數,然後呼叫具體服務方的。

PowerManager這個類才真正是服務實現的本體,reboot方法真正實現了重啟的邏輯:

status_t PowerManager::reboot(bool confirm, const String16& reason, bool wait) {
  const std::string reason_str(String8(reason).string());
  if (!(reason_str.empty() || reason_str == kRebootReasonRecovery)) {
    LOG(WARNING) << "Ignoring reboot request with invalid reason \""
                 << reason_str << "\"";
    return BAD_VALUE;
  }

  LOG(INFO) << "Rebooting with reason \"" << reason_str << "\"";
  if (!property_setter_->SetProperty(ANDROID_RB_PROPERTY,
                                     kRebootPrefix + reason_str)) {
    return UNKNOWN_ERROR;
  }
  return OK;
}

通過這樣結構的設計,將框架相關的邏輯(BnPowerManager中的實現)和業務本身的邏輯(PowerManager中的實現)徹底分離開了,保證每一個類都非常的“乾淨”,這一點是很值得我們在做軟體設計時學習的。

服務的釋出

服務實現完成之後,並不是立即就能讓別人使用的。上文中,我們就說到過:所有在Binder上釋出的服務必須要註冊到ServiceManager中才能被其他模組獲取和使用。而在BinderService類中,提供了publishAndJoinThreadPool方法來簡化服務的釋出,其程式碼如下:

static void publishAndJoinThreadPool(bool allowIsolated = false) {
   publish(allowIsolated);
   joinThreadPool();
}

static status_t publish(bool allowIsolated = false) {
   sp<IServiceManager> sm(defaultServiceManager());
   return sm->addService(
           String16(SERVICE::getServiceName()),
           new SERVICE(), allowIsolated);
}

...

static void joinThreadPool() {
   sp<ProcessState> ps(ProcessState::self());
   ps->startThreadPool();
   ps->giveThreadPoolName();
   IPCThreadState::self()->joinThreadPool();
}

由此可見,Binder服務的釋出其實有三個步驟:

  1. 通過IServiceManager::addService在ServiceManager中進行服務的註冊
  2. 通過ProcessState::startThreadPool啟動執行緒池
  3. 通過IPCThreadState::joinThreadPool將主執行緒加入的Binder中

遠端介面:Proxy端

Proxy類是供客戶端使用的。BpPowerManager需要實現IPowerManager中的所有介面。

我們還是以上文提到的reboot介面為例,來看看BpPowerManager::reboot方法是如何實現的:

virtual status_t reboot(bool confirm, const String16& reason, bool wait)
{
   Parcel data, reply;
   data.writeInterfaceToken(IPowerManager::getInterfaceDescriptor());
   data.writeInt32(confirm);
   data.writeString16(reason);
   data.writeInt32(wait);
   return remote()->transact(REBOOT, data, &reply, 0);
}

這段程式碼很簡單,邏輯就是:通過Parcel寫入呼叫引數進行打包,然後呼叫remote()->transact將請求傳送出去。

其實BpPowerManager中其他方法,甚至所有其他BpXXX中所有的方法,實現都是和這個方法一樣的套路。就是:通過Parcel打包資料,通過remote()->transact傳送資料。而這裡的remote()返回的其實就是BpBinder物件,由此經由IPCThreadState將資料傳送到了驅動層。如果你已經不記得,請重新看一下下面這幅圖:

另外,需要一下的是,這裡的REBOOT就是請求的code,而這個code是在IPowerManager中定義好的,這樣子類可以直接使用,並保證是一致的:

enum {
   ACQUIRE_WAKE_LOCK            = IBinder::FIRST_CALL_TRANSACTION,
   ACQUIRE_WAKE_LOCK_UID        = IBinder::FIRST_CALL_TRANSACTION + 1,
   RELEASE_WAKE_LOCK            = IBinder::FIRST_CALL_TRANSACTION + 2,
   UPDATE_WAKE_LOCK_UIDS        = IBinder::FIRST_CALL_TRANSACTION + 3,
   POWER_HINT                   = IBinder::FIRST_CALL_TRANSACTION + 4,
   UPDATE_WAKE_LOCK_SOURCE      = IBinder::FIRST_CALL_TRANSACTION + 5,
   IS_WAKE_LOCK_LEVEL_SUPPORTED = IBinder::FIRST_CALL_TRANSACTION + 6,
   USER_ACTIVITY                = IBinder::FIRST_CALL_TRANSACTION + 7,
   WAKE_UP                      = IBinder::FIRST_CALL_TRANSACTION + 8,
   GO_TO_SLEEP                  = IBinder::FIRST_CALL_TRANSACTION + 9,
   NAP                          = IBinder::FIRST_CALL_TRANSACTION + 10,
   IS_INTERACTIVE               = IBinder::FIRST_CALL_TRANSACTION + 11,
   IS_POWER_SAVE_MODE           = IBinder::FIRST_CALL_TRANSACTION + 12,
   SET_POWER_SAVE_MODE          = IBinder::FIRST_CALL_TRANSACTION + 13,
   REBOOT                       = IBinder::FIRST_CALL_TRANSACTION + 14,
   SHUTDOWN                     = IBinder::FIRST_CALL_TRANSACTION + 15,
   CRASH                        = IBinder::FIRST_CALL_TRANSACTION + 16,
};

服務的獲取

在服務已經發布之後,客戶端該如何獲取其服務介面然後對其發出請求呼叫呢?

很顯然,客戶端應該通過BpPowerManager的物件來請求其服務。但看一眼BpPowerManager的建構函式,我們會發現,似乎沒法直接建立一個這類的物件,因為這裡需要一個sp<IBinder>型別的引數。

BpPowerManager(const sp<IBinder>& impl)
   : BpInterface<IPowerManager>(impl)
{
}

那麼這個sp<IBinder>引數我們該從哪裡獲取呢?

回憶一下前面的內容:Proxy其實是包含了一個指向Server的控制程式碼,所有的請求傳送出去的時候都需要包含這個控制程式碼作為一個標識。而想要拿到這個控制程式碼,我們自然應當想到ServiceManager。我們再看一下ServiceManager的介面自然就知道這個sp<IBinder>該如何獲取了:

/**
* Retrieve an existing service, blocking for a few seconds
* if it doesn't yet exist.
*/
virtual sp<IBinder>         getService( const String16& name) const = 0;

/**
* Retrieve an existing service, non-blocking.
*/
virtual sp<IBinder>         checkService( const String16& name) const = 0;

這裡的兩個方法都可以獲取服務對應的sp<IBinder>物件,一個是阻塞式的,另外一個不是。傳遞的引數是一個字串,這個就是服務在addServer時對應的字串,而對於PowerManager來說,這個字串就是”power”。因此,我們可以通過下面這行程式碼建立出BpPowerManager的物件。

sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
sp<IPowerManager> pm = new BpPowerManager(bs);

但這樣做還會存在一個問題:BpPowerManager中的方法呼叫是經由驅動然後跨程式呼叫的。通常情況下,當我們的客戶端與PowerManager服務所在的程式不是同一個程式的時候,這樣呼叫是沒有問題的。那假設我們的客戶端又剛好和PowerManager服務在同一個程式該如何處理呢?

針對這個問題,Binder Framework提供的解決方法是:通過interface_cast這個方法來獲取服務的介面物件,由這個方法本身根據是否是在同一個程式,來自動確定返回一個本地Binder還是遠端Binder。interface_cast是一個模板方法,其原始碼如下:

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

呼叫這個方法的時候我們需要指定Binder服務的IInterface,因此對於PowerManager,我們需要這樣獲取其Binder介面物件:

const String16 serviceName("power");
sp<IBinder> bs = defaultServiceManager()->checkService(serviceName);
if (bs == NULL) {
  return NAME_NOT_FOUND;
}
sp<IPowerManager> pm = interface_cast<IPowerManager>(bs);

我們再回頭看一下interface_cast這個方法體,這裡是在呼叫INTERFACE::asInterface(obj),而對於IPowerManager來說,其實就是IPowerManager::asInterface(obj)。那麼IPowerManager::asInterface這個方法是哪裡定義的呢?

這個正是上文提到的DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE兩個巨集所起的作用。IMPLEMENT_META_INTERFACE巨集包含了下面這段程式碼:

android::sp<I##INTERFACE> I##INTERFACE::asInterface(  \
       const android::sp<android::IBinder>& obj)      \
{                                                     \
   android::sp<I##INTERFACE> intr;                    \
   if (obj != NULL) {                                 \
       intr = static_cast<I##INTERFACE*>(             \
           obj->queryLocalInterface(                  \
                   I##INTERFACE::descriptor).get());  \
       if (intr == NULL) {                            \
           intr = new Bp##INTERFACE(obj);             \
       }                                              \
   }                                                  \
   return intr;                                       \
}                                                     \

這裡我們將“##INTERFACE”通過“PowerManager”代替,得到的結果就是:

android::sp<IPowerManager> IPowerManager::asInterface(
        const android::sp<android::IBinder>& obj)      
{                                                      
    android::sp<IPowerManager> intr;                   
    if (obj != NULL) {                                 
        intr = static_cast<IPowerManager*>(            
            obj->queryLocalInterface(                  
                    IPowerManager::descriptor).get());
        if (intr == NULL) {                            
            intr = new BpPowerManager(obj);            
        }                                              
    }                                                  
    return intr;                                       
}

這個便是IPowerManager::asInterface方法的實現,這段邏輯的含義就是:

  • 先嚐試通過queryLocalInterface看看能夠獲得本地Binder,如果是在服務所在程式呼叫,自然能獲取本地Binder,否則將返回NULL
  • 如果獲取不到本地Binder,則建立並返回一個遠端Binder。

由此保證了:我們在程式內部的呼叫,是直接通過方法呼叫的形式。而不在同一個程式的時候,才通過Binder進行跨程式的呼叫。

C++層的ServiceManager

前文已經兩次介紹過ServiceManager了,我們知道這個模組負責了所有Binder服務的管理,並且也看到了Binder驅動中對於這個模組的實現。可以說ServiceManager是整個Binder IPC的控制中心和交通樞紐。這裡我們就來看一下這個模組的具體實現。

ServiceManager是一個獨立的可執行檔案,在裝置中的程式名稱是/system/bin/servicemanager,這個也是其可執行檔案的路徑。

ServiceManager實現原始碼的位於這個路徑:frameworks/native/cmds/servicemanager/,其main函式的主要內容如下:

int main()
{
    struct binder_state *bs;

    bs = binder_open(128*1024);
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    ...

    binder_loop(bs, svcmgr_handler);

    return 0;
}

這段程式碼很簡單,主要做了三件事情:

  1. binder_open(128*1024); 是開啟Binder,並指定快取大小為128k,由於ServiceManager提供的介面很簡單(下文會講到),因此並不需要普通程式那麼多(1M – 8K)的快取
  2. binder_become_context_manager(bs) 使自己成為Context Manager。這裡的Context Manager是Binder驅動裡面的名稱,等同於ServiceManager。binder_become_context_manager的方法實現只有一行程式碼:ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); 看過Binder驅動部分解析的內容,這行程式碼應該很容易理解了
  3. binder_loop(bs, svcmgr_handler); 是在Looper上迴圈,等待其他模組請求服務

service_manager.c中的實現與普通Binder服務的實現有些不一樣:並沒有通過繼承介面類來實現,而是通過幾個c語言的函式來完成了實現。這個檔案中的主要方法如下:

方法名稱 方法說明
main 可執行檔案入口函式,剛剛已經做過說明
svcmgr_handler 請求的入口函式,類似於普通Binder服務的onTransact
do_add_service 註冊一個Binder服務
do_find_service 通過名稱查詢一個已經註冊的Binder服務

ServiceManager中,通過svcinfo結構體來描述已經註冊的Binder服務:

struct svcinfo
{
    struct svcinfo *next;
    uint32_t handle;
    struct binder_death death;
    int allow_isolated;
    size_t len;
    uint16_t name[0];
};

next是一個指標,指向下一個服務,通過這個指標將所有服務串成了連結串列。handle是指向Binder服務的控制程式碼,這個控制程式碼是由Binder驅動翻譯,指向了Binder服務的實體(參見驅動中:Binder中的“物件導向”),name是服務的名稱。

ServiceManager的實現邏輯並不複雜,這個模組就好像在整個系統上提供了一個全域性的HashMap而已:通過服務名稱進行服務註冊,然後再通過服務名稱來查詢。而真正複雜的邏輯其實都是在Binder驅動中實現了。

ServiceManager的介面

原始碼路徑:

frameworks/native/include/binder/IServiceManager.h
frameworks/native/libs/binder/IServiceManager.cpp

ServiceManager的C++介面定義如下:

class IServiceManager : public IInterface
{
public:
    DECLARE_META_INTERFACE(ServiceManager);

    virtual sp<IBinder>         getService( const String16& name) const = 0;

    virtual sp<IBinder>         checkService( const String16& name) const = 0;

    virtual status_t            addService( const String16& name,
                                            const sp<IBinder>& service,
                                            bool allowIsolated = false) = 0;

    virtual Vector<String16>    listServices() = 0;

    enum {
        GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
        CHECK_SERVICE_TRANSACTION,
        ADD_SERVICE_TRANSACTION,
        LIST_SERVICES_TRANSACTION,
    };
};

這裡我們看到,ServiceManager提供的介面只有四個,這四個介面說明如下:

介面名稱 介面說明
addService 向ServiceManager中註冊一個新的Service
getService 查詢Service。如果服務不存在,將阻塞數秒
checkService 查詢Service,但是不會阻塞
listServices 列出所有的服務

這其中,最後一個介面是為了除錯而提供的。通過adb shell連線到裝置上之後,可以通過輸入service list 輸出所有註冊的服務列表。這裡”service”可執行檔案其實就是通過呼叫listServices介面獲取到服務列表的。

service命令的原始碼路徑在這裡:frameworks/native/cmds/service

service list的輸出看起來像下面這樣(一次輸出可能有一百多個服務,這裡省略了):

255|angler:/ # service list                                                    
Found 125 services:
0	sip: [android.net.sip.ISipService]
1	nfc: [android.nfc.INfcAdapter]
2	carrier_config: [com.android.internal.telephony.ICarrierConfigLoader]
3	phone: [com.android.internal.telephony.ITelephony]
4	isms: [com.android.internal.telephony.ISms]
5	iphonesubinfo: [com.android.internal.telephony.IPhoneSubInfo]
6	simphonebook: [com.android.internal.telephony.IIccPhoneBook]
7	telecom: [com.android.internal.telecom.ITelecomService]
8	isub: [com.android.internal.telephony.ISub]
9	contexthub_service: [android.hardware.location.IContextHubService]
10	dns_listener: [android.net.metrics.IDnsEventListener]
11	connmetrics: [android.net.IIpConnectivityMetrics]
12	connectivity_metrics_logger: [android.net.IConnectivityMetricsLogger]
13	bluetooth_manager: [android.bluetooth.IBluetoothManager]
14	imms: [com.android.internal.telephony.IMms]
15	media_projection: [android.media.projection.IMediaProjectionManager]
16	launcherapps: [android.content.pm.ILauncherApps]
17	shortcut: [android.content.pm.IShortcutService]
18	fingerprint: [android.hardware.fingerprint.IFingerprintService]
19	trust: [android.app.trust.ITrustManager]
20	media_router: [android.media.IMediaRouterService]
...

普通的Binder服務我們需要通過ServiceManager來獲取介面才能呼叫,那麼ServiceManager的介面有如何獲得呢?在libbinder中,提供了一個defaultServiceManager方法來獲取ServiceManager的Proxy,並且這個方法不需要傳入引數。原因我們在驅動篇中也已經講過了:Binder的實現中,為ServiceManager留了一個特殊的位置,不需要像普通服務那樣通過標識去查詢。defaultServiceManager程式碼如下:

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;

    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }

    return gDefaultServiceManager;
}

結束語

本文我們詳細講解了Binder Framework C++層的實現。

但對於Android App開發者來說,絕大部分情況下都是在用Java語言開發。那麼,在下一篇文章中,我就來詳細講解Binder Framework Java層的實現。並且也會講解AIDL與Binder的關係,敬請期待。

相關文章