Binder對象跨進(jìn)程傳輸?shù)睦斫?/h2>

問題來源

做Android的都知道,我們綁定Service的代碼一般是這么寫的:

bindService(service, object : ServiceConnection {
    override fun onServiceDisconnected(name: ComponentName?) {
    }
    override fun onServiceConnected(name: ComponentName?, service: IBinder) {
    }
}, Context.BIND_AUTO_CREATE)

在onServiceConnected回調(diào)中拿到IBinder對象,再調(diào)用asInterface即可轉(zhuǎn)成接口實例。但是大家想過沒有,這個IBinder對象到底是個啥?和我們在Service中的onBind方法返回的Binder對象是同一個嗎?稍微對進(jìn)程有點概念的人應(yīng)該都知道肯定不會是同一個對象。那么到底是什么呢?為什么拿到了這個IBinder對象就能調(diào)用Service中的方法了呢?可能很多人會覺得這都不算問題吧,反正多半和binder驅(qū)動有關(guān)唄。嗯,好吧,筆者以前也是這么覺得的,直到后面看了一些framework層的一些源碼后,發(fā)現(xiàn)binder對象跨進(jìn)程傳輸很頻繁。舉個例子:我們知道AMS能控制應(yīng)用程序的Activity的啟動和相關(guān)的生命周期,AMS是使用token來標(biāo)識一個應(yīng)用程序的Activity實例的,這個token就是IBinder對象。為什么IBinder對象可以作為token呢?如果IBinder對象可以作為唯一標(biāo)識,那不就說明Binder對象跨進(jìn)程來回傳輸?shù)臅r候在相同的進(jìn)程會恢復(fù)出相同的實例?有了一些疑問,筆記決定好好捋一捋binder對象到底是怎么傳到另一個進(jìn)程的。

Binder對象傳輸過程

應(yīng)用進(jìn)程部分

我們以服務(wù)的注冊過程來詳細(xì)闡述binder對象的傳輸過程。
從Java層的ServiceManager的addService方法開始分析,該方法用于向SMgr注冊服務(wù),代碼如下:

// ServiceManager.java
public static void addService(String name, IBinder service) {
    getIServiceManager().addService(name, service, false);
}
private static IServiceManager getIServiceManager() {
    if (sServiceManager != null) {
        return sServiceManager;
    }
    // Find the service manager
    sServiceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
    return sServiceManager;
}

BinderInternal.getContextObject()這個方法是native方法,它的實現(xiàn)是android_util_binder.cpp的android_os_BinderInternal_getContextObject方法:

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

這里兩個冒號在C++中的意思是指定命名空間調(diào)用函數(shù)。self函數(shù)一般就是返回單例對象。這里的ProcessState是進(jìn)程內(nèi)的單例對象,它在實例化時會打開binder驅(qū)動。我們繼續(xù)看其getContextObject方法:

// ProcessState.cpp
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);
}

直接調(diào)用了另一個方法,注意傳的handle句柄是0,后面會看到,驅(qū)動通過handle句柄就能找到目標(biāo)進(jìn)程的binder實體對象,而0號句柄特指SMgr進(jìn)程的binder實體對象。我們現(xiàn)在跟的代碼當(dāng)前就是要獲取SMgr進(jìn)程提供的服務(wù),而服務(wù)說白了不就是Binder實體對象嘛,繼續(xù)跟進(jìn):

// ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    // 這里的handle_entry是緩存,如果不存在則創(chuàng)建新項,新項的binder域為空
    handle_entry* e = lookupHandleLocked(handle);
    if (e != NULL) {
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            ...
            // 這里很關(guān)鍵,BpBinder是Binder Proxy的縮寫,也就是說BpBinder是C++層的binder代理對象,這里構(gòu)造傳入了handle句柄。
            b = new BpBinder(handle);
            e->binder = b;
            ...
            result = b;
        } ...
    }

    return result;
}

看代碼,注釋寫的很清楚了。
獲取到BpBinder對象后,我們繼續(xù)接上上面的流程,看javaObjectForIBinder方法,這個方法的主要作用就是獲取java層的BinderProxy對象:

jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
    ...
    // 這里先根據(jù)給定的IBinder對象嘗試獲取原來綁定的BinderProxy對象,如果原來綁定的對象還可以用,那么直接返回,否則就會new一個新的;
    // 如果已經(jīng)不可用了,則會執(zhí)行解綁。
    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
    if (object != NULL) {
        jobject res = jniGetReferent(env, object);
        if (res != NULL) {
            return res;
        }
        // 不可用,解綁
        val->detachObject(&gBinderProxyOffsets);
        env->DeleteGlobalRef(object);
    }
    // 這里反射實例化BinderProxy,這里反射用到的類和構(gòu)造方法等信息是在進(jìn)程啟動時初始化的,具體初始化過程可以參考這篇博客:
    // https://blog.csdn.net/fchyang/article/details/82260138
    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
    if (object != NULL) {
        // 這里對新的BinderProxy對象的mObject賦值為IBinder的指針,很關(guān)鍵。
        // 這樣后續(xù)和遠(yuǎn)程進(jìn)程通信時,就能通過mObject找到BpBinder對象。
        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
        ...
        // 進(jìn)行綁定。
        val->attachObject(&gBinderProxyOffsets, refObject,
                jnienv_to_javavm(env), proxy_cleanup);
        ...
    }
    return object;
}

