前面 binder 原理和通信模型中在接口实现部分(Bp 和 Bn)中应该看到很多地方都有使用 parcel。这个 android 专门设计用来跨进程传递数据的,实现在 native,java 层有接口(基本上是 jni 马甲)。照例先说下源代码位置(4.4 的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# java parcel (MemoryFile 是封装好的匿名共享内存的接口)
frameworks/base/core/java/os/Parcel.java
frameworks/base/core/java/os/Parcelable.java
frameworks/base/core/java/os/ParcelFileDescriptor.java
frameworks/base/core/java/os/MemoryFile.java
# parcel jni 相关
frameworks/base/core/jni/android_os_Parcel.h
frameworks/base/core/jni/android_os_MemoryFile.h
frameworks/base/core/jni/android_os_Parcel.cpp
# native parcel 实现(Memory 相关的是封装好的匿名共享内存实现)
frameworks/native/include/binder/Parcel.h
frameworks/native/include/binder/IMemory.h
frameworks/native/include/binder/MemoryHeapBase.h
frameworks/native/include/binder/MemoryBase.h
frameworks/native/libs/binder/Parcel.cpp
frameworks/native/libs/binder/Memory.cpp
frameworks/native/libs/binder/MemoryHeapBase.cpp
frameworks/native/libs/binder/MemoryBase.cpp
# kernel binder 驱动
kernel/drivers/staging/android/binder.h
kernel/drivers/staging/android/binder.c
# kernel ahsmem 驱动
kernel/include/linux/ashmem.h
kernel/mm/ashmem.c

原理

在 java 层 parcel 有个接口叫 Parcelable,和 java 的 Serializable 很像,刚开始我还没搞明白这2个有什么区别(以前对 java 也不太熟)。这里简单说一下, Serializable 是 java 的接口,翻译过来是序列化的意思,就是通过实现这个接口能够让 java 的对象序列化能够永久保存在的存储介质上,然后反序列化就能从存储介质上实列化出 java 对象(通俗点,就是一个 save/load 的功能)。因为保存到了存储介质上,所以是可以跨进程的(一个进程把数据写入文件,另外一个去读)。但是为什么 android 还要搞一个 parcel 出来,是因为 java 的 Serializable 是通过存储介质的,所以速度慢。parcel 是基于内存传递的,比磁盘I/O要块,而且更加轻量级(这个我是从网上看到的,我没研究过 java 的 Serializable 代码)。

parcel 在内存中的结构是一块连续的内存,会动根据需要自动扩展大小(这个设计比较赞,一些对性能要求不是太高、或是小数据的地方,可以不用废脑想分配多大空间)。parcel 传递数据,可以分为3种,传递方式也不一样:

  • 小型数据: 从用户空间(源进程)copy 到 kernel 空间(binder 驱动中)再写回用户空间(目标进程,binder 驱动负责寻找目标进程)。
  • 大型数据: 使用 android 的匿名共享内存(Ashmem)传递
  • binder 对象: kernel binder 驱动专门处理

下面逐一分析。这里我打算从 natvie 到 kernel 再到 java 的顺序进行,因为接着前面通信原型那里,所以从 natvie 开始会比较好,而且实现的地方也在 native。

小型数据

先来看看 Parcel.h 中几个比较关键的几个变量:

1
2
3
4
5
6
7
8
9
uint8_t* mData;
size_t mDataSize;
size_t mDataCapacity;
mutable size_t mDataPos;
size_t* mObjects;
size_t mObjectsSize;
size_t mObjectsCapacity;
mutable size_t mNextObjectHint;
  • mData: 数据指针,也是数据在本进程空间内的内存地址
  • mDataSize: 存储的数据大小(使用的空间大小)
  • mDataCapacity: 数据空间大小,如果不够的话,可以动态增长
  • mDataPos: 数据游标,当前数据的位置,和读文件的游标类似,可以手动设置。声明了 mutbale 属性,可以学习下这个属性应该用在声明地方 ^_^。
  • mObjects: flat_binder_object 对象的位置数据,注意这个是个指针(其实就是个数组),里面保存的不是数据,而且地址的偏移(后面再具体说)。
  • mObjectsSize: 这个简单来说其实就是上面那个 objects 数组的大小。
  • mObjectsCapacity: objects 偏移地址(再次强调一次是地址)的空间大小,同样可以动态增长
  • mNextObjectHint: 可以理解为 objects 的 dataPos 。

还记得通信模型 IPCThreadState 中有2个 Parcel 变量: mIn、mOut,前面分析这2个东西是 binder 通信的时候打包数据用的。我们通过结合前面的例子来分析。

首先是初始化,IPCThreadState 是直接使用变量的(栈内存),使用默认构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Parcel::Parcel()
{
initState();
}
void Parcel::initState()
{
mError = NO_ERROR;
mData = 0;
mDataSize = 0;
mDataCapacity = 0;
mDataPos = 0;
ALOGV("initState Setting data size of %p to %d\n", this, mDataSize);
ALOGV("initState Setting data pos of %p to %d\n", this, mDataPos);
mObjects = NULL;
mObjectsSize = 0;
mObjectsCapacity = 0;
mNextObjectHint = 0;
mHasFds = false;
mFdsKnown = true;
mAllowFds = true;
mOwner = NULL;
}

初始话很简单,几乎都是初始化为 0(NULL) 的。然后看看 IPCThreadState 使用的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()),
mMyThreadId(androidGetTid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
pthread_setspecific(gTLS, this);
clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}

首先调用 setDataCapacity 来初始化 parcel 的数据空间大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
status_t Parcel::setDataCapacity(size_t size)
{
if (size > mDataCapacity) return continueWrite(size);
return NO_ERROR;
}
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;
}
if(!(mDataCapacity == 0 && mObjects == NULL
&& mObjectsCapacity == 0)) {
ALOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired);
}
mData = data;
mDataSize = mDataPos = 0;
ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos);
mDataCapacity = desired;
}
return NO_ERROR;
}

