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 的传输过程:
<center>
图2.1 writeInt 流程图 </center>
前面几步没什么好说的,直接来看第五步 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 数据读取
<center>
图2.2 Parcel 读取数据流程</center>
同进程情况下,数据读取过程跟写入几乎一致,由于使用的是同一个 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 对象序列化:
<center>
图 3.1 Binder 对象写入流程</center>
第三步中会获取 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 对象的读取
<center>
图 3.2 Binder 对象的读取流程</center>
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 跨进程通信过程做更详细的分析。
看的我热血沸腾啊
想想你的文章写的特别好https://www.ea55.com/
想想你的文章写的特别好www.jiwenlaw.com