這個返回了,我們就得到了一個java層的BinderProxy對象了,并且它通過mObject域與C++層的BpBinder對象進(jìn)行關(guān)聯(lián)。
接下來我們回到j(luò)ava層,Binder.allowBlocking只是把返回的BinderProxy對象的mWarnOnBlocking變量賦值為false,暫時不知是何含義。
緊接著看ServiceManagerNative.asInterface方法,該方法僅僅是new了一個ServiceManagerProxy對象,而其mRemote指向上面分析的從C++層返回的BinderProxy對象。
這個ServiceManagerProxy實現(xiàn)了IServiceManager接口,這個類的作用就是完成了參數(shù)寫入parcel的臟活累活。至此,我們有了一個java層的接口實例,并且通過mRemote域指向BinderProxy,而BinderProxy又和C++層的BpBinder綁定,BpBinder內(nèi)部包含handler句柄,這層層的持有關(guān)系都是為了拿到handler句柄。當(dāng)然了,這一系列的封裝最大的目的就是為了使遠(yuǎn)程方法調(diào)用像調(diào)用一般方法一樣簡單。
繼續(xù)分析之前,我們先來了解下Parcel。
對于這個Parcel,java層的Parcel只是對C++層Parcel的一個封裝,通過mNativePtr指針指向C++層的Parcel對象。C++層的Parcel其數(shù)據(jù)的組織方式就是一個字節(jié)數(shù)組,寫入數(shù)據(jù)的時候就直接追加到數(shù)組中,因此讀取數(shù)據(jù)時必須按照寫入的順序來讀取,不然類型就對不上了。既然是扁平的數(shù)組,那string和binder對象是怎么存儲和恢復(fù)的呢?答:存儲還是和基本類型數(shù)據(jù)一樣,只不過多存儲了String或者對象的大小,這樣當(dāng)我們readStrongBinder時,Parcel首先會讀取一個整型值,然后把其當(dāng)成len再讀取后面的數(shù)據(jù),最后再強轉(zhuǎn)成相應(yīng)的結(jié)構(gòu)體。
ok,了解了Parcel,我們繼續(xù)分析addService方法,此時我們知道addService的具體實現(xiàn)是ServiceManagerProxy類,ok,跟進(jìn)去:

// ServiceManagerProxy.java
public void addService(String name, IBinder service, boolean allowIsolated)
        throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    // 這里記一下我們寫入了什么數(shù)據(jù),以便理解后面經(jīng)過重重調(diào)用后,在遙遠(yuǎn)的smgr服務(wù)中是怎么解析數(shù)據(jù)的。
    data.writeInterfaceToken(IServiceManager.descriptor); // descriptor是字符串,值為:android.os.IServiceManager
    data.writeString(name); // 服務(wù)的名稱
    data.writeStrongBinder(service); // 要注冊的binder實體對象
    data.writeInt(allowIsolated ? 1 : 0);
    // 如前所述,mRemote是BinderProxy對象
    mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
    reply.recycle();
    data.recycle();
}

writeStrongBinder方法用于向parcel中寫入binder實體對象,binder實體對象和一般的實體對象不同,首先是Binder并沒有實現(xiàn)Parcelable接口,因此寫入Parcel的僅僅是指針,另外因為binder驅(qū)動需要為傳輸?shù)腷inder實體對象建立數(shù)據(jù)結(jié)構(gòu)體,因此必須要標(biāo)記出parcel中哪里存在binder對象的傳輸。
binder實體對象在parcel中以flat_binder_object的結(jié)構(gòu)形式存儲,它的定義如下:

// binder.h
struct flat_binder_object {
   /* 8 bytes for large_flat_header. */
   unsigned long     type;
   unsigned long     flags;

   /* 8 bytes of data. */
   union {
      void      *binder;   /* local object */
      signed long    handle;       /* remote object */
   };

   /* extra data associated with local object */
   void         *cookie;
};

type域表明了存儲的是binder實體對象還是binder引用,如果是實體對象,則后面的binder指針指向該實體對象,如果是引用,則handle域存儲binder句柄。
flags域只對第一次傳遞Binder實體時有效,因為此刻驅(qū)動需要在內(nèi)核中創(chuàng)建相應(yīng)的實體節(jié)點,有些參數(shù)需要從該域取出:第0-7位:代碼中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示處理本實體請求數(shù)據(jù)包的線程的最低優(yōu)先級。當(dāng)一個應(yīng)用程序提供多個實體時,可以通過該參數(shù)調(diào)整分配給各個實體的處理能力。第8位:代碼中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示該實體可以接收其它進(jìn)程發(fā)過來的文件形式的Binder。由于接收文件形式的Binder會在本進(jìn)程中自動打開文件,Server可以用該標(biāo)志禁止該功能,以防打開過多文件。
最后的cookie域則是額外攜帶的數(shù)據(jù),驅(qū)動不關(guān)心該值。
往parcel中寫入binder對象的時候,parcel內(nèi)部會記錄flat_binder_object的位置,以及數(shù)量,后續(xù)會用來賦值給binder_transaction_data結(jié)構(gòu)體:

// parcel.h
size_t*             mObjects; // 指針,指向一個數(shù)組,數(shù)組的每一個元素對應(yīng)一個Parcel中保存的binder對象的偏移。
size_t              mObjectsSize;

參數(shù)等數(shù)據(jù)寫入Parcel后,就調(diào)用mRemote的transact方法,回憶下上面的流程,這里的mRemote就是從C++層返回的BinderProxy對象,而BinderProxy對象是Binder.java的一個內(nèi)部類,它的transact方法的實現(xiàn)是一個native方法,具體實現(xiàn)是android_util_binder.cpp的android_os_BinderProxy_transact方法,我們跟進(jìn)去嘍:

// android_util_binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ...
    // 使用java層Parcel的mNativePtr獲取C++層的Parcel對象。下同
    Parcel* data = parcelForJavaObject(env, dataObj);
    ...
    Parcel* reply = parcelForJavaObject(env, replyObj);
    ...
    // 獲取前面分析的JNI層的BpBinder對象
    IBinder* target = (IBinder*)
        env->GetLongField(obj, gBinderProxyOffsets.mObject);
    ...
    // 使用BpBinder執(zhí)行數(shù)據(jù)傳輸
    status_t err = target->transact(code, *data, reply, flags);
    //...
    return err;
}

整體來看邏輯很簡單,就是根據(jù)mObject域獲取BpBinder對象,然后調(diào)用其transact方法。
BpBinder的transact的實現(xiàn)又進(jìn)一步委托給了IPCThreadState,這個類就是專門和驅(qū)動打交道的,包括server端那邊的BBinder也是委托給這個類來實現(xiàn)和驅(qū)動交互的。
PS:驅(qū)動傳輸數(shù)據(jù)會有自己的一套規(guī)則,IPCThreadState就是封裝屏蔽了規(guī)則的細(xì)節(jié),讓使用者只需關(guān)心業(yè)務(wù)處理即可:

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive) {
        // 特別注意這里傳了mHandle進(jìn)去,他就是目標(biāo)Binder實體的handle句柄,在我們這個環(huán)境handle是0,也就是會找到SMgr的Binder實體。
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

關(guān)于這個handle已經(jīng)強調(diào)了很多次了,之所以一直強調(diào)也是這次研究binder底層得到的最大收獲,也就是binder對象跨進(jìn)程傳輸?shù)谋举|(zhì)就是handle句柄,僅僅是一個整型值。
來看IPCThreadState的transact方法:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    ... 
    if (err == NO_ERROR) {
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
    // 這里后面的代碼是處理回復(fù)的,我們暫時先把請求走通,后續(xù)再補充響應(yīng)(其實響應(yīng)也可以理解為是目標(biāo)進(jìn)程的一次寫入)
    return err;
}

這里writeTransactionData的第一個參數(shù)BC_TRANSACTION是一個驅(qū)動命令字,具體可以查看binder驅(qū)動協(xié)議部分的筆記。我們跟進(jìn)這個方法,就會看到binder協(xié)議中提到的binder_transaction_data結(jié)構(gòu)體了:

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; // 此結(jié)構(gòu)體定義參見Binder驅(qū)動部分筆記
    tr.target.ptr = 0; // 此時ptr是0,后面解析handle后就會賦值為目標(biāo)binder實體對象的地址。
    tr.target.handle = handle; // handle句柄給target的handle域,很關(guān)鍵。
    tr.code = code;
    tr.flags = binderFlags;
    ...
    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData(); // 這個方法其實就是返回parcel的起始地址
        // 下面兩行代碼的賦值,就是大佬文章中提到的在buffer中標(biāo)記出傳輸?shù)腷inder對象,這里data是Parcel,
        // 在往Parcel中寫入Strong binder時,Parcel內(nèi)部已經(jīng)對binder對象進(jìn)行計數(shù)了,并且記錄了其偏移量,
        // 這一點去看writeStrongBinder的代碼就知道了。
        tr.offsets_size = data.ipcObjectsCount() * sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects(); // 返回數(shù)組的起始地址
    } // ...
    // 先寫命令字,再寫結(jié)構(gòu)體數(shù)據(jù)
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
    return NO_ERROR;
}

從上述代碼可以看到數(shù)據(jù)寫入了mOut中,這個mOut是Parcel類型的,用于暫存即將寫入驅(qū)動的數(shù)據(jù),還有一個mInt,用于暫存從驅(qū)動中接收到的數(shù)據(jù)。
那么,什么時候會將mOut真正寫入驅(qū)動呢?
答案是調(diào)用writeTransactionData方法之后的waitResponse方法,該方法內(nèi)部會調(diào)用talkWithDriver,進(jìn)一步的該方法內(nèi)部會通過ioctl方法將mOut中的數(shù)據(jù)傳入驅(qū)動。

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    ...
    // 這里binder_write_read的結(jié)構(gòu)可以看驅(qū)動協(xié)議部分,以下代碼就是對bwr相關(guān)的域進(jìn)行賦值,然后調(diào)用ioctl傳入驅(qū)動。
    binder_write_read bwr;
    ...
    // 先重點關(guān)注buffer部分,這里賦值為了parcel的起始地址,因此整個mOut的內(nèi)容會寫入驅(qū)動
    bwr.write_buffer = (long unsigned int)mOut.data();
    ...
    status_t err;
    do {
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
        ...
    } while (err == -EINTR);
    ...
    return err;
}

OK,到此就要開始進(jìn)入驅(qū)動了,在這里總結(jié)一下目前為止mOut中都存了哪些內(nèi)容:
[cmd, binder_transaction_data] ....
其中cmd是BC_TRANSACTION,tr中:target.handle = 0,data域的buffer指向了我們最上層要傳輸?shù)膬?nèi)容。

驅(qū)動部分