设置空间大小的,其实主要是调用到了 contiueWrite 函数。前面那个 size > mDataCapacity 判断意思是如果设置的大小比原来的要大,则需要调整申请的内存的大小,如果小的话,就直接使用原来的大小。

接下来看 contiueWrite,前面有个 object 的判断先不管。然后下面主要是分开3个分支,分别是:

  • 分支一: 如果设置了 release 函数指针(mOwner是个函数指针),调用 release 函数进行处理。
  • 分支二: 没有设置 release 函数指针,但是 mData 中存在数据,需要在原来的数据的基础上扩展存储空间。
  • 分支三: 没有设置 release 函数指针,并且 mData 中不存在数据(就是注释中说的第一次使用, Easy -_-||),调用 malloc 申请内存块,保存在 mData。设置相应的设置 capacity、size、pos、object 的值。

这里先贴出分支三的代码,第一次使用,是走分支三的,其它2个后面再说。这里注意一点,这里只 malloc 了一个块内存,就是 mData 的,前面说 parcel 存储结构是一块连续的内存,mObjects 只是保存的只是地址的偏移,这里可以看到一些端倪(后面就能清楚)。

初始化了之后,我们看看怎么使用的,在通信模型中我们说道 Bp 端发起 IPC 调用,通过 IPCThreadState 对 binder 驱动写入请求数据发送到 Bn 端,我们回想下 Bp 端写数据的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
... ...
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}

先看前面 writeInt32 这个,对 parcel 写入一个 32bit 的 int 型数据。parcel 接口中有一类是专门针对基本类型(int、float、double、int数组),writeInt32、writeInt64、writeFloat、writeDouble、writeIntArray 这些(对应有 read 接口)。然后他们都是另一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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:
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
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;
}

writeAligned 看名字就知道要内存对齐,第一句好像就是验证下是否内存对齐的,好像能够根据编译选项判断,应该是如果打开某个编译选项,如果传过来的 size 没内存对齐直接报错吧,内存对齐的算法都是搞一些位运算(这里好像是4字节对齐吧):

#define PAD_SIZE(s) (((s)+3)&~3)

int32、int64、float、double 都是4字节对齐的。接着往下看,有个判断当前 pos + 要写入的数据的所占用的空间是否比 capacity 大,就是看空间是不是够大。前面所了 parcel 能够根据需求自动增长空间,这里我们先看空间够的情况,就是走 if 里面:

*reinterpret_cast(mData+mDataPos) = val;

直接取当前地址强制转化指针类型,然后赋值(c/c++语言就是舒服)。然后调用 finishWrite 完成写入。finishWrite 就是把 mDataPos 和 mDataSize 值改了一下(加上刚刚写入数据的大小),从这里可以看得出,对于 write 来说,mDataPos = mDataSize。

然后我们看看当空间不够的情况,就是走 if 后面,有一个 growData 的函数,这个是用来调整内存空间的,然后一个 goto 跳转回 if 里面重写写入(parcel 的实现很多地方有 goto,其实 goto 在本函数里面用还好)。我们来看看 growData:

1
2
3
4
5
6
7
8
status_t Parcel::growData(size_t len)
{
size_t newSize = ((mDataSize+len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}

这里 parcel 的增长算法: ((mDataSize+len)*3)/2, 带一定预测性的增长,避免频繁的空间调整(每次调整需要重新 malloc 内存的,频繁的话会影响效率的)。然后这里有个判断 newSize < mDataSize 就认为 NO_MEMORY。这是所如果如果溢出了(是负数),就认为申请不到内存了。然后调用的函数是 continueWrite ,和前面 setCapacity 调用的是同一个。前面说这个函数有3个分支,这里我们就可以来看第2个分支了(mData 有数据的情况):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
status_t Parcel::continueWrite(size_t desired)
{
// If shrinking, first adjust for any objects that appear
// after the new data size.
size_t objectsSize = mObjectsSize;
if (desired < mDataSize) {
if (desired == 0) {
objectsSize = 0;
} else {
while (objectsSize > 0) {
if (mObjects[objectsSize-1] < desired)
break;
objectsSize--;
}
}
}
if (mOwner) {
... ...
} else if (mData) {
if (objectsSize < mObjectsSize) {
// Need to release refs on any objects we are dropping.
const sp<ProcessState> proc(ProcessState::self());
for (size_t i=objectsSize; i<mObjectsSize; i++) {
const flat_binder_object* flat
= reinterpret_cast<flat_binder_object*>(mData+mObjects[i]);
if (flat->type == BINDER_TYPE_FD) {
// will need to rescan because we may have lopped off the only FDs
mFdsKnown = false;
}
release_object(proc, *flat, this);
}
size_t* objects =
(size_t*)realloc(mObjects, objectsSize*sizeof(size_t));
if (objects) {
mObjects = objects;
}
mObjectsSize = objectsSize;
mNextObjectHint = 0;
}
// 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 {
if (mDataSize > desired) {
mDataSize = desired;
ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
}
if (mDataPos > desired) {
mDataPos = desired;
ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos);
}
}
} else {
... ...
}
return NO_ERROR;
}

这里有关 object 的处理也是先放放,后面再一起说。看后面的,如果需要的空间比原来的大,那么调用 realloc 把空间调整一下。realloc 可以看 man,是说保留原来的内存空间,然后尝试在原来的空间后面扩展需要的内存空间。然后就是把 mDataPos 和 mDataSize 设置一下。如果是要的空间比原来的小,那就什么都不干,就是说用就当成小的用,内存还是以前那么大。

