http://blog.csdn.net/a254373829/article/details/8026078
binder是用于进程间通信的一种机制, 通信的本质就是数据的交换(流动), 通信需要发送方和接收方的参与才能完成一次数据传输。发送方和接收方必须遵守一定的协议才能保证数据传输的正确性,在android系统里,这种协议是通过ioctl系统调用和约定的命令实现的,有一些命令还实现了自己的一套协议(子命令),有些命令只能由服务端(service)使用,约定全局命令如下:
命令宏(cmd) 数据(arg)
BINDER_WRITE_READ (由于binder驱动没有实现read/write接口, 因些数据的读写是通过该命令实现的) struct binder_write_read (该结构在用户空间和内核都存在)
BINDER_SET_MAX_THREADS (only service, 设置线程池的大小) int max_threads
BINDER_SET_CONTEXT_MGR (only binder manager) --(木有参数)
BINDER_THREAD_EXIT (only service ?) --(木有参数)
BINDER_VERSION --(木有参数)
全局的命令宏都是以BINDER_开头的
写协议实现的子命令宏:
命令宏(cmd) 数据(arg)
BC_TRANSACTION
BC_REPLY struct binder_transaction_data(存在于用户和内核空间,是对应的)
BC_FREE_BUFFER 指向需要释放的缓存区的指针(该指针位于收到的binder数据包中)
读协议实现的子命令宏 :
BR_SPAWN_LOOPER
BR_TRANSACTION
BR_REPLY (命令对应的参数结构为struct binder_transaction_data, 分别对应发送方的BC_TRANSACTION和BC_REPLY)
binder是用面向对象的思想实现的,当用户态应用进程向内核传递一个或多个binder(由struct binder_transaction_data结构来表述flat binder在发送数据缓存的位置和个数)的时候,内核会把类型为binder_transaction_data 的对象内容(位于用户空间)复制到内核中对应的一个binder_transaction_data对象中(位于内核空间) ,然后在根据内核空间的binder_transaction_data对象和通信的双方等信息来构造一个类型为binder_transaction对象,这个对象描述了这次传输过程中所有相关的信息,如收发双方,接收缓存等,代码将用户空间需要传输的数据(flat binder对象为数据的一部分) 复制到binder_transaction对象所拥有的内核缓存区,然后通过binder_transaction对象中的work成员挂到接收线程的一个特定的数据等待队列当中等待接收,如果目标线程的等待队列有正在等待的线程,则唤醒它来处理数据,类似于生产者和消费者模型。 在接收线程的处理过程中,会到特定的数据等待队列中获取一个传输数据并对其内容解析来构造一个内核的binder_transaction_data对象 (主要的解析过程是将接收线程内核的binder节点中保存的用户态的binder实体的地址保存在binder_transaction_data对象的target.ptr成员中,以便接收线程返回到用户态后能找到正确的binder实体来处理接收到的消息,另外,还将发送方的 BC_TRANSACTION/BC_REPLY命令转换为对应的BR_TRANSACTION/BR_REPLY命令来返回给用户空间指示接收到的命令),然后将该对象的内容复制到用户空间传入的对应的binder_transaction_data对象中,最后在通过copy_to_user将内核中的binder_transaction_data对象的内容复制到用户 空间对应的binder_transaction_data对象中。
---------------------------------我是分割线------------------------------------------------
看着一年前写得东西,竟然有些许恍惚, 感觉不是自己写的一样,有些东西长时间的不用,就会慢慢地生疏甚至淡忘,其实这也可以认为是自己没能真正地把握它。 这两天又遇到binder相关的bug, 又开始看binder相关的代码, 每次看别人对binder的理解,然后再看代码,感觉也不是很难懂,可是过段时间,又会忘得干干净净, 也许知识要转换成自己的理解才能说真的懂了,也许我应该实实在在的写个demo,真正地去使用它,估计才能真正地懂了。
看了两天的代码,简单总结一下吧
1. binder引用号(用户空间,一个简单的整形变量)----> binder引用(内核空间, 用binder_ref结构体来描述) ----> binder实体(内核空间,用binder_node结构体来描述) --->用户空间的binder实体
binder引用号和binder引用类似于文件描述符/句柄和文件对象的关系,两者是一一对应的,文件描述符和文件对象是通过数组来映射的,也就是文件描述符其实只是一个数组索引而已, 而binder引用号和binder引用是通过一颗红黑树来一一映射的,引用号作为键值来找到对应的引用。
2. binder引用和binder实体对象一般从属于某个进程,因此一个进程可能拥有多个binder引用和binder实体,在binder驱动里面为每个进程维护了两颗红黑树来分别保存引用和实体。一个binder实体可能有多个binder引用关联, 这是通过binder实体里的refs成员(struct hlist_head,一个链表头)来实现的。binder引用里有个node成员(struct binder_node *)来指向对应的binder实体。
3. 内核中的binder实体和用户空间的binder实体也是一一对应的, 当应用程序向servicemananger注册一个binder实体时, 会将该实体的用户态指针保存在内核态binder实体的ptr指针成员中(还有一个cookie成员也用于保存用户态的binder实体的附加信息)。
从2, 3可以得到1的实现过程, 用户空间传递下来的binder引用号通过红黑树查找对应的binder引用对象, binder引用的node成员可以找到属于某个进程的内核binder实体, 最后通过内核binder实体的ptr成员就可以找到用户空间的binder实体了,从而也就达到了通信的过程。
4. 数据传输过程:
网上抠得图,很直观
http://www.linuxidc.com/upload/2011_07/110726160293041.jpg
应用程序通过ioctl系统调用向binder驱动传递一个BINDER_WRITE_READ命令及所需的数据, 该数据由binder_write_read结构体承载, 读和写操作可以当作一个一条命令来执行,而且多条读操作或者写操作可以放在一起,这样就减少了系统调用的开销, 如图所示, 多条写操作可以放在一个缓冲区,也是以命令+数据的格式存放的, 最重要的写命令是BC_TRANSACTION, 带一个binder_transaction_data结构的数据, 该结构只是一个消息头,真正有效的数据是由结构为flag_binder_object的对象承载的,而这些对象所在的位置则由消息头里面的相关成员来描述(既buffer, data_size, offsets_size, offsets)。
[cpp] view plaincopyprint?
1. struct flat_binder_object {
2. /* 8 bytes for large_flat_header. */
3. unsigned long type;
4. unsigned long flags;
5.
6. /* 8 bytes of data */
7. union {
8. void *binder; /* local object */
9. signed long handle; /* remote object */
10.
11. }
12. /* extra data associated with local object */
13. void *cookie;
14. }
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;
}
[cpp] view plaincopyprint?
1. struct binder_transaction_data {
2. /* The first two are only used for bcTRANSACTION and brTRANSACTION, identifying the target and contents of the transaction. */
3. union {
4. size_t handle; //客户端传递下来的
5. void *ptr; //返还给服务端的用户态binder实体指针
6. } target;
7. void *cookie;
8. unsigned int code; //由server约定的命令码
9. unsigned int flags;
10. pid_t sender_pid;
11. uid_t sender_euid;
12. size_t data_size;
13. size_t offsets_size;
14.
15. union {
16. struct {
17. const void *buffer;
18. const void *offsets;
19. } ptr;
20. uint8_t buf[8];
21.
22. } data;
23.
24. }
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; //客户端传递下来的
void *ptr; //返还给服务端的用户态binder实体指针
} target;
void *cookie;
unsigned int code; //由server约定的命令码
unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size;
size_t offsets_size;
union {
struct {
const void *buffer;
const void *offsets;
} ptr;
uint8_t buf[8];
} data;
}
flat_binder_object对象在从用户空间传到内核空间时,会从接受进程的缓冲区分配一块内存用于保存上图中的两个buffer(既一个data, 一个offsets), 核心代码如下:
[cpp] view plaincopyprint?
1. void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply)
2. {
3. struct binder_transaction *t;
4. ...
5. t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offset_size, !reply && (t->flags & TF_ONE_WAY));
6. offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
7. if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)
8. return ERROR;
9. if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) //两个buffer挨在一块
10. reutnr ERROR;
11.
12. off_end = (void *)offp + tr->offsets_size;
13.
14. for (; offp < off_end, offp++) {
15. struct flat_binder_object *fp;
16. fp = (struct flat_binder_object *)(t->buffer->data + *offp);
17. switch(fp->type) { //对用户空间传递下来的flat binder进行解析和转换
18. case BINDER_TYPE_BINDER:
19. case BINDER_TYPE_WEAK_BINDER: {
20. struct binder_ref *ref;
21. struct binder_node *node = binder_get_node(proc, fp->binder); //根据用户态binder实体的地址为键值在红黑树上找对应的内核binder实体
22. if (node == NULL) {
23. node = binder_new_node(proc, fp->binder, fp->cookie); //创建一个新的对应于用户空间binder实体的内核版本,并保存用户版本的binder实体的地址及附加信息
24. }
25. ref = binder_get_ref_for_node(target_proc, node); //在接收进程中找到/创建对应于此binder实体的binder引用, 在一个进程中一个引用只对应于一个binder实体,所以可以用内核binder实体地址作为键值在红黑树中找.
26.
27. /* 核心解析和转换代码, flat_binder_object类型的转换, 为什么要转,因为进程之间地址空间是相互隔离的,你直接把一个进程中的 Binder地址传给另一个进程是毛有用的,所以binder驱动会把地址转换成引用号在传给接受进程, 接受进程就可以利用这个binder引用号去申请服务端的Binder提供的服务, 这个连接过程上面已经描述过了 */
28. if (fp->type == BINDER_TYPE_BINDER)
29. fp->type = BINDER_TYPE_HANDLE;
30. else
31. fp->type = BINDER_TYPE_WEAK_HANDLE;
32. fp->handle = ref->desc; //binder地址不在有效
33. } break;
34.
35. /* 把binder引用转换成内核binder实体 */
36. case BINDER_TYPE_HANDLE:
37. case BINDER_TYPE_WEAK_HANDLE: {
38. struct binder_ref *ref = binder_get_ref(proc, fp->handle); //根据引用号找到对应的内核binder引用对象
39. if (ref->node->proc == target_proc) {
40. if (fp->type == BINDER_TYPE_HANDLE)
41. fp->type = BINDER_TYPE_BINDER;
42. else
43. fp->type = BINDER_TYPE_WEAK_BINDER;
44. fp->binder = ref->node->ptr;
45. fp->cookie = ref->node->cookie;
46. binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
47. }
48.
49. } break;
50.
51. case BINDER_TYPE_FD: { //文件形式的binder
52. <span style="white-space: pre;"> </span>int target_fd;
53. <span style="white-space: pre;"> </span>struct file *file;
54. <span style="white-space: pre;"> </span>if (!target_node->accept_fds) //接收进程不允许文件形式的binder
55. <span style="white-space: pre;"> </span>return ERROR;
56. file = fget(fp->handle);
57. <span style="white-space: pre;"> </span> target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
58. <span style="white-space: pre;"> </span> task_fd_install(target_proc, target_fd, file); //绑定文件描述符和文件对象, 这样两个不同的进程共享同一个文件对象,也即同一组文件操作表
59. <span style="white-space: pre;"> </span> fp->hnadle = target_fd; //将发送进程的文件描述符/引用号替换为接收进程可以识别的文件描述符
60. } break;
61. }
62. ...
63. t->work.type = BINDER_WORK_TRANSACTION;
64. list_add_tail(&t->work.entry, target_list); //数据神马的都解析好后,放到一个队列里去,等待读线程去消耗
65.
66. if (target_wait)
67. wake_up_interruptible(target_wait);
void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply)
{
struct binder_transaction *t;
...
t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offset_size, !reply && (t->flags & TF_ONE_WAY));
offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)
return ERROR;
if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) //两个buffer挨在一块
reutnr ERROR;
off_end = (void *)offp + tr->offsets_size;
for (; offp < off_end, offp++) {
struct flat_binder_object *fp;
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
switch(fp->type) { //对用户空间传递下来的flat binder进行解析和转换
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct binder_ref *ref;
struct binder_node *node = binder_get_node(proc, fp->binder); //根据用户态binder实体的地址为键值在红黑树上找对应的内核binder实体
if (node == NULL) {
node = binder_new_node(proc, fp->binder, fp->cookie); //创建一个新的对应于用户空间binder实体的内核版本,并保存用户版本的binder实体的地址及附加信息
}
ref = binder_get_ref_for_node(target_proc, node); //在接收进程中找到/创建对应于此binder实体的binder引用, 在一个进程中一个引用只对应于一个binder实体,所以可以用内核binder实体地址作为键值在红黑树中找.
/* 核心解析和转换代码, flat_binder_object类型的转换, 为什么要转,因为进程之间地址空间是相互隔离的,你直接把一个进程中的Binder地址传给另一个进程是毛有用的,所以binder驱动会把地址转换成引用号在传给接受进程, 接受进程就可以利用这个binder引用号去申请服务端的Binder提供的服务, 这个连接过程上面已经描述过了 */
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc; //binder地址不在有效
} break;
/* 把binder引用转换成内核binder实体 */
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct binder_ref *ref = binder_get_ref(proc, fp->handle); //根据引用号找到对应的内核binder引用对象
if (ref->node->proc == target_proc) {
if (fp->type == BINDER_TYPE_HANDLE)
fp->type = BINDER_TYPE_BINDER;
else
fp->type = BINDER_TYPE_WEAK_BINDER;
fp->binder = ref->node->ptr;
fp->cookie = ref->node->cookie;
binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
}
} break;
case BINDER_TYPE_FD: { //文件形式的binder
int target_fd;
struct file *file;
if (!target_node->accept_fds) //接收进程不允许文件形式的binder
return ERROR;
file = fget(fp->handle);
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
task_fd_install(target_proc, target_fd, file); //绑定文件描述符和文件对象, 这样两个不同的进程共享同一个文件对象,也即同一组文件操作表
fp->hnadle = target_fd; //将发送进程的文件描述符/引用号替换为接收进程可以识别的文件描述符
} break;
}
...
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list); //数据神马的都解析好后,放到一个队列里去,等待读线程去消耗
if (target_wait)
wake_up_interruptible(target_wait);