對于字符設(shè)備驅(qū)動,以前也學(xué)過一點,驅(qū)動在實現(xiàn)的時候有個地方會注冊當(dāng)上層調(diào)用open、ioctl等方法時,驅(qū)動對應(yīng)的實現(xiàn)是哪個方法,對于binder驅(qū)動,ioctl方法對應(yīng)的就是binder_ioctl方法,該方法實現(xiàn)在binder.c,我們進(jìn)去看:

// binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
   int ret;
   // binder_proc結(jié)構(gòu)體中存儲了和用戶進(jìn)程有關(guān)的信息,binder_open的時候會實例化。
   struct binder_proc *proc = filp->private_data;
   struct binder_thread *thread;
   ...
   // 這個方法用于獲取給定進(jìn)程一個空閑的binder線程,請注意,一個線程是不是binder線程本質(zhì)上就是
   // 在驅(qū)動中是不是有相應(yīng)的binder_thread結(jié)構(gòu)體和它對應(yīng)(通過pid綁定)。
   // 如果這個方法沒有找到binder_thread和當(dāng)前的線程綁定,則會實例化一個然后建立綁定關(guān)系。
   // PS:線程池的內(nèi)容還有待進(jìn)一步研究,這里先不做過多討論,本次分析的重點在于handle如何解析,以及parcel中的實體對象如何傳遞
   thread = binder_get_thread(proc);
   ...
   switch (cmd) {
       case BINDER_WRITE_READ: {
          struct binder_write_read bwr;
          ...
          if (bwr.write_size > 0) {
             ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
             ...
          }
          if (bwr.read_size > 0) {
             // 這里是讀,通常同步的ipc請求會等待響應(yīng),因此讀的時候可能會阻塞。
          }
          ...
          break;
   }
   ...
   return ret;
}

接著繼續(xù)看binder_thread_write方法,重點關(guān)注對buffer的處理:

int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
         void __user *buffer, int size, signed long *consumed)
{
   uint32_t cmd;
   void __user *ptr = buffer + *consumed;
   void __user *end = buffer + size;
   // 根據(jù)binder驅(qū)動協(xié)議的內(nèi)容,buffer中的結(jié)構(gòu)就是cmd+特定的數(shù)據(jù)結(jié)構(gòu),因此這里是一個循環(huán),取出每一個命令字處理
   // 我們要關(guān)注的是BC_TRANSACTION這個命令字
   while (ptr < end && thread->return_error == BR_OK) {
      // 讀命令字
      if (get_user(cmd, (uint32_t __user *)ptr))
         return -EFAULT;
      ...
      switch (cmd) {
      ...
          case BC_TRANSACTION:
          case BC_REPLY: {
             struct binder_transaction_data tr;
             if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
             ptr += sizeof(tr);
             binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
             break;
          }
      }
      // ...
   }
   return 0;
}

可以看到,會拷貝BC_TRANSACTION命令后面的數(shù)據(jù)到內(nèi)核,并用binder_transaction_data結(jié)構(gòu)體存儲,然后調(diào)用了binder_transaction進(jìn)一步處理,以下代碼重點關(guān)注對tr結(jié)構(gòu)體的處理:

static void binder_transaction(struct binder_proc *proc,
                struct binder_thread *thread,
                struct binder_transaction_data *tr, int reply)
{
   ... // 省略大量變量聲明
   if (reply) {
      ... // 先忽略響應(yīng)的場景
   } else {
       // 高能,這里開始用handle解析出驅(qū)動中的引用
      if (tr->target.handle) {
         struct binder_ref *ref;
         // 這里binder_get_ref會從紅黑樹中找binder_ref對象,對比的字段就是handle和ref的desc域是否一致,
         // 而ref的desc域我們在后面會看到,其實就是紅黑樹的索引,從1開始的編號,可見貫穿Binder機制始終的句柄原來就是一個編號而已。。。
         ref = binder_get_ref(proc, tr->target.handle);
         if (ref == NULL) {
            ... // 錯誤處理,ref是一定要被找到的,不然就是無效的handle句柄
         }
         // 找到ref,輕松就能找到目標(biāo)節(jié)點,即binder_node對象,node里就有實體對象的ptr地址
         target_node = ref->node;
      } else {
          // 這里binder_context_mgr_node特指smgr服務(wù)的binder_node節(jié)點,由此可見,如果handle不傳,驅(qū)動會默認(rèn)訪問的是smgr進(jìn)程。
         target_node = binder_context_mgr_node;
         ...
      }
      ...
   }
   ...
   // t的類型是binder_transaction,驅(qū)動協(xié)議中沒有討論這個結(jié)構(gòu)體,因為它是驅(qū)動內(nèi)部使用的,對它的賦值也很關(guān)鍵,重點關(guān)注以下幾個域的賦值:
   ...
   t->code = tr->code;
   ...
   t->buffer->target_node = target_node;
   ...
   // t->buffer->data會用于存儲tr中的data.ptr.buffer + data.ptr.offsets的數(shù)據(jù),即把parcel中的數(shù)據(jù)和offsets數(shù)組扁平化了。
   // 這里要注意理解,別把我們傳入驅(qū)動的數(shù)據(jù)跟丟了。這里offp會指向offsets數(shù)組的起始位置。
   offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
    // 拷貝用戶數(shù)據(jù),先拷貝parcel數(shù)據(jù)部分
    if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
       ...
    }
    // 拷貝offsets數(shù)組
    if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
       ...
    }
    ...
   off_end = (void *)offp + tr->offsets_size;
   // 開始遍歷處理parcel中傳輸?shù)腷inder對象
   for (; offp < off_end; offp++) {
      struct flat_binder_object *fp;
      ...
      fp = (struct flat_binder_object *)(t->buffer->data + *offp);
      switch (fp->type) {
          // 我們這里是注冊服務(wù),所以type是BINDER_TYPE_BINDER,后面的case處理的是引用的情況,
          // 從smgr中獲取服務(wù)時,數(shù)據(jù)緩存區(qū)中存放的就是binder引用。
      case BINDER_TYPE_BINDER:
      case BINDER_TYPE_WEAK_BINDER: {
         struct binder_ref *ref;
         struct binder_node *node = binder_get_node(proc, fp->binder);
         if (node == NULL) {
             // 這里非常關(guān)鍵,前面嘗試用binder域,即binder實體的指針找出驅(qū)動和它綁定的node對象,
             // 如果找不到,說明該binder實體是首次在驅(qū)動中傳輸,因此這里new了新的node節(jié)點和它綁定。
            node = binder_new_node(proc, fp->binder, fp->cookie);
            ...
         }
         ...
         // 這里嘗試獲取一個和node相對應(yīng)的ref對象,如果不存在,就會生成一個,因為等下馬上要去smgr進(jìn)程了,
         // node是不可能傳過去的,只能ref過去,也就相當(dāng)于binder實體對象嘗試跨驅(qū)動傳輸,但是卻止步于當(dāng)前進(jìn)程,傳過去的只是ref而已。
         ref = binder_get_ref_for_node(target_proc, node);
         // 這里把新的ref的desc域的賦值邏輯貼一下,以便理解handle句柄的本質(zhì):
         // 以下片段是binder_get_ref_for_node方法里的,貼在這里方便分析:
         
         // 片段start
          new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1; // 賦起始值,對于smgr的node,起始賦為0。
          // 傳進(jìn)來的proc是target的proc,refs_by_desc就是target進(jìn)程持有的所有的ref引用,用紅黑樹存儲,for循環(huán)就是遍歷這棵樹。
          for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
               // 這句代碼涉及到linux中紅黑樹的寫法,暫時不用管,只需要知道執(zhí)行完后,ref指向當(dāng)前遍歷到的引用對象。
               ref = rb_entry(n, struct binder_ref, rb_node_desc);
               if (ref->desc > new_ref->desc)
                  break;
               new_ref->desc = ref->desc + 1; // 看到了吧,就是這么簡單,挨個加1就是handle句柄
           }
         // 片段end
         
         ...
         if (fp->type == BINDER_TYPE_BINDER)
            fp->type = BINDER_TYPE_HANDLE; // 關(guān)鍵點:修改type為handle類型,后面即將對handle域賦值。
         else
            fp->type = BINDER_TYPE_WEAK_HANDLE;
         fp->handle = ref->desc; // handle就是desc
         // 以上代碼直接操作fp指向的內(nèi)存,修改了type和handle域,這樣的話數(shù)據(jù)區(qū)域只需整體拷貝到目標(biāo)進(jìn)程即可,
         // 散落在parcel中的flat_binder_object的位置等是不變的。
         ...
      } break;
      ... // 省略其他case
   }
   // 以上,for循環(huán)執(zhí)行完畢,則散落在parcel各個地方的binder實體對象就相應(yīng)的生成了node節(jié)點了,并且后續(xù)的傳輸已經(jīng)變成了handle的傳輸了。
   if (reply) {
      ...
   } else if (!(t->flags & TF_ONE_WAY)) {
       ... // 一般rpc調(diào)用都是同步的,所以看這個if
      thread->transaction_stack = t;
   } else {
      ...
   }
   t->work.type = BINDER_WORK_TRANSACTION;
   // 把t中的binder_work工作項添加到target_list中,這個target_list就是目標(biāo)進(jìn)程的todo隊列,
   // 每個參與進(jìn)程間通信的進(jìn)程在驅(qū)動中都有這樣的隊列,binder線程們會不斷從中取出work工作項處理。
   list_add_tail(&t->work.entry, target_list); 
   ...
   return;

OK,目前為止,傳輸?shù)臄?shù)據(jù)被封裝成了工作項,放到了目標(biāo)進(jìn)程的todo隊列中了?,F(xiàn)在要把鏡頭轉(zhuǎn)向smgr進(jìn)程了,也就是我們的目標(biāo)進(jìn)程。

SMgr進(jìn)程部分

SMgr進(jìn)程啟動的時候,會open驅(qū)動 -> 注冊成為smgr服務(wù) -> 主線程注冊成為binder線程 -> 主線程循環(huán)等待請求數(shù)據(jù)。所謂的循環(huán)等待其實就是嘗試從驅(qū)動中讀取數(shù)據(jù),每次的嘗試讀,驅(qū)動會檢查有沒有你這個線程的工作項,以及檢查有沒有你這個線程所屬進(jìn)程的工作項,如果有,就會把工作項取出來,然后返回,如果沒有,則會等待在binder_proc的wait域上。具體我們看代碼。
smgr啟動過程可以看ServiceManager部分的筆記,這里直接從ioctl開始分析:

res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

BINDER_WRITE_READ這個命令我們在上面已經(jīng)見過了,這里是讀,上面我們分析的是寫,讀部分的代碼如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
   // ...
   switch (cmd) {
   case BINDER_WRITE_READ: {
      struct binder_write_read bwr;
      // ...
      if (bwr.write_size > 0) {
         // ... 寫已經(jīng)分析過了
      }
      if (bwr.read_size > 0) {
         ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, 
                                 &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
         if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait);
         if (ret < 0) {
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
               ret = -EFAULT;
            goto err;
         }
      }
      // ...
      break;
   }
   // ...
   return ret;
}