然后回到 IPCThreadState::writeTransactionData 我们看看后面那个 mOut.write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
status_t Parcel::write(const void* data, size_t len)
{
void* const d = writeInplace(len);
if (d) {
memcpy(d, data, len);
return NO_ERROR;
}
return mError;
}
void* Parcel::writeInplace(size_t len)
{
// 4字节对齐,看样子字节对齐对效率还是有影响的
const size_t padded = PAD_SIZE(len);
// sanity check for integer overflow
if (mDataPos+padded < mDataPos) {
return NULL;
}
if ((mDataPos+padded) <= mDataCapacity) {
restart_write:
//printf("Writing %ld bytes, padded to %ld\n", len, padded);
uint8_t* const data = mData+mDataPos;
// Need to pad at end?
if (padded != len) {
#if BYTE_ORDER == BIG_ENDIAN
static const uint32_t mask[4] = {
0x00000000, 0xffffff00, 0xffff0000, 0xff000000
};
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
static const uint32_t mask[4] = {
0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff
};
#endif
//printf("Applying pad mask: %p to %p\n", (void*)mask[padded-len],
// *reinterpret_cast<void**>(data+padded-4));
*reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];
}
finishWrite(padded);
return data;
}
status_t err = growData(padded);
if (err == NO_ERROR) goto restart_write;
return NULL;
}

这个函数首先调用 writeInplace。来看下 writeInplace,这个函数参数是一个大小,返回是一个地址。进去里面看下,除去字节对齐的部分,就是把 mDataPos 和 mDataSize 的值加上了传过去的 len 大小,然后返回 mData + len 的地址。注意这里也有空间不够的情况,和前面的处理一样,调用 growData 去调整空间(parcel 写的接口基本上都有这个 growData 的处理)。这个函数相当于是帮你把内存分配好,然后返回计算好的起始地址给你。然后回到 write 下面直接 memcpy,把传过来的地址中的数据复制过来。

这2个接口的示例很经典,一个是写基本类型,一个是写对象类型的。基本类型可以说是值类型,直接把值写入内存中;对象类型,是把对象的内存数据写进来,这个相当于 c++ 里面的深拷贝,复制数据。上面 IPCThreadState 写入的是 binder_transaction_data 这个结构体,后面具体说说 binder 通信之间的数据格式。现在再来看看 writeString8 这个接口,加深下理解:

1
2
3
4
5
6
7
8
9
10
11
12
status_t Parcel::writeString8(const String8& str)
{
status_t err = writeInt32(str.bytes());
// only write string if its length is more than zero characters,
// as readString8 will only read if the length field is non-zero.
// this is slightly different from how writeString16 works.
if (str.bytes() > 0 && err == NO_ERROR) {
err = write(str.string(), str.bytes()+1);
}
return err;
}

String8 是 android 在 native 对 char* 封装了一下,有点像 java 的 String方便字符串操作的。这个也是对象类型,看到 parcel 先是把 String8 的 size 写进去,然后 write 把 char* 数据写在 size 后面的内存中。我们再来看看对应的 readString8:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
String8 Parcel::readString8() const
{
int32_t size = readInt32();
// watch for potential int overflow adding 1 for trailing NUL
if (size > 0 && size < INT32_MAX) {
const char* str = (const char*)readInplace(size+1);
if (str) return String8(str, size);
}
return String8();
}
status_t Parcel::readInt32(int32_t *pArg) const
{
return readAligned(pArg);
}
template<class T>
status_t Parcel::readAligned(T *pArg) const {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(T)) <= mDataSize) {
const void* data = mData+mDataPos;
mDataPos += sizeof(T);
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
}
const void* Parcel::readInplace(size_t len) const
{
if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) {
const void* data = mData+mDataPos;
mDataPos += PAD_SIZE(len);
ALOGV("readInplace Setting data pos of %p to %d\n", this, mDataPos);
return data;
}
return NULL;
}

读是先 readInt32 把 write 写入的 size 取出来,readInt32 是调用 readAligned 的。和前面 writeAligned 对应,先是判断下字节对齐,然后直接取 mData + mDataPos 地址的数据,转化成模版类型(再次感叹一次 c、c++ 爽)。注意一下这个函数最后移动了 mDataPos 的位置(对应 read 的数据的大小)。

然后是调用 readInplace ,有了前面的说明,你也应该知道这个是去返回对应的 writeInplace 的地址。里面果然是,同样注意最后移动了 mDataPos 的位置。然后去返回的地址取之前写入的 char* 数据,基于上面的数据重新构造出新的 String8 对象。这里你看出 pacrel 和 Serializable 很像,只不过 parcel 是在内存中捣腾,还有后面你会发现 parcel 还为 binder 做了一些别的事情。

还有前面说的 read 的接口回自动移动 mDataPos 的位置(parcel 所有 read 的接口都会自动移动 mDataPos),然后看前面的代码,你会发现,write 之后,到 read 的时候,能否取得到正确的数据,依赖于 mDataPos 的位置。这里就要求 binder 通信的时候,双方在用 parcel 读写数据的时候顺序一定要一致。例如说一个 IPC 调用,传递一个函数的参数: int、float、object,用 parcel 写顺序是: writeInt32、writeFloat、write,那么对方接到传过来的 parcel read 的顺序也必须为: readInt32、readFloat、read。就算其中某些参数你不用,你也要 read 一下,主要是要把 mDataPos 的位置移动对。

这里可以看得出 parcel 只是提供的是一块连续的内存块,至于往里面写什么东西,格式是怎么样的,取决于使用的人,所以使用人要要保证自己读得正确(要和写对应),例如前面说的 String8,前面一个 int32 是大小,后面这个大小的是 char* 数据,这个读的人必须按这个格式才重新创建出 String8。这个我们后面看 binder 中的使用能够看得出来。

