Binder学习笔记—— Parcel是怎么打包的?

Posted 晋文

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Binder学习笔记—— Parcel是怎么打包的?相关的知识,希望对你有一定的参考价值。

前文中曾经遇到过Parcel,从命名上知道他负责数据打包。在checkService的请求/响应体系中,Parcel只打包了基本数据类型,如Int32、String16……后面还要用于打包抽象数据类型flat_binder_object,这会稍微复杂一些,因此有必要拿出来单独研究。我们从Parcel::writeInterfaceToken(…)追起,它的层层调用关系如下,这些函数都在frameworks/native/libs/binder/Parcel.cpp文件中,行数和函数名为:

582 writeInterfaceToken(…)
748 Parcel::writeInt32(int32_t val)
1149 Parcel::writeAligned(val)

所有的基本数据类型的打包最后都由writeAligned(…)实现的,其内部逻辑也非常简单,
frameworks/native/libs/binder/Parcel.cpp:1149

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

    if ((mDataPos+sizeof(val)) <= mDataCapacity) 
restart_write:
        *reinterpret_cast<T*>(mData+mDataPos) = val; // 将val追加到mData
        return finishWrite(sizeof(val));
    

    status_t err = growData(sizeof(val));  // 如果mData空间不够,则先扩容
    if (err == NO_ERROR) goto restart_write;
    return err;

mData是一块内存栈,writeXXX则把数据写入栈,如果mData空间不够,先给mData扩容,并把原先的数据搬到新的空间,再把新数据写入栈。
Parcel::writeStrongBinder(…)的逻辑更复杂一些,它的调用关系如下:
frameworks/native/libs/binder/Parcel.cpp

872 Parcel::writeStrongBinder(const sp& val)
205 Parcel::flatten_binder(const sp& /proc/, const sp& binder =val, Parcel* out=this)

来看flatten_binder(…),frameworks/native/libs/binder/Parcel.cpp:205

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)  // remote类型的binder封装逻辑
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) 
                ALOGE("null proxy");
            
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
         else        // local类型的binder封装逻辑
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);
        
     else 
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    

    return finish_flatten_binder(binder, obj, out);

它根据传入binder的类型做不同的数据封装,在frameworks/native/include/binder/IBinder.h:139,可以看到IBinder声明了两个虚函数:

class IBinder : public virtual RefBase

public:
    ……
    virtual BBinder*        localBinder();
    virtual BpBinder*       remoteBinder();
    ……
;

并在frameworks/native/libs/binder/Binder.cpp:47定义了默认实现:

BBinder* IBinder::localBinder()

    return NULL;


BpBinder* IBinder::remoteBinder()

    return NULL;

flat_binder_object这个数据结构在《Binder学习笔记(四)—— ServiceManager如何响应checkService请求》研究ServiceManager如何组织reply数据时遇到过,它定义在external/kernel-headers/original/uapi/linux/binder.h:57。对于不同的binder封装成的数据示意图如下:


然后flatten_binder(…)调用finish_flatten_binder(…),frameworks/native/libs/binder/Parcel.cpp:199

inline static status_t finish_flatten_binder(
    const sp<IBinder>& /*binder*/, const flat_binder_object& flat, Parcel* out)

    return out->writeObject(flat, false);

继续调用writeObject(…),frameworks/native/libs/binder/Parcel.cpp:1035

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:
        // 如果空间足够,他把前面组装的flat_binder_object实体追加到mData里
        *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;
        ……
        if (nullMetaData || val.binder != 0) 
      // mObjects记录每次向mData追加的flat_binder_object的偏移位置
            mObjects[mObjectsSize] = mDataPos; 
            acquire_object(ProcessState::self(), val, this, &mOpenAshmemSize);
            mObjectsSize++;
        

        return finishWrite(sizeof(flat_binder_object));
    
    ……

总结一下:Parcel的数据区域分两个部分:mData和mObjects,所有的数据不管是基础数据类型还是对象实体,全都追加到mData里,mObjects是一个偏移量数组,记录所有存放在mData中的flat_binder_object实体的偏移量。Parcel的数据模型如下:

以上是关于Binder学习笔记—— Parcel是怎么打包的?的主要内容,如果未能解决你的问题,请参考以下文章

Parcel个人笔记

Android中Parcel的分析以及使用

ContentProvider学习笔记

parcel啥意思

parcel是啥意思

Parcel上手——又一个打包工具