Binder 原理浅析 # 数据传输的集装箱 Parcel

Binder 是 Android IPC 的重要角色,在平时一般的工程中,知道 AIDL 的用法大抵都够用了,但如果要谈 Binder 的内部原理如调用效率、数据传输限制,或者 Binder 的其他用法如 pingBinder,再或者写一个自己的 ServiceManager,Binder 的深度原理就值得探究了,本系列从数据传输出发,逐步解开 Binder 的原理。

Parcel 在 Android 中扮演着跨进程传输的集装箱的角色,是数据序列化的一种手段,平时工程中获取的 Binder 对象接口就是靠它来跨进程传输的,本文来看 Parcel 对象的数据传输原理。

1. Parcel 的创建

大家对 Parcel 一定不陌生,我们平时经常用它来序列化对象(Parcelable),比如下面这段代码:

Parcel data = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1 : 0);

就是将一个接口 token(String)、String字符串、IBinder对象、一个 int 值序列化入 Parcel 中,可以看出 Parcel 不仅仅可以传输诸如String、int 这样的普通类型,而且可以传输 IBinder 对象,大部分时候我们用到的 Binder 对象都是通过 Parcel 传输到系统/服务中注册的,一句话概括 Parcel 就是提供数据存储和读取的集装箱。

每一个 Parcel 对象都有 Native 对象对应(以后均简称为 Parcel-Native 对象,而 Parcel 对象均指 java 层对象),该 Native 对象就是实际的写入读出的一个对象,java 端对它的引用是:

public final class Parcel {
    ...
    private int mNativePtr; // used by native code
    ...
}

对应 Native 层的 Parcel 定义是在 /frameworks/native/inlcude/binder/Parcel.h:

class Parcel {
public:
    ...
    int32_t             readInt32() const; // 举个例子
    ...
    status_t            writeInt32(int32_t val);
    ...
private:
    uint8_t*            mData;
    size_t              mDataSize;
    size_t              mDataCapacity;
    mutable size_t      mDataPos;
}

Parcel 对象的数据读取、写入操作都是最终通过 Parcel-Native 对象搞定的,这里有几个需要注意的重要成员,mData 就是数据存储的起始指针,mDataSize 是总数据大小,mDataCapacity 是指 mData 总空间(包括已用和可用)大小,这个空间是变长的,mDataPos 是指当前数据可写入的内存起始位置,mData 总是一块连续的内存地址,每一次其总空间大小增长都会通过 realloc 进行内存分配,如果数据量过大、内存碎片过多导致内存分配失败就会报错。

使用 Parcel 一般都是通过 Parcel.obtain() 或者 Parcel.obtain(int) 来(从对象池中)获取一个新的 Parcel 对象,前者是主动创建 Parcel-Native 对象,Parcel 对象需要主动负责它的回收,不然会造成内存泄漏,后者是用外部已经创建好的 Parcel(Native) 对象,Parcel 对象不用负责其生命周期,回收工作是在 Parcel 对象销毁 即 finalize() 方法调用时:

protected void finalize() throws Throwable {
    ...
*   destroy();
}

private void destroy() {
    if (mNativePtr != 0) {
        if (mOwnsNativeParcelObject) {
*           nativeDestroy(mNativePtr);
        }
        mNativePtr = 0;
    }
}

我们来看获取一个 Parcel 对象的方法 Parcel.obtain():

public static Parcel obtain() {
    final Parcel[] pool = sOwnedPool;
    synchronized (pool) {
        Parcel p;
        for (int i=0; i<POOL_SIZE; i++) {
            p = pool[i];
            if (p != null) {
                pool[i] = null;
                ...
                return p; // 对象池中有可用的
            }
        }
    }
    return new Parcel(0); // 对象池中没有可用的,就创建一个新的
}

sOwnedPool 算是一个 Parcel 对象池(sHolderPool 也是,二者区别在于 sOwnedPool 用于保存 Parcel-Native 对象声明周期需要自己管理的 Parcel 对象),可以用复用之前创建好的 Parcel 对象,我们在使用完 Parcel 对象后,可以通过 recycle 函数回收到对象池中:

public final void recycle() {
    if (DEBUG_RECYCLE) mStack = null;
    freeBuffer(); // Parcel-Native 对象的数据清空

    final Parcel[] pool;
    if (mOwnsNativeParcelObject) { 
        pool = sOwnedPool; // 选择合适的对象池
    } else {
        mNativePtr = 0;
        pool = sHolderPool;
    }

    synchronized (pool) {
        for (int i=0; i<POOL_SIZE; i++) {
            if (pool[i] == null) {
                pool[i] = this; // 如果有位置就加入对象池,没位置就直接放弃等死了
                return;
            }
        }
    }
}