接下来我们看看 binder 怎么把 parcel 打包的数据传递给另外一个进程的。这里我们结合下通信模型那分析的东西。首先 Bp 端调用 writeTransationData 把 IPC 请求打包发送到 binder 驱动。前面看到打包的是一个 cmd 和一个 binder_transaction_data 结构。先来看看这个结构(在 kernel binder.h 中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct binder_transaction_data {
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
* identifying the target and contents of the transaction.
*/
union {
size_t handle; /* target descriptor of command transaction */
void *ptr; /* target descriptor of return transaction */
} target;
void *cookie; /* target object cookie */
unsigned int code; /* transaction command */
/* General information about the transaction. */
unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size; /* number of bytes of data */
size_t offsets_size; /* number of bytes of offsets */
/* If this transaction is inline, the data immediately
* follows here; otherwise, it ends with a pointer to
* the data buffer.
*/
union {
struct {
/* transaction data */
const void *buffer;
/* offsets from buffer to flat_binder_object structs */
const void *offsets;
} ptr;
uint8_t buf[8];
} data;
};

然后我们看看 Bp 端对这个结构体填充了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.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(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 = statusBuffer;
tr.offsets_size = 0;
tr.data.ptr.offsets = NULL;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}

binder_transatcion_data target 这个 union 先看注释的说明, target.handle 是说是 IPC 目标的标示,这个 handle 这个东西后面再细说。code 是 IPC 接口定义的接口的标示(例如 START_ACTIVITY, GET_TASK 之类的玩意)。然后是检查下 parcel 的错误状态,一般是没啥错误的。然后后面几个赋值,来看看从 parcel 取出的是什么东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const uint8_t* Parcel::ipcData() const
{
return mData;
}
size_t Parcel::ipcDataSize() const
{
return (mDataSize > mDataPos ? mDataSize : mDataPos);
}
const size_t* Parcel::ipcObjects() const
{
return mObjects;
}
size_t Parcel::ipcObjectsCount() const
{
return mObjectsSize;
}

看注释 binder_transatcion_data 的 data 这个变量是个 union,远程传输的时候用的是 ptr 这个结构,里面保存的是数据的地址。ptr.buffer 是 parcel 的 ipcData() ,这个函数返回的是 mData 就是数据地址。注意一下这里 data.ptr 保存的是 IPC Bp 传入的那个 parcel,不是 IPCThreadState mOut 这个用来打包 binder 数据的 parcel(都是用 parcel 容易搞混)。这里 data 这个 parcel 是将 IPC 的接口函数的参数数据打包起来的,例如 int、string 之类的参数。Bn 端返回的数据也是通过 parcel 打包的。而 IPCThreadState 的 mOut 只是写入了 cmd 和 binder_transatcion_data 而已,而 binder_transation_data 保存了 IPC 中传递的真正数据的地址(从参数 parcel 或取的),仅仅是地址而已。所以开头为什么 mOut 和 mIn 只把空间大小设置为 256,刚开始以为是因为 parcel 可以动态增长空间,先在看来,其实根本用不了到 256,因为数据大小只有一个 int32 的 cmd 和 binder_transation_data 这个结构而已。算一下 int32 4字节,binder_transation_data 第一个 target union 2个都是4字节的地址,所以就是4字节,除去后面 data 的 union 其余的7个都是4字节的地址,后面那个 data union 算最大的数据,是 4字节x2,所以 binder_transation_data 结构占 40字节,加上 cmd 就是 44字节。回去通信模型那看看图,是不是 Bp 端发 BC_TRANSACTION write_size 是不是 44。mIn 从 Bn 那读回来的数据也是差不多的,所以 256 足够了,基本上不需要动态调整空间的。

好,回到赋值那,看看后面几个,data_size 是取 mDataSize, mDataPos 比较大的那个(估计是为了保险吧,对于写 mDataSize 应该等于 mDataPos),然后看看后面的把 parcel 的 mObjectsSize 和 mObjects 分别给了 offset_size 和 ptr.offsets,offset_size 还乘了个地址的大小。前面说过了 parcel 的 mObjects 保存的是偏移地址,parcel 的名字很奇怪,kernel 里面的数据结构用名字再次告诉了我们这个是偏移地址。这个到后面就清楚了。

好 mOut 把数据打包好了,到了 waitForResponse 循环调用 talkWithDriver 向 binder 驱动写数据,以及等待 Bn 端返回数据(忘记了的回通信模型看看流程图)。我们先来看第一次写通信命令写了什么东西进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD <= 0) {
return -EBADF;
}
binder_write_read bwr;
// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (long unsigned int)mOut.data();
// This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (long unsigned int)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
... ...
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
... ...
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
... ...
} while (err == -EINTR);
... ...
return err;
}

我们在 kernel 的 binder.h 看到 BINDER_WRITE_READ 的参数是 binder_write_read 这个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
/*
* On 64-bit platforms where user code may run in 32-bits the driver must
* translate the buffer (and local binder) addresses apropriately.
*/
struct binder_write_read {
signed long write_size; /* bytes to write */
signed long write_consumed; /* bytes consumed by driver */
unsigned long write_buffer;
signed long read_size; /* bytes to read */
signed long read_consumed; /* bytes consumed by driver */
unsigned long read_buffer;
};

binder_write_read 的结构并不复杂,就是一个数据地址,一个数据大小,一个数据确认处理的大小,分为2部分,write 和 read(看注释后面要支持 64bit binder 数据传输这里要改不少东西吧)。回来看下赋值。前面那个那个判断 mIn 中的是否有读的数据,是通过 mDataPos 的位置来判断的,就是说如果 mDataPos 的位置比 mDataSize 小,说明还有数据还没读完,前面说了 parcel 每调用一次 read 接口就会自动移动 mDataPos,如果正好把 read 次数(Bp 端读)对应上 write 次数(Bn 端写),那么 mDataPos 是正好等于 mDataSize 的。后面根据 (!doReceive || needRead) 决定 write_size 的大小,这个后面到 kernel 里可以知道 size 的大小是否为 0 决定了是否调用 binder 驱动的读写处理函数。如果 mIn 中还有数据还没读取完,needRead 为 true, doReceive 默认是 true(默认要接收 Bn 端返回的数据),所以如果还有 Bn 端发过来的数据还没读完,本次循环在 binder 驱动中是发不出数据的。这里开始是能没有读数据的,所以能发得出来,write_size 大小是 mOut parcel 的 mDataSize,write_buffer 是 mOut 的 mData 地址。读的部分相应的取 mIn 的,这里给接收的大小也是 256,后面可以看到 Bn 端发过来也是也是 binder_transatcion_data 结构,所以 256 也够了。然后在 ioctl 前把 consumed 都设置成0。