OK,跟進(jìn)binder_thread_read方法:

static int binder_thread_read(struct binder_proc *proc,
               struct binder_thread *thread,
               void  __user *buffer, int size,
               signed long *consumed, int non_block)
{
   // ...
   int wait_for_proc_work;
   // ... 
   // 這里判斷thread自己是否有工作項要處理
   wait_for_proc_work = thread->transaction_stack == NULL &&
            list_empty(&thread->todo);
   // ...
   if (wait_for_proc_work) {
      // ...
      // non_block是外面?zhèn)鬟^來的,表示線程沒工作時是否阻塞
      if (non_block) {
         if (!binder_has_proc_work(proc, thread))
            ret = -EAGAIN;
      } else {
          // 等待在proc的wait上,第二個參數(shù)是等待的condition,即條件
          ret = wait_event_interruptible_exclusive(proc->wait, binder_has_proc_work(proc, thread));
      } 
   } else {
      if (non_block) {
         if (!binder_has_thread_work(thread))
            ret = -EAGAIN;
      } else
         ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread));
   }
   // ...
   // 代碼能走到這里,說明線程喚醒了,此時有工作項要處理了,因此進(jìn)入循環(huán)處理每一個work項。
   while (1) {
      uint32_t cmd;
      struct binder_transaction_data tr;
      struct binder_work *w;
      struct binder_transaction *t = NULL;
      // 優(yōu)先看線程自己有沒有todo,沒有就去處理進(jìn)程的todo  
      if (!list_empty(&thread->todo))
         w = list_first_entry(&thread->todo, struct binder_work, entry);
      else if (!list_empty(&proc->todo) && wait_for_proc_work)
         w = list_first_entry(&proc->todo, struct binder_work, entry);
      else {
         // ...
      }
      // ...
      // 判斷工作項的類型,回憶前面的流程,我們這個場景的工作項類型是BINDER_WORK_TRANSACTION
      switch (w->type) {
          case BINDER_WORK_TRANSACTION: {
              // 可見,只是把t取出來了,t的類型就是前面提到的binder_transaction
             t = container_of(w, struct binder_transaction, work);
          } break;
          // ... 省略其他case
      }
      // ...
      if (t->buffer->target_node) {
         struct binder_node *target_node = t->buffer->target_node;
         // tr是剛剛定義的binder_transaction_data類型的變量,后面的代碼就是對這個結(jié)構(gòu)體進(jìn)行賦值,最后會傳給目標(biāo)進(jìn)程處理
         tr.target.ptr = target_node->ptr;
         tr.cookie =  target_node->cookie;
         t->saved_priority = task_nice(current);
         // ...
         cmd = BR_TRANSACTION;
      } else {
         // ...
      }
      tr.code = t->code;
      tr.flags = t->flags;
      // ...
      tr.data_size = t->buffer->data_size;
      tr.offsets_size = t->buffer->offsets_size;
      // 前面說過,我們傳入驅(qū)動的數(shù)據(jù)被扁平化到了t->buffer->data里,這里則分別取出來賦值到tr中,
      // 但是注意,數(shù)據(jù)依然是連續(xù)的,只不過此時用了兩個指針去存儲而已。
      tr.data.ptr.buffer = (void *)t->buffer->data +
               proc->user_buffer_offset;
      tr.data.ptr.offsets = tr.data.ptr.buffer +
               ALIGN(t->buffer->data_size,
                   sizeof(void *));
      // ptr就是smgr進(jìn)程讀的時候告訴內(nèi)核要寫入的地址,這里先寫命令字進(jìn)去,我們的場景該值為BR_TRANSACTION             
      if (put_user(cmd, (uint32_t __user *)ptr))
         return -EFAULT;
      ptr += sizeof(uint32_t);
      // 隨后再寫入tr,即binder_transaction_data結(jié)構(gòu)體
      if (copy_to_user(ptr, &tr, sizeof(tr)))
         return -EFAULT;
      ptr += sizeof(tr);
      // ...
      break;
   }
   // ...
   return 0;
}

ok,現(xiàn)在smgr進(jìn)程提供的緩沖區(qū)中已經(jīng)被驅(qū)動寫入binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)了,現(xiàn)在回到smgr進(jìn)程,看看如何處理的:
smgr的for循環(huán)中,一旦ioctl返回,就調(diào)用binder_parse解析數(shù)據(jù):

void binder_loop(struct binder_state *bs, binder_handler func)
{
    // ...
    for (;;) {
        // ...
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        // ...
        res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
        // ...
    }
}