加入对象池之后的对象除非重新靠 obtain() 启用,否则不要直接使用,因为它时刻都可能被其他地方获取使用导致数据错误。

如果对象池中已经没有了可以用的 Parcel 对象,就只能创建新的了,来看下 Parcel 的构造函数:

private Parcel(int nativePtr) {
    ...
*   init(nativePtr);
}

private void init(int nativePtr) {
    if (nativePtr != 0) {
        mNativePtr = nativePtr;
        mOwnsNativeParcelObject = false;
    } else {
*       mNativePtr = nativeCreate();
        mOwnsNativeParcelObject = true;
    }
}

这里只看要创建自己的 Parcel-Native 的情况,即 nativePtr==0,通过 jni 方法 nativeCreate 来创建 Parcel-Native 对象:

static jint android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    Parcel* parcel = new Parcel();
    return reinterpret_cast<jint>(parcel);
}

可以发现,mNativePtr 实际上是 Parcel-Native 对象的地址了,有了这个地址我们可以随时取得该 Parcel-Native 对象引用:

Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);

2. 简单数据传输

2.1 数据写入

先来看简单的 writeInt 与 readInt 来说明 Parcel 的传输过程:

Parcel.jpg

图2.1 writeInt 流程图


前面几步没什么好说的,直接来看第五步 writeAligned:

template<class T>
status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));

    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
2.      *reinterpret_cast<T*>(mData+mDataPos) = val;
3.      return finishWrite(sizeof(val));
    }

1.  status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

步骤1:在这里 T 就是 int32_t,我们先来看 Parcel-Native 对象刚刚创建了的情况,这时候 mDataCapacity=0,mDataPos=0,mData=0,mDataSizemDataSize=0,于是本步先走 growData 函数分配内存:

status_t Parcel::growData(size_t len)
{
    size_t newSize = ((mDataSize+len)*3)/2;
    return (newSize <= mDataSize) // newSize 一般来说都会大于 mDataSize
            ? (status_t) NO_MEMORY
*           : continueWrite(newSize);
}

growData 函数又调用了 continueWrite,通过 malloc 进行内存分配,为mData、mDataSize、mDataPos、mDataCapacity 初始化:

status_t Parcel::continueWrite(size_t desired)
{
    ......
    if (mOwner) {
        ......
    } else if (mData) {
        ......
    } else {
        // This is the first data.  Easy!
*       uint8_t* data = (uint8_t*)malloc(desired);
        if (!data) {
            mError = NO_MEMORY;
            return NO_MEMORY;
        }
        ...
        mData = data;
        mDataSize = mDataPos = 0;
        ...
*       mDataCapacity = desired;
    }

    return NO_ERROR;
}

步骤2:将数据 val 写入正确的位置;
步骤3:调用 finishWrite 根据写入数据的大小调整 mDataPos 和 mDataSize:

status_t Parcel::finishWrite(size_t len)
{
    //printf("Finish write of %d\n", len);
*   mDataPos += len;
    ALOGV("finishWrite Setting data pos of %p to %d\n", this, mDataPos);
    if (mDataPos > mDataSize) {
*       mDataSize = mDataPos;
        ALOGV("finishWrite Setting data size of %p to %d\n", this, mDataSize);
    }
    //printf("New pos=%d, size=%d\n", mDataPos, mDataSize);
    return NO_ERROR;
}

再来看 Parcel-Native 对象已经创建过了的,并且空间不足的情况,这时 continueWrite 走的是这个分支:

status_t Parcel::continueWrite(size_t desired)
{
    ......
    if (mOwner) {
        ......
    } else if (mData) {
        ......
        // We own the data, so we can just do a realloc().
        if (desired > mDataCapacity) {
*           uint8_t* data = (uint8_t*)realloc(mData, desired);
            if (data) {
                mData = data;
                mDataCapacity = desired;
            } else if (desired > mDataCapacity) {
                mError = NO_MEMORY;
                return NO_MEMORY;
            }
        } else {
            ......
        }

    } else {
        ......
    }

    return NO_ERROR;
}

看带*那行代码,即靠 realloc 将 mData 的空间扩展至 desired 大小。

2.1 数据读取

parcel_read.jpg

图2.2 Parcel 读取数据流程

同进程情况下,数据读取过程跟写入几乎一致,由于使用的是同一个 Parcel 对象,mDataPos 首先要调整到 0 之后才能用于读取,同进程下数据写入/读取并不会有什么效率提高,仍然会进行内存的拷贝和分配,所以一般来说尽量避免同进程使用 Parcel 传输大数据。