然后就 ioctl 到 kernel 的 binder 驱动里面去了,我在 binder 驱动中看看,parcel 是怎么从 Bp 端传递到 Bn 端(或者从 Bn 返回到 Bp)的。首先是上面的 Bp 向 binder 发送 BC_TRANSACTIONbinder_transtion_data 的地址保存到 ioctl 的参数 binder_write_read 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
... ...
switch (cmd) {
case BINDER_WRITE_READ: {
struct binder_write_read bwr;
if (size != sizeof(struct binder_write_read)) {
ret = -EINVAL;
goto err;
}
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
... ...
if (bwr.write_size > 0) {
ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
if (ret < 0) {
bwr.read_consumed = 0;
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
goto err;
}
}
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;
}
}
... ...
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
ret = -EFAULT;
goto err;
}
break;
}
... ...
default:
ret = -EINVAL;
goto err;
}
... ...
}

这里有个 kernel 函数调用: copy_from_user,是从用户空间 copy 指定的一段内存数据到 kernel 空间(用户态(空间),kernel态(空间)有啥区别网上查吧,我也不是很清除),这样 IPCThreadState talkWithDriver 那些填写的那个 binder_write_read 就传递到 kernel binder 驱动中了。这里可以看得到,如果 IPCThreadState 把 wirte 或是 read 的 size 设置为 0 的话就不会处理(前面也说过)。我们先看 write, write_buffer 里面的数据是 IPCThreadState 用 mOut 打包的内存数据块。根据前面的分析,应该是这样的格式:

然后去 binder_thread_write 里面去看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
while (ptr < end && thread->return_error == BR_OK) {
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
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;
}
... ...
default:
printk(KERN_ERR "binder: %d:%d unknown command %d\n",
proc->pid, thread->pid, cmd);
return -EINVAL;
}
*consumed = ptr - buffer;
}
return 0;
}

这里有个 while 循环,结束条件是 ptr 指针移动 end 处,就是处理完 IPCThreadState write 进来的数据为止。循环一开始就用 get_user 从 ptr 指向的用户空间出一个 int32 的数据到 kernel 空间(get_usercopy_from_user 的区别是,一个是 copy 一个简单的变量,一个是 copy 一块内存块)。然后接着把 ptr 指针移动一个 int32 大小。这里注意下,前面 ioctl 那 copy_from_user 是从用户空间得到 binder_write_read 结构(地址在 ioctl 的参数里面),而这里从用户空间 copy 的是保存在 binder_write_read write_buffer 中的地址,也就是前面 mOut 的 mData 的地址。所以要根据前面打包的格式来读(看上面的图)。前面说了 parcel 的读和写对应。所以这里先取 cmd(是 BC_TRANSACTION),然后 parcel 调用 read 接口会自动移动 mDataPos ,binder 驱动里面要自己手动移动指针位置(这里再次看出,parcel 提供简单的内存读写,很灵活,也比较简单,但是同时也比较容易出错)。然后后面继续 copy_from_user 从用户态的 mOut 地址把 binder_transation_data copy 过来(顺带移动指针),然后交由 binder_transation 函数处理(这篇的流程其实和前面通信模型是一样的,但是本篇主要讲数据的流动)。这里先看到后面,binder_transation 处理完后, consumed 就被设置为相应读取的数据大小(这个 consumed 是个指针,其实就设置 binder_write_read 这个结构 write_consumed 这个变量的, binder_write_read 这个结构最后又会被传回用户空间去的,后面能看到)。

至于 binder_transation 中怎么传递到另一个进程中的去,去看我下一篇 binder 的内存管理篇吧,那里有详细的说明,这里不多说这些。反正最后通过 binder_thread_read 传递用 Bn 端的用户空间,然后借着上一篇 Bn 端的 getAndExecuteCommand 从 talkWithDriver 那的 ioctl 返回得到 Bp 端通过 kernel 发送的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
status_t IPCThreadState::getAndExecuteCommand()
{
status_t result;
int32_t cmd;
result = talkWithDriver();
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
// 这里先读 cmd
cmd = mIn.readInt32();
IF_LOG_COMMANDS() {
alog << "Processing top-level Command: "
<< getReturnString(cmd) << endl;
}
result = executeCommand(cmd);
... ...
return result;
}

Bn 端等待 Bp 端的时候,把自己 mIn 的 parcel 的 buffer 传递到 kernel 里面去了,所以 Bp 端发送过来的 parcel 通过 kernel 传递到 Bn 端的 mIn 中去了。
内存管理篇那里 binder_thread_read 会把 cmd 写入 mIn buffer 的第一个 int32 的地址,所以这里先读 int32 的 cmd,然后送给 executeCommand 处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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));
ALOG_ASSERT(result == NO_ERROR,
"Not enough command data for brTRANSACTION");
if (result != NO_ERROR) break;
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);
... ...
Parcel reply;
if (tr.target.ptr) {
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
} else {
const status_t error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
if (error < NO_ERROR) reply.setError(error);
}
if ((tr.flags & TF_ONE_WAY) == 0) {
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
sendReply(reply, 0);
} else {
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
}
mCallingPid = origPid;
mCallingUid = origUid;
}
break;
default:
printf("*** BAD COMMAND %d received from Binder driver\n", cmd);
result = UNKNOWN_ERROR;
break;
}
if (result != NO_ERROR) {
mLastError = result;
}
return result;
}
//======================================
status_t Parcel::read(void* outData, size_t len) const
{
if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) {
memcpy(outData, mData+mDataPos, len);
mDataPos += PAD_SIZE(len);
ALOGV("read Setting data pos of %p to %d\n", this, mDataPos);
return NO_ERROR;
}
return NOT_ENOUGH_DATA;
}