其中readbuf就是驅(qū)動寫入數(shù)據(jù)的地址,跟進(jìn)parse方法:

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uint32_t *ptr, uint32_t size, binder_handler func)
{
    int r = 1;
    uint32_t *end = ptr + (size / 4);
    // 因為驅(qū)動可能傳了很多個[cmd+數(shù)據(jù)]形式的數(shù)據(jù)過來,因此這里循環(huán)逐個處理,我們關(guān)注的是BR_TRANSACTION這個命令。
    while (ptr < end) {
        // 第一個是命令字,直接讀出來
        uint32_t cmd = *ptr++;
        // ...
        switch(cmd) {
        // ...
            case BR_TRANSACTION: {
                // 注意這里,非常騷氣,一度看的我很懵逼,ptr既然是cmd+數(shù)據(jù)格式的數(shù)據(jù),那么BR_TRANSACTION后面跟的
                // 應(yīng)該是binder_transaction_data結(jié)構(gòu)的數(shù)據(jù)啊,怎么這里強轉(zhuǎn)成了binder_txn呢?
                // 后面仔細(xì)對比,原來這個binder_txn結(jié)構(gòu)體和binder_transaction_data結(jié)構(gòu)體驚人的相似,僅僅是把union去掉了,
                // 在C++中,這種強轉(zhuǎn)是沒毛病的。
                struct binder_txn *txn = (void *) ptr;
                // 這里把binder_txn這個結(jié)構(gòu)體貼出來,然后總結(jié)下各個域目前代表的含義,畢竟數(shù)據(jù)經(jīng)過了一波驅(qū)動,記憶會有些模糊。
                struct binder_txn
                {
                    // 驅(qū)動已經(jīng)悄悄修改成了目標(biāo)binder實體的指針,代碼片段:tr.target.ptr = target_node->ptr;
                    void *target; 
                    void *cookie;
                    // 方法編號,賦值片段:tr.code = t->code;
                    uint32_t code;
                    uint32_t flags;
                    
                    uint32_t sender_pid;
                    uint32_t sender_euid;
                    // 數(shù)據(jù)區(qū)的大小,賦值片段:tr.data_size = t->buffer->data_size;
                    uint32_t data_size;
                    // 偏移數(shù)組的大小,片段:tr.offsets_size = t->buffer->offsets_size;
                    uint32_t offs_size;
                    // 原來tr這里的data是一個union,里面有個ptr結(jié)構(gòu)體存儲了兩個指針,一個是數(shù)據(jù)區(qū),一個offsets區(qū),
                    // 強轉(zhuǎn)后,第一個指針就給了這里的data域,而第二個指針就給了這里的offs域。
                    void *data;
                    void *offs;
                };
                // ...
                if (func) { // func函數(shù)是傳進(jìn)來的,名稱是svcmgr_handler
                    // ...
                    struct binder_io msg;
                    // ...
                    // 這里把txn中的相關(guān)域賦值給了binder_io,后面會從這個binder_io中讀取數(shù)據(jù),它里面維護了當(dāng)前讀到了哪里。
                    bio_init_from_txn(&msg, txn);
                    res = func(bs, txn, &msg, &reply);
                    // 。。。
                }
                ptr += sizeof(*txn) / sizeof(uint32_t); // 偏移指針,處理下一個命令字。
                break;
            }
        // ...
        }
    }

    return r;
}

拿到binder_io后,調(diào)用了svcmgr_handler函數(shù),我們繼續(xù)跟進(jìn):

int svcmgr_handler(struct binder_state *bs,
                   struct binder_txn *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    unsigned len;
    void *ptr;
    uint32_t strict_policy;
    int allow_isolated;
    // ...
    // 這里bio_get_unit32會從msg中的data數(shù)據(jù)區(qū)中讀一個指針出來,但是這個strict_policy后面沒有用到,
    // 而且一路跟源碼過來,好像沒有哪里會往數(shù)據(jù)區(qū)中寫入指針?。???
    // 其實在Server端的addService方法中,有這么一行代碼:
    // data.writeInterfaceToken(IServiceManager.descriptor);
    // descriptor的值就是:android.os.IServiceManager
    // writeInterfaceToken這個方法在往parcel中寫入desc的之前,寫入了一個整型值:
    // writeInt32(IPCThreadState::self()->getStrictModePolicy() | STRICT_MODE_PENALTY_GATHER);
    // ok,現(xiàn)在知道指針的來歷了,不過現(xiàn)在不考究strict_policy的含義,之所以這里展開說明是為了要證明msg的data數(shù)據(jù)區(qū)
    // 跟最開始我們寫入parcel中的數(shù)據(jù)是完全一致的。
    strict_policy = bio_get_uint32(msg);
    // 這里就是對應(yīng)獲取descriptor的值,讀字符串就是先讀len,然后讀len個字節(jié)的數(shù)據(jù)。
    s = bio_get_string16(msg, &len);
    if ((len != (sizeof(svcmgr_id) / 2)) ||
        // svcmgr_id是一個字節(jié)數(shù)組,該值正好是android.os.IServiceManager
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s));
        return -1;
    }
    // 根據(jù)code調(diào)用相應(yīng)的方法,我們關(guān)注ADD_SERVICE_TRANSACTION
    switch(txn->code) {
        // ...
        case SVC_MGR_ADD_SERVICE:
            // 讀出service的名稱
            s = bio_get_string16(msg, &len);
            // 這個方法下面要展開分析,因為涉及到handle句柄的讀取問題,這里先只需知道返回的ptr指針會指向一個整型值,而這個整型值
            // 就是handle句柄。
            ptr = bio_get_ref(msg);
            allow_isolated = bio_get_uint32(msg) ? 1 : 0;
            if (do_add_service(bs, s, len, ptr, txn->sender_euid, allow_isolated))
                return -1;
            break;
        // ..
    }

    bio_put_uint32(reply, 0);
    return 0;
}

bio_get_ref這個方法我們展開分析:

void *bio_get_ref(struct binder_io *bio)
{
    // 這個binder_object和flat_binder_object也是驚人的一致,C++中這種強轉(zhuǎn)的方式前面的binder_txn已經(jīng)見過了
    struct binder_object *obj;
    
    obj = _bio_get_obj(bio);
    if (!obj)
        return 0;

    if (obj->type == BINDER_TYPE_HANDLE)
        return obj->pointer;

    return 0;
}

跟進(jìn)_bio_get_obj方法:

static struct binder_object *_bio_get_obj(struct binder_io *bio)
{
    unsigned n;
    // 這里bio.data和bio.data0是同時被初始化為數(shù)據(jù)區(qū)的起始地址的,但是bio后面被讀的時候data在偏移,但是data0是不動的,
    // 因此這里data-data0不就是當(dāng)前讀的字節(jié)的偏移嗎?parcel中的數(shù)據(jù)不就是寫完了name,再寫入strongBinder的嘛,
    // 因此這里斷定off開始處,一定是那個要注冊進(jìn)來的binder對象。
    unsigned off = bio->data - bio->data0;
    /* TODO: be smarter about this? */  // 因為有寫死的嫌疑,所以源碼這里有個todo
    for (n = 0; n < bio->offs_avail; n++) {
        // 對比offs數(shù)組中是不是存在這樣一個off,存在即說明數(shù)據(jù)傳輸正常,于是把binder對象恢復(fù)出來。
        if (bio->offs[n] == off)
            return bio_get(bio, sizeof(struct binder_object));
    }

    bio->data_avail = 0;
    bio->flags |= BIO_F_OVERFLOW;
    return 0;
}

OK,這個方法返回,則正確恢復(fù)出了要注冊的binder對象,并且強轉(zhuǎn)成了和flat_binder_object結(jié)構(gòu)體類似的結(jié)構(gòu)體binder_object。
回憶一下,parcel中的binder對象在驅(qū)動傳輸過程中,做了以下賦值:

if (fp->type == BINDER_TYPE_BINDER)
   fp->type = BINDER_TYPE_HANDLE;
else
   fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc; 

可見,union中handle域被賦值為了desc域,即handle句柄,那么強轉(zhuǎn)成binder_object后,binder_object的pointer這個指針域就指向handle句柄這個整型值了。
OK,說清楚了pointer域的含義后,咱們回到svcmgr_handler方法,繼續(xù)接著看do_add_service方法:

int do_add_service(struct binder_state *bs,
                   uint16_t *s, unsigned len,
                   void *ptr, unsigned uid, int allow_isolated)
{
    // 傳進(jìn)來的s就是服務(wù)的名稱,ptr上面說了,指向handle句柄這個整型值。
    struct svcinfo *si;
    // ...
    si = find_svc(s, len);
    if (si) {
        // 原來存在,則進(jìn)行覆蓋,被覆蓋的被標(biāo)記為死亡。
        if (si->ptr) {
            svcinfo_death(bs, si);
        }
        si->ptr = ptr;
    } else {
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        // ...
        si->ptr = ptr;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        // ...
        si->next = svclist; // 可見,smgr管理service是一個鏈表
        svclist = si;
    }
    // ...
    return 0;
}

到此,就把整個addService方法的調(diào)用流程都分析完了。
總結(jié)一下:
1、client進(jìn)程拿到的IBinder對象只是一個本地的代理對象,真正起作用的是代理對象C++層的handle句柄,而handle句柄其實就是client進(jìn)程在驅(qū)動中持有的所有的binder_ref的一個序號,驅(qū)動根據(jù)序號找到ref對象,進(jìn)而找到binder_node對象。node對象中記錄了target進(jìn)程以及目標(biāo)binder實體對象的地址,因此可以確切的找到調(diào)用的目的地。
2、parcel中試圖首次傳遞binder實體對象時,驅(qū)動會為其建立binder_node對象,隨后生成一個ref引用,最后把parcel中的傳遞的binder對象類型改為引用類型,這樣目標(biāo)進(jìn)程接收到的IBinder就是本地的代理對象,一樣的,通過handle來和遠(yuǎn)程的Binder實體對象關(guān)聯(lián)。
3、binder線程就是一個普通的用戶進(jìn)程的線程,只不過在驅(qū)動中建立對應(yīng)的binder_thread結(jié)構(gòu)體而已,驅(qū)動只會把work工作項給目標(biāo)進(jìn)程的binder線程處理。對于上面分析的流程,smgr進(jìn)程是主動把主線程注冊成為binder線程的,但是對于一般的應(yīng)用進(jìn)程是怎么處理的呢?這個目前沒研究相關(guān)的代碼,不過可以來看下大佬是怎么說的:

應(yīng)用程序通過INDER_SET_MAX_THREADS告訴驅(qū)動最多可以創(chuàng)建幾個線程,每當(dāng)驅(qū)動接收完數(shù)據(jù)包返回讀Binder的線程時,都要檢查一下是不是已經(jīng)沒有閑置線程了,
如果是,而且線程總數(shù)不會超出線程池最大線程數(shù),就會在當(dāng)前讀出的數(shù)據(jù)包后面再追加一條BR_SPAWN_LOOPER消息,告訴用戶線程即將不夠用了,請再啟動一些,
新線程一啟動又會通過BC_xxx_LOOP告知驅(qū)動更新狀態(tài)。這樣只要線程沒有耗盡,總是有空閑線程在等待隊列中隨時待命,及時處理請求。

根據(jù)目前的理解,以上能成立,需要用戶進(jìn)程至少有一個線程是一直在循環(huán)讀binder的。

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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