跨进程传输数据时,Parcel-Native 对象一定会有一个从 Binder 中读取原始数据进行初始化的过程,这个过程就在 IPCThreadState.cpp 中:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;

    switch (cmd) {
    ......

    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            ...
*           Parcel buffer;
            buffer.ipcSetDataReference(
                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(size_t), freeBuffer, this);

            ......
        }
        break;

    ......
    }

    if (result != NO_ERROR) {
        mLastError = result;
    }

    return result;
}

本文是Binder 系列的开场,只说 Parcel 不说 Binder 是因为数据传输本就是 Binder 的一个重要部分,在这里不会细说 executeCommand 或是 BR_TRANSACTION 的问题,executeCommand 就是在接收到 Binder 某一个指令后的执行函数,BR_TRANSACTION 就是数据传输执行指令,mIn 就是离 Binder 驱动最近的数据读取的 Parcel-Native 对象,它读出来的就是 Binder 驱动传过来的原始数据,即结构体 binder_transaction_data:

struct binder_transaction_data {

    union {
        size_t handle;
        void *ptr;
    } target; // 内核 binder 句柄
    void *cookie;
    unsigned int code;

    unsigned int flags;
    pid_t sender_pid;
    uid_t sender_euid;
    size_t data_size;
    size_t offsets_size; // 下面的 offsets 数组的大小

    union {
        struct {

            const void *buffer; // 数据所在buffer

            const void *offsets; // binder 描述对象(flat_binder_object)在 buffer 中的 offset 数组
        } ptr;
        uint8_t buf[8];
    } data;
};

可以用 offsets_size/sizeof(size_t) 来表示 Binder 描述对象的个数,可以用 buffer+((size_t*)offsets)[i] 表示某binder描述对象(flat_binder_object)的起始地址。

然后构造的 "Parcel buffer;" 才是真正传给上层的 Parcel,调用 ipcSetDataReference() 来初始化数据,并指明释放 buffer 的手段:

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
    const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
{
    size_t minOffset = 0;
1.  freeDataNoInit();
    mError = NO_ERROR;
2.  mData = const_cast<uint8_t*>(data);
    mDataSize = mDataCapacity = dataSize;
    ...
    mDataPos = 0;
    ...
    mObjects = const_cast<size_t*>(objects);
    mObjectsSize = mObjectsCapacity = objectsCount;
    mNextObjectHint = 0;
    mOwner = relFunc;
    mOwnerCookie = relCookie;
3.  for (size_t i = 0; i < mObjectsSize; i++) {
        size_t offset = mObjects[i];
        if (offset < minOffset) {
            ...
            mObjectsSize = 0;
            break;
        }
        minOffset = offset + sizeof(flat_binder_object);
    }
4.  scanForFds();
}

步骤1:释放 mData、mObjects 数组;
步骤2:各字段数据赋值;
步骤3:确保 mObjects 里每一个对象的完整性(只是检验大小而已);
步骤4:检查是否有包含文件句柄;

3. Binder 对象的传输

3.1 Binder 对象的写入

前面说完了基本的数据传输流程,心里有一个轮廓后,再来看 Binder 对象的传输。首先需要对 Binder 有一个概念,就是每一个 java 端的 Binder 对象(服务端)在初始化时都会对应一个 native 对象,类型是 BBinder,它继承于 IBinder 类。

通过 Parcel 的 writeStrongBinder 方法将 Binder 对象序列化:

binder_write_parcel_4.jpg

图 3.1 Binder 对象写入流程

第三步中会获取 Binder 在 Native 端的对象:

static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr, jobject object)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
*       const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

ibinderForJavaObject() 返回的就是 BBinder 对象(实际上是 JavaBBinder,它继承于 BBinder);
第四步,执行 Parcel-Native 的 writeStrongBinder 方法:

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

第五步,执行 flatten_binder 方法:

status_t flatten_binder(const sp<ProcessState>& proc,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;
    
    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            ......
        } else {
*           obj.type = BINDER_TYPE_BINDER;
            obj.binder = local->getWeakRefs();
            obj.cookie = local;
        }
    } else {
        ......
    }
    
*   return finish_flatten_binder(binder, obj, out);
}

第六步,执行 finish_flatten_binder 方法:

inline static status_t finish_flatten_binder(
    const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out)
{
*   return out->writeObject(flat, false);
}

“flat_binder_object flat;” 就是 Binder 描述对象,writeObject 就是将 flat 写到 mObjects 中并更新数据大小和写入位置(mDataSize、mObjectsSize、mObjectsCapacity、mDataPos):
第七步,执行 writeObject 方法:

status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
    const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
    const bool enoughObjects = mObjectsSize < mObjectsCapacity;
    if (enoughData && enoughObjects) {
restart_write:
3.      *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;
        
        // Need to write meta-data?
        if (nullMetaData || val.binder != NULL) {
            mObjects[mObjectsSize] = mDataPos;
            acquire_object(ProcessState::self(), val, this);
            mObjectsSize++;
        }
        ......
4.      return finishWrite(sizeof(flat_binder_object));
    }

    if (!enoughData) {
1.      const status_t err = growData(sizeof(val));
        if (err != NO_ERROR) return err;
    }
    if (!enoughObjects) {
        size_t newSize = ((mObjectsSize+2)*3)/2;
2.      size_t* objects = (size_t*)realloc(mObjects, newSize*sizeof(size_t));
        if (objects == NULL) return NO_MEMORY;
        mObjects = objects;
        mObjectsCapacity = newSize;
    }
    
    goto restart_write;
}

步骤1:如果 mData 空间不足,扩展空间,第二节我们已经说过了;
步骤2:mObjects 空间不足,扩展空间,跟 mData 空间扩展基本相似;
步骤3:mObjects 数据写入;
步骤4:调整 mDataPos 和 mDataSize,第二节我们已经说过了;

3.2 Binder 对象的读取

binder_read_parcel.jpg

图 3.2 Binder 对象的读取流程


Binder 对象的读取流程基本与写入流程对应,需要注意的是:
第三步,

static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
*       return javaObjectForIBinder(env, parcel->readStrongBinder());
    }
    return NULL;
}

先执行 Parcel::readStrongBinder() 获取 Binder 代理对象,即第四步:

sp<IBinder> Parcel::readStrongBinder() const
{
    sp<IBinder> val;
*   unflatten_binder(ProcessState::self(), *this, &val);
    return val;
}

sp<IBinder> val 就是 Binder 代理对象,第五步,执行 unflatten_binder 函数:

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);
    
    if (flat) {
        switch (flat->type) {
            ......
            case BINDER_TYPE_HANDLE:
*               *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }        
    }
    return BAD_TYPE;
}

客户端一方从 Binder 驱动中获取的数据 flat_binder_object 的 type 域被赋值为 BINDER_TYPE_HANDLE(服务端是 BINDER_TYPE_BINDER),先通过 ProcessState::getStrongProxyForHandle 获取/新建 BpBinder 对象,BpBinder 与服务端对象 BBinder 对应,是客户端代理对象了,

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);

    handle_entry* e = lookupHandleLocked(handle);

    if (e != NULL) {
        ...
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            ......
*           b = new BpBinder(handle); 
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            ......
        }
    }

    return result;
}

然后第六步执行 finish_unflatten_binder 函数,该函数没有任何逻辑直接返回 NO_ERROR。返回结果(BpBinder)给 android_os_Parcel.cpp 的 android_os_Parcel_readStrongBinder 方法,第八步执行 javaObjectForIBinder:

jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
    ......
*   object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
    if (object != NULL) {
        ......//各种给 object 的成员赋值
    }

    return object;
}

构造 java 对象并进行各种成员赋值,对象类型是 gBinderProxyOffsets:

static struct binderproxy_offsets_t
{
    // Class state.
    jclass mClass;
    jmethodID mConstructor;
    jmethodID mSendDeathNotice;

    // Object state.
    jfieldID mObject;
    jfieldID mSelf;
    jfieldID mOrgue;

} gBinderProxyOffsets;

gBinderProxyOffsets 的初始化是在虚拟机启动的时候(在 AndroidRuntime::start),最终赋值是在android_util.Binder.cpp 中的函数 int_register_android_os_BinderProxy():

const char* const kBinderProxyPathName = "android/os/BinderProxy";

static int int_register_android_os_BinderProxy(JNIEnv* env)
{
    ......
    clazz = env->FindClass(kBinderProxyPathName);
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.BinderProxy");

    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.Error");
    gBinderProxyOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gBinderProxyOffsets.mConstructor
        = env->GetMethodID(clazz, "<init>", "()V");
    ......
}

可以看出这个类型是 android.os.BinderProxy,也就是说代理端 java 层的对象是 android.os.BinderProxy。

4. 总结

本文分析了跨进程数据传输的集装箱 Parcel 的数据传输,并对 Binder 对象的传输作了流程上的总结,Parcel 是跨进程传输的基础,分析完 Parcel 接下来的文章中将会对 Binder 跨进程通信过程做更详细的分析。

标签: none

添加新评论