继续看前一篇的那张通信模型的图, Bn 这里是接到的 cmd 是 kernel 发过来的 BR_TRANSACTION, 然后前面 Bp 把 binder_transaction_data 通过 Parcel 写入,这里就要通过 read 来读出来了。内存流的,直接一个强制转化就行了,read 也很简单,就是 memcpy (可以好好看看内存管理篇,kernel 里面传递 parcel data 的 buffer 的技巧很牛x)。然后这里的 Parcel buffer 是临时变量, ipcSetDataReference 设置 freeBuffer 函数怎么回事,内存管理篇都有讲,这里就不多说了。然后最后

b->transact(tr.code, buffer, &reply, tr.flags)

就把 Bp 传递过来的 parcel 传递子类实现 binder 业务的 transact 函数去处理的,顺带,把放返回值的 reply 给传过去了。

我们拿 AM 中的一个简单的接口来看一下(frameworks/base/core/java/android/app/ActivityManagerNative.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ActivityManagerNativeProxy: Bp 端
public boolean finishActivity(IBinder token, int resultCode, Intent resultData)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
// 写入本服务接口的标志,一个字符串,一般是完整的类名 android.xx.xx
data.writeInterfaceToken(IActivityManager.descriptor);
// 参数1: flat_binder_object 特殊类型
data.writeStrongBinder(token);
// 参数2: int 类型的
data.writeInt(resultCode);
if (resultData != null) {
// 参数3: 一个自定义类型的对象,支持 Parcelable 的
// 在开始写一个 1,标记一下
data.writeInt(1);
resultData.writeToParcel(data, 0);
} else {
// 如果传递的参数非法,标记写0
data.writeInt(0);
}
mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
return res;
}
//============================================
// ActivityManagerNative: Bn 端
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
... ...
case FINISH_ACTIVITY_TRANSACTION: {
// 先读接口标志,对比下是不是本服务的 Bp 端发过来的请求
data.enforceInterface(IActivityManager.descriptor);
// 参数1: 特殊 flat_binder_object
IBinder token = data.readStrongBinder();
Intent resultData = null;
// 参数2: int 类型
int resultCode = data.readInt();
// 参数3: Parcelable 类型自定义对象
// 前面如果传递的参数2合法,这里读出的就是 1
if (data.readInt() != 0) {
// 把内存流的 parcel 传递给实现了 Parcelable 结构的自定义类
// 通过数据在 Bn 端重新创建一个对象
resultData = Intent.CREATOR.createFromParcel(data);
}
// 参数解析完毕,调用真正的业务函数实现功能
boolean res = finishActivity(token, resultCode, resultData);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
}
... ...
}
return super.onTransact(code, data, reply, flags);
}

上面看代码中的注释就差不多了,顺序都是一一对应的。然后说说那个 Parcelable 这个接口。这个接口最主要就是2个函数:writeToParcel、createFromParcel 这2个,一个相当于是序列化,一个是反序列化。就是自己类自己实现了的,再复杂的对象都可以通过 Parcel 前面的那些基本类型来存储。

大型数据

大型数据主要是通过 Parcel 的匿名共享内存(Ashmem)接口来使用的(writeBlob、readBlob),当然你也可以使用 writeInPlace 使用普通内存来传递,效率么,呵呵(还有不能超过 binder 的 1MB 大小的限制哦)。 这个话去看我的那篇专门说 ashmem 的吧。

binder 对象

Parcel 有一个特殊的结构叫 flat_binder_object。这个是专门用来传递 binder 对象的(其实这个在 ashmem 篇里发现这个还可以传递文件描述 fd,咋这只说 binder 句柄)。

这里我略过 Parcel 的 java 接口 和 jni 的马甲了,直接拿 native 的代码说,稍微简洁一些。

我还是以上面 AM 里面那个例子说。里面在 IPC 有个要传递的对象是 IBinder 类型的,通过原型篇的分析,这个就是 binder 对象了,这里说的 binder 句柄传递,就是针对这一类型的数据。这里就是调用了 Parcel 的2个接口,我们先来看看写的那个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
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) {
// 区分是 Bn(localBinder) 还是 Bp(remoteBinder)
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
// 如果是 Bp 的话,则保存 Bp 的 handle 值
// Bp 的 type 是 BINDER_TYPE_HANDLE
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
// 如果是 Bn 的话,直接保存 binder 对象本身
// Bn 的 type 是 BINDER_TYPE_BINDER
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}
// 前面只是设置 flat_binder_object,这个函数才是真正写入 parcel 数据
return finish_flatten_binder(binder, obj, out);
}

Parcel 其实还有 writeWeakBinder,但是这里只管 writeStrongBinder,而且一般也是 strong 用的多。先说说 flat_binder_object 这个东西,它是 kernel binder 驱动里面的一个结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* This is the flattened representation of a Binder object for transfer
* between processes. The 'offsets' supplied as part of a binder transaction
* contains offsets into the data where these structures occur. The Binder
* driver takes care of re-writing the structure type and data as it moves
* between processes.
*/
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;
};

这个注释就已经真相了,这个玩意就是专门拿来传 binder 对象的(前面说了还有 fd),而且 offsets 就是这个东西的在传递数据中的位置,就是前面说的 parcel 中那个 mObjects 其实是个偏移来的啦。然后 binder 和 handle 是一个 union,就是说这个 binder 对象要么是 Bn(local),要么是 Bp(remote)。

那么我接下去看 finish_flatten_binder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 内联函数 -_-||
inline static status_t finish_flatten_binder(
const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out)
{
return out->writeObject(flat, false);
}
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
// 这里得同时判断 mData 和 mObjects 够不够咧
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;
// Need to write meta-data?
if (nullMetaData || val.binder != NULL) {
// 再注意这里,保存刚刚写 flat_binder_object 的开始的地址
// 这里就能说明 mObjects 保存的是偏移了。
mObjects[mObjectsSize] = mDataPos;
// 给 flat_binder_object 保存的 binder 对象增加引用计数
acquire_object(ProcessState::self(), val, this);
// 数据中保存的 object 数量 +1
mObjectsSize++;
}
// remember if it's a file descriptor
if (val.type == BINDER_TYPE_FD) {
if (!mAllowFds) {
return FDS_NOT_ALLOWED;
}
mHasFds = mFdsKnown = true;
}
return finishWrite(sizeof(flat_binder_object));
}
// 空间不够就和前面一样,调整内存大小
if (!enoughData) {
const status_t err = growData(sizeof(val));
if (err != NO_ERROR) return err;
}
// 这里还有可能保存 offset 的空间不够了
if (!enoughObjects) {
size_t newSize = ((mObjectsSize+2)*3)/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;
}

最后是调用 writeObject(object 只能是 flat_binder_object) 来写到 parcel 中去。这里和前面的差不多,都得先判断空间够不够,但是这里还得多判断一个 mObjects 的空间够不够。不够的和前面一样调用 growData 去调整大小。这里同样多处理一个 mObjects 空间调整,这里很简单了就是 realloc 一下就行了。这里看代码就知道 flat_binder_object 是保存在 mData 的区域的,而且后面几句代码彻底说明了 mObjects 保存的是偏移地址。最后 finishWrite 和前面一样,把 mDataPos 移动一下。

这里多嘴说下 acquire_object,我就说我烦 android 的那个啥智能指针,也不是太智能的样子,这里还得显示的增加下引用计数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void Parcel::acquireObjects()
{
const sp<ProcessState> proc(ProcessState::self());
size_t i = mObjectsSize;
uint8_t* const data = mData;
size_t* const objects = mObjects;
while (i > 0) {
i--;
// 看到 flat_binder_object 怎么取了的没,首地址 + 偏移地址
const flat_binder_object* flat
= reinterpret_cast<flat_binder_object*>(data+objects[i]);
acquire_object(proc, *flat, this);
}
}
void acquire_object(const sp<ProcessState>& proc,
const flat_binder_object& obj, const void* who)
{
switch (obj.type) {
case BINDER_TYPE_BINDER:
if (obj.binder) {
LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie);
static_cast<IBinder*>(obj.cookie)->incStrong(who);
}
return;
case BINDER_TYPE_WEAK_BINDER:
if (obj.binder)
static_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who);
return;
case BINDER_TYPE_HANDLE: {
const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
if (b != NULL) {
LOG_REFS("Parcel %p acquiring reference on remote %p", who, b.get());
b->incStrong(who);
}
return;
}
case BINDER_TYPE_WEAK_HANDLE: {
const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
if (b != NULL) b.get_refs()->incWeak(who);
return;
}
case BINDER_TYPE_FD: {
// intentionally blank -- nothing to do to acquire this, but we do
// recognize it as a legitimate object type.
return;
}
}
ALOGD("Invalid object type 0x%08lx", obj.type);
}

然后 parcel 中内存分配应该是这样的:

然后打包了 flat_binder_object 的 parcel 就传到 kernel 的 binder 驱动里面去了。驱动里面有做特殊处理的,驱动里的处理放到后面一篇说 ServiceManager 那里细说,这里只要知道驱动里面倒腾了一下就到目标进程了,然后目标进程可以使用 parcel 的读接口读到之前写的 binder 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
// 这个 inline 函数是耍存在感的么 -_-||
inline static status_t finish_unflatten_binder(
BpBinder* proxy, const flat_binder_object& flat, const Parcel& in)
{
return NO_ERROR;
}
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_BINDER:
// Bn 本地的直接强转一下
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
// Bp 的话要通过 handle 构造一个远程的代理对象(Bp 对象)
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}

readStrongBinder 其实挺简单的,是本地的可以直接用,远程的那个 getStrongProxyForHandle 也是放到后面 ServiceManager 再细说。到这里目标进程就收到原始进程传递过来的 binder 对象了,然后可以转化为 binder 的 interface 调用对应的 IPC 接口。

然后最后看下清理的情况,由于 binder 实现的接口中,Parcel 基本都是局部变量,所以 IPC 调用一结束,就会调用析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Parcel::~Parcel()
{
freeDataNoInit();
}
void Parcel::freeDataNoInit()
{
if (mOwner) {
//ALOGI("Freeing data ref of %p (pid=%d)\n", this, getpid());
mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie);
} else {
// 这里看下面这个分支,mOwner 是 IPC 传递参数的 parcel 专用的
// 减少引用计数,还得手动减少 -_-||
releaseObjects();
// 直接 free mData 和 mObjects
// 申请的是 malloc,释放就是 free 咯
if (mData) free(mData);
if (mObjects) free(mObjects);
}
}
void Parcel::releaseObjects()
{
const sp<ProcessState> proc(ProcessState::self());
size_t i = mObjectsSize;
uint8_t* const data = mData;
size_t* const objects = mObjects;
// 这里和 acquireObjects 很像呐
while (i > 0) {
i--;
const flat_binder_object* flat
= reinterpret_cast<flat_binder_object*>(data+objects[i]);
release_object(proc, *flat, this);
}
}
void release_object(const sp<ProcessState>& proc,
const flat_binder_object& obj, const void* who)
{
switch (obj.type) {
case BINDER_TYPE_BINDER:
if (obj.binder) {
LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie);
static_cast<IBinder*>(obj.cookie)->decStrong(who);
}
return;
case BINDER_TYPE_WEAK_BINDER:
if (obj.binder)
static_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who);
return;
case BINDER_TYPE_HANDLE: {
const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
if (b != NULL) {
LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get());
b->decStrong(who);
}
return;
}
case BINDER_TYPE_WEAK_HANDLE: {
const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
if (b != NULL) b.get_refs()->decWeak(who);
return;
}
case BINDER_TYPE_FD: {
if (obj.cookie != (void*)0) close(obj.handle);
return;
}
}
ALOGE("Invalid object type 0x%08lx", obj.type);
}

这里的函数都不复杂,我直接贴到底了。其实就是 free 掉之前 malloc 的 mData 和 mObjects,还有就是前面手动增加了的引用计数,这里得再手动减少(这玩意就是麻烦)。

其实传递 binder 对象,最关键的地方其实在 kernel 的 binder 驱动里面,但是鉴于这篇已经够长了,而且这个和 ServiceManager 关系也挺密切的,所以决定把这块地方放到 ServiceManager 那篇去。

Parcel 是 android binder 通信中扮演着数据打包、解包的角色,是比较重要的一个东西。它的内存结构其实很简单,以后自己用的时候要注意下遵守规则(读、写顺序一致)。然后它可以传递一些小数据、也还可以传一些二进制流,对于大型数据提供匿名共享内存(Ashmem)的支持,它还有一个很特殊的功能,就是传递 binder 对象,保证了 binder IPC 通信的正常使用。

原文地址: http://light3moon.com/2015/01/28/Android%20Binder%20%E5%88%86%E6%9E%90%E2%80%94%E2%80%94%E6%95%B0%E6%8D%AE%E4%BC%A0%E9%80%92%E8%80%85%5BParcel%5D/

Android Binder 分析——数据传递者(Parcel)相关推荐

  1. Android Binder 分析——匿名共享内存(Ashmem)

    前面分析了 binder 中用来打包.传递数据的 Parcel,一般用来传递 IPC 中的小型参数和返回值.binder 目前每个进程 mmap 接收数据的内存是 1M,所以就算你不考虑效率问题用 P ...

  2. Android Binder 分析——通信模型

    通信就要搞一些协议,binder 的比较简单,但是也有一个基本的模型,这里以最基本的一次 IPC 调用来说明一下.然后涉及的代码主要在(这里不列 java 层的代码了,java 层的代码前面原理篇分析 ...

  3. Android Binder 分析——匿名共享内存(好文)

    原址 文章目录 1. 原理概述 2. java 层接口 3. native 层接口 4. kernel 驱动 5. 例子 前面分析了 binder 中用来打包.传递数据的 Parcel,一般用来传递 ...

  4. Android Binder 分析——原理

    分析之前说一下原理.为要 android 要搞这么复杂的一个东西.那是因为 android 是个多进程的系统,进程间的数据交换.相互调用(某几个程序配合完成某些业务)就涉及跨进程通信.2个进程不能直接 ...

  5. Android中的数据传递之Parcelable接口

    对于Android来说传递复杂类型,主要是将自己的类转换为基础的字节数组,Activity之间传递数据是通过Intent实现的. Android序列化对象主要有两种方法,实现Serializable接 ...

  6. android数据回传的代码,android Activity之间数据传递 Parcelable和Serializable接口的使用...

    Activity之间传数据时,为了避免麻烦,往往会将一些值封装成对象,然后将整个对象传递过去.传对象的时候有两种情况,一种是实现Parcelable接口,一种是实现Serializable接口. 0. ...

  7. Android组件间数据传递

    Intent是什么?     是一种在运行时绑定的,链接程序中两个不同组件的组件 Intent做什么?     1 向Android操作系统请求资源     2 组件之间进行数据传递(通信)      ...

  8. android Module之间数据传递

    方法一:使用接口回调 (1)在子module创建回调接口(参数可变) public interface OnChangeLisener {void onChanged(Date date);} (2) ...

  9. 理解Android Binder机制(3/3):Java层

    本文是Android Binder机制解析的第三篇,也是最后一篇文章.本文会讲解Binder Framework Java部分的逻辑. Binder机制分析的前面两篇文章,请移步这里: 理解Andro ...

最新文章

  1. keil编写正弦函数_【高中数学】62个重要函数图像
  2. dellr320故障_dell r320服务器,idrac问题
  3. sqlite DLL load failed
  4. 查看和修改Oracle数据库服务器端的字符集
  5. P3954 [NOIP2017 普及组] 成绩(python3实现)
  6. MATLAB目标检测恒虚警技术,一种自适应的恒虚警率目标检测方法
  7. 95-136-043-源码-Operator-CoProcessOperator
  8. android ascii 比较大小写,为什么可以通过ASCII中的字母排序规则来进行字母的大小写转换?...
  9. 赛锐信息-AMS系列产品简介
  10. react-router v4 路由规则解析
  11. Python 学习第一周
  12. img标签的alt属性和title属性的区别
  13. 网络篇 - netty实现高并发安全聊天客户端
  14. Android 6.0/7.0权限
  15. 【实践*感悟】程序员的品质
  16. 网易云音乐数据服务之路
  17. 软件生存周期的图示——DO-178B/ED-12B学习笔记之九
  18. 中兴笔试题目总结(一)C++部分
  19. 这哥们儿的日志让我的心情好得一塌糊涂(ZZ)
  20. 王良:你牛(有趣又特别的牛年祝福)

热门文章

  1. 常用chrome插件
  2. tf.truncated_normal的用法
  3. Go 语言web 框架 Gin 练习5
  4. Python:安装dlib库
  5. pragma comment的使用 pragma预处理指令详解
  6. latex箭头表示符号
  7. spring_整体系统
  8. Linux系统中查看图片信息
  9. 科大星云诗社动态20201222
  10. pyecharts离线